Android Tutorial KeyValueStore class - Simple and efficient key/value data store

Status
Not open for further replies.
KeyValueStore v2 is available here: https://www.b4x.com/android/forum/threads/b4x-keyvaluestore-2-simple-powerful-local-datastore.63633/

In many cases applications need to store all kinds of data.

Key / value data stores (sometimes referred as NoSQL) can offer an alternative to relational databases (SQL). The key / value store offers a simple functionality. It allows you to store all kinds of values, where each value is mapped to a key. Very similar to Maps (as well as Dictionary, Hashtable, HashMap...). The main difference is that the store is persisted in the file system.

KeyValueStore class uses an SQLite database to store and retrieve all kinds of values.

It uses RandomAccessFile.WriteObject or WriteEncryptedObject to save collections and user types.

Using KeyValueStore is similar to using a Map:
B4X:
Sub Process_Globals
   Private kvs As KeyValueStore
End Sub

Sub Activity_Create(FirstTime As Boolean)
   If FirstTime Then
      kvs.Initialize(File.DirDefaultExternal, "datastore")
   End If
   'put a "simple" value
   kvs.PutSimple("time", DateTime.Now)
   'fetch this value
   Log(DateTime.Time(kvs.GetSimple("time")))

   'put a Bitmap
   kvs.PutBitmap("bitmap1", LoadBitmap(File.DirAssets, "asteroids.png"))
   'fetch a bitmap
   Activity.SetBackgroundImage(kvs.GetBitmap("bitmap1"))

   'remove the bitmap from the store
   kvs.Remove("bitmap1")

   'add a collection
   Dim list1 As List
   list1.Initialize
   For i = 1 To 10
      list1.Add("Item #" & i)
   Next
   kvs.PutObject("list1", list1)

   'fetch the collection
   Dim list2 As List = kvs.GetObject("list1")
   Log(list2)
   'encrypt the list
   kvs.PutEncyptedObject("encrypted list", list1, "topsecret")
   Try
      'note that if you run this example in Debug then it will break on this call. Press F5 to continue...
      list2 = kvs.GetEncryptedObject("encrypted list", "wrong password")
   Catch
      Log("Wrong password!")
   End Try
   list2 = kvs.GetEncryptedObject("encrypted list", "topsecret")
   Log(list2)
End Sub

The public methods of KeyValueStore:
B4X:
'Puts a simple value in the store.
'Strings and number types are considered "simple" values.
Sub PutSimple(Key As String, Value As Object) As Boolean

'Puts an object in the store. This method uses RandomAccessFile.WriteObject to save the object in the store.
'It is capable of writing the following types of objects: Lists, Arrays, Maps, Strings, primitive types and user defined types.
'Combinations of these types are also supported. For example, a Map with several lists of arrays can be written.
'The element type inside a collection must be a String OR primitive Type.
Sub PutObject(Key As String, Value As Object) As Boolean

'Similar to PutObject. Encrypts the object before writing it. Note that you can use it to store "simple" types as well.
Sub PutEncyptedObject(Key As String, Value As Object, Password As String) As Boolean

'Puts a bitmap in the store.
Sub PutBitmap(Key As String, Value As Bitmap) As Boolean

'Reads the data from the input stream and saves it in the store.
Sub PutInputStream(Key As String, Value As InputStream) As Boolean

'Removes the key and value mapped to this key.
Sub Remove(Key As String)

'Returns a list with all the keys.
Sub ListKeys As List

'Tests whether a key is available in the store.
Sub ContainsKey(Key As String) As Boolean

'Deletes all data from the store.
Sub DeleteAll

'Returns a "simple" value. See PutSimple.
Sub GetSimple(Key As String) As String

'Returns an InputStream from the store. See PutInputStream.
Sub GetInputStream(Key As String) As InputStream

'Returns a bitmap from the store. See PutBitmap.
Sub GetBitmap(Key As String) As Bitmap

'Returns an object from the store. See PutObject.
Sub GetObject(Key As String) As Object

'Returns an encrypted object from the store. See PutEncryptedObject.
Sub GetEncryptedObject(Key As String, Password As String) As Object

'Closes the store.
Sub Close

So if you do not need the more advanced features of a relational database then KeyValueStore is your probably best solution for data persisting.

The class is included in the attached example. It depends on the SQL and RandomAccessFile libraries.

V1.01 - Fixes an issue with open cursors.
 

Attachments

  • KeyValueStore.zip
    10.7 KB · Views: 4,656
Last edited:

freetoair

Member
Licensed User
Longtime User
I can not understand why the list "TempList" is not initialized after calls: "Dim TempList As List = kvs.GetObject(lblCustom.Text)"
B4X:
Sub Activity_Resume
      kvs.PutObject("Start",lblCustom.Text)
     Dim TempList As List =kvs.GetObject(lblCustom.Text)
     SeekBar1.Value = TempList.Get(0)
    SeekBar2.Value = TempList.Get(1)
     SeekBar3.Value = TempList.Get(2)
     Convert.konverzija(0)
    VUdata = False
End Sub
 

DonManfred

Expert
Licensed User
Longtime User

freetoair

Member
Licensed User
Longtime User
Yes, I found it, during the declaration "Start" I added one more space. Thanks DonManfred !
 

tufanv

Expert
Licensed User
Longtime User
Hello
When i try to check if there is a key "gecerlitarih"
B4X:
If kvs.GetEncryptedObject("gecerlitarih","pass") = "ok" Then
I am getting
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.Object.equals(java.lang.Object)' on a null object reference
it points to "return null" line of Private Sub getObjectInternal(Key As String, decrypt As Boolean, password As String) As Object

Am i doing stg wrong ?
 

tufanv

Expert
Licensed User
Longtime User
I just put a try before the code and catch - end try at the end. Now it works correctly. Maybe it was because that there werent any key "gecerlitarih"
Please start a new thread for this question and post the relevant code.
 

postasat

Active Member
Licensed User
Longtime User
Hi,

I'm searching for a software to edit on windows the file created with B4A and kvs.
I tried DBBrowser for SQLite, I can open the file but I can't' see my data.

Thanks.
 

derez

Expert
Licensed User
Longtime User
I'm searching for a software to edit on windows the file created with B4A and kvs.
I tried DBBrowser for SQLite, I can open the file but I can't' see my data.
SQlite_Expert does it !
 

Cebuvi

Active Member
Licensed User
Longtime User
Hello,

I have also found that the message:

"An error has occured in sub:Keyvaluestore_initialize (java line:251)
android.database.sqlite.SQLiteCantOpenDatabaseException:
unknown error (code 14): Could not open database
Continue?"

occurs when the manifest it has:
B4X:
android:targetSdkVersion="23"/>

Setting the manifest for APi 22, no longer the problem occurs.

What would be the solution to use KeyValueStore class with API 23?

Thanks.
 

Cebuvi

Active Member
Licensed User
Longtime User
Thanks Manfred,

I've tried adding in the main activity


B4X:
Permission.Initialize
   
    Log ("Done - Has permission = " & Permission.CheckPermission ("android.permission.WRITE_EXTERNAL_STORAGE"))
   
    If Permission.CheckPermission ("android.permission.WRITE_EXTERNAL_STORAGE") = False Then   
        Permission.RequestPermission ("android.permission.WRITE_EXTERNAL_STORAGE")       
    End If

and gives the error before requesting permission.

Cesar
 

Carlos Serra

New Member
Licensed User
Longtime User
Dear Erel,

i've change it and still have that error:

Copying updated assets files (8)
** Service (starter) Create **
Error occurred on line: 13 (KeyValueStore)
android.database.sqlite.SQLiteCantOpenDatabaseException: unknown error (code 14): Could not open database
at android.database.sqlite.SQLiteConnection.nativeOpen(Native Method)
at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:209)
at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:193)
at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:463)
at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:185)
at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:177)
at android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:804)
at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:789)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:694)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:669)
at anywheresoftware.b4a.sql.SQL.Initialize(SQL.java:37)
at b4a.example.keyvaluestore._initialize(keyvaluestore.java:54)
at b4a.example.starter._service_create(starter.java:140)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:697)
at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:336)
at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:246)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.ShellBA.raiseEvent2(ShellBA.java:134)
at anywheresoftware.b4a.BA.raiseEvent(BA.java:153)
at b4a.example.starter.onCreate(starter.java:53)
at android.app.ActivityThread.handleCreateService(ActivityThread.java:2529)
at android.app.ActivityThread.access$1600(ActivityThread.java:141)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1316)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:5041)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
at dalvik.system.NativeStart.main(Native Method)
** Service (starter) Start **

Project Data:
AddManifestText(
<uses-sdk android:minSdkVersion="5" android:targetSdkVersion="22"/>
<supports-screens android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true"
android:anyDensity="true"/>)
SetApplicationAttribute(android:icon, "@drawable/icon")
SetApplicationAttribute(android:label, "$LABEL$")
SetApplicationAttribute(android:theme, "@android:style/Theme.Holo")
'End of default text.


Any other idea?

thanks
 

Douglas Farias

Expert
Licensed User
Longtime User
Dear Erel,

i've change it and still have that error:

Copying updated assets files (8)
** Service (starter) Create **
Error occurred on line: 13 (KeyValueStore)
android.database.sqlite.SQLiteCantOpenDatabaseException: unknown error (code 14): Could not open database
at android.database.sqlite.SQLiteConnection.nativeOpen(Native Method)
at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:209)
at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:193)
at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:463)
at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:185)
at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:177)
at android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:804)
at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:789)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:694)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:669)
at anywheresoftware.b4a.sql.SQL.Initialize(SQL.java:37)
at b4a.example.keyvaluestore._initialize(keyvaluestore.java:54)
at b4a.example.starter._service_create(starter.java:140)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:697)
at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:336)
at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:246)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at anywheresoftware.b4a.ShellBA.raiseEvent2(ShellBA.java:134)
at anywheresoftware.b4a.BA.raiseEvent(BA.java:153)
at b4a.example.starter.onCreate(starter.java:53)
at android.app.ActivityThread.handleCreateService(ActivityThread.java:2529)
at android.app.ActivityThread.access$1600(ActivityThread.java:141)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1316)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:5041)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
at dalvik.system.NativeStart.main(Native Method)
** Service (starter) Start **

Project Data:
AddManifestText(
<uses-sdk android:minSdkVersion="5" android:targetSdkVersion="22"/>
<supports-screens android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true"
android:anyDensity="true"/>)
SetApplicationAttribute(android:icon, "@drawable/icon")
SetApplicationAttribute(android:label, "$LABEL$")
SetApplicationAttribute(android:theme, "@android:style/Theme.Holo")
'End of default text.


Any other idea?

thanks

try change
B4X:
File.DirDefaultExternal

to

B4X:
File.DirInternal
 

Carlos Serra

New Member
Licensed User
Longtime User
Hi Douglas,

you are right. in true was an issue with Android Emulator (i've forget to put available space on Memory Card).

Thanks
 

luke2012

Well-Known Member
Licensed User
Longtime User
Hi @Erel.
Is it possible to store more than one (persistent) "dataset" within a single KVS store assuming to use a (key) naming convention to handle each (persistent) "dataset".
So each (persistent) dataset within the KVS store ideally can represent a SQL db data.

For example each key could have a prefix that represent the (persistent) dataset name and after the key prefix I can have a unique key in order to identify the item within a specific dataset.

Could be a good approach to handle different data entity within the same KVS store?
 
Status
Not open for further replies.
Top