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,658
Last edited:

alhaim

Member
Licensed User
Longtime User
Why after 15 minutes on my Samsung Galaxy Tab, and after only 15 seconds on my IDEOS android phone, does the data stored with keyvaluestore disappear? Is there some code I am missing? What do I have to change in the code to make a permanent save? Thanks.
 
Last edited:

RonC.

Member
Licensed User
Longtime User
Tried to use Keystore, but it's as if Basic4Android doesn't see the Type and I'm not sure why... Your example works great, but when I even try to copy over the first line into a new program it displays in red... I used the Core, SQL, and RandomAccessFile Libraries... What am I forgetting or doing wrong?

Note: When I attempted to zip and attach the file to the forum (from within Basic4Android) I got the error: "An Error occurred. Could not find a part of the path 'C:\Users\rcotton\Documents\My Basic4Android\KeyStoreValue\Objects"
 

Attachments

  • KeyValueStoreProblem.png
    KeyValueStoreProblem.png
    110.8 KB · Views: 411
Last edited:

Douglas Farias

Expert
Licensed User
Longtime User
how can i get only
only Item #1 in string?
lala = Item #1 ?
(ArrayList) [Item #1, Item #2, Item #3, Item #4, Item #5]
 

driesvp

Member
Licensed User
Longtime User
Strange thing: when putting and reading a value to KVS it's possible to recall it from the same app but not from another app. I tought it was put on the external store so it would be accessible from all apps?

first app
B4X:
    kvs.Initialize(File.DirDefaultExternal, "Versions")

    msgbox(kvs.GetSimple("ipadres"),"") ' the value was stored in this app

second app:

B4X:
    kvs2.Initialize(File.DirDefaultExternal, "Versions")
    Msgbox(kvs2.GetSimple("ipadres"),"")
 

alexwekell

Member
Licensed User
Longtime User
Android L seems to have broken this?

I used to have a simple implementation:

B4X:
kvs.Initialize(File.DirDefaultExternal,"datastore")

And this is the code I've narrowed it down to. When I comment this out, my app starts correctly (though it doesn't work)

I keep getting nosuchfield errors...

B4X:
LogCat connected to: 03fbf34c093c82f9
--------- beginning of crash


Fatal signal 6 (SIGABRT), code -6 in tid 28638 (AsyncTask #1)
Fatal signal 6 (SIGABRT), code -6 in tid 28732 (AsyncTask #1)
Fatal signal 11 (SIGSEGV), code 1, fault addr 0xb51bd21a in tid 20187 (Binder_1)
--------- beginning of events
--------- beginning of system


Data connection inactive timestamp 27942082 is before start time 21422376000
Data connection inactive timestamp 27960692 is before start time 21422376000
Data connection inactive timestamp 27965912 is before start time 21422376000
Data connection inactive timestamp 28049982 is before start time 21422376000
Data connection inactive timestamp 28058772 is before start time 21422376000
Data connection inactive timestamp 28070822 is before start time 21422376000
Data connection inactive timestamp 28076095 is before start time 21422376000
Data connection inactive timestamp 28108352 is before start time 21422376000
Data connection inactive timestamp 28164212 is before start time 21422376000


Starting service ServiceState{17c27b92 com.qualcomm.qcrilmsgtunnel.QcrilMsgTunnelService pkg=com.qualcomm.qcrilmsgtunnel proc=17c27b92} without owner
Starting service ServiceState{3940baba com.google.android.tts.service.GoogleTTSService pkg=com.google.android.tts proc=3940baba} without owner
Starting service ServiceState{17c27b92 com.qualcomm.qcrilmsgtunnel.QcrilMsgTunnelService pkg=com.qualcomm.qcrilmsgtunnel proc=17c27b92} without owner
Data connection inactive timestamp 28428512 is before start time 21422376000
--------- beginning of main


** Activity (main) Create, isFirst = true **


** Activity (main) Resume **


** Activity (main) Pause, UserClosed = true **
java.lang.RuntimeException: java.lang.NoSuchFieldException: mostCurrent
    at anywheresoftware.b4a.keywords.Common$5.run(Common.java:955)
    at android.os.Handler.handleCallback(Handler.java:738)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:135)
    at android.app.ActivityThread.main(ActivityThread.java:5070)
    at java.lang.reflect.Method.invoke(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:372)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:836)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:631)
Caused by: java.lang.NoSuchFieldException: mostCurrent
    at java.lang.Class.getDeclaredField(Class.java:886)
    at anywheresoftware.b4a.keywords.Common$5.run(Common.java:914)
    ... 8 more
dns64_detection/no dns64, giving up
Data connection inactive timestamp 28851542 is before start time 21422376000
** Activity (main) Create, isFirst = true **


** Activity (main) Resume **
** Activity (main) Pause, UserClosed = true **
java.lang.RuntimeException: java.lang.NoSuchFieldException: mostCurrent
    at anywheresoftware.b4a.keywords.Common$5.run(Common.java:955)
    at android.os.Handler.handleCallback(Handler.java:738)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:135)
    at android.app.ActivityThread.main(ActivityThread.java:5070)
    at java.lang.reflect.Method.invoke(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:372)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:836)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:631)
Caused by: java.lang.NoSuchFieldException: mostCurrent
    at java.lang.Class.getDeclaredField(Class.java:886)
    at anywheresoftware.b4a.keywords.Common$5.run(Common.java:914)
    ... 8 more
dns64_detection/no dns64, giving up
Data connection inactive timestamp 28918522 is before start time 21422376000
** Activity (main) Create, isFirst = true **
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = true **
java.lang.RuntimeException: java.lang.NoSuchFieldException: mostCurrent
    at anywheresoftware.b4a.keywords.Common$5.run(Common.java:955)
    at android.os.Handler.handleCallback(Handler.java:738)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:135)
    at android.app.ActivityThread.main(ActivityThread.java:5070)
    at java.lang.reflect.Method.invoke(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:372)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:836)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:631)
Caused by: java.lang.NoSuchFieldException: mostCurrent
    at java.lang.Class.getDeclaredField(Class.java:886)
    at anywheresoftware.b4a.keywords.Common$5.run(Common.java:914)
    ... 8 more
dns64_detection/no dns64, giving up
Data connection inactive timestamp 28974532 is before start time 21422376000
** Activity (main) Create, isFirst = true **


** Activity (main) Resume **


** Activity (main) Pause, UserClosed = true **


java.lang.RuntimeException: java.lang.NoSuchFieldException: mostCurrent


    at anywheresoftware.b4a.keywords.Common$5.run(Common.java:955)
    at android.os.Handler.handleCallback(Handler.java:738)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:135)
    at android.app.ActivityThread.main(ActivityThread.java:5070)
    at java.lang.reflect.Method.invoke(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:372)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:836)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:631)
Caused by: java.lang.NoSuchFieldException: mostCurrent
    at java.lang.Class.getDeclaredField(Class.java:886)
    at anywheresoftware.b4a.keywords.Common$5.run(Common.java:914)
    ... 8 more
 

alexwekell

Member
Licensed User
Longtime User
I tested it on the emulator. There were some issues with the rapid debugger. However once these issues were fixed the KeyValueStore example worked correctly in both debug and release modes.

Did you encounter similar issues with other projects?

This is the only project I use it in
 

Douglas Farias

Expert
Licensed User
Longtime User
@Erel how can i create a new key?

Private Sub CreateTable
sql1.ExecNonQuery("CREATE TABLE IF NOT EXISTS main(key TEXT PRIMARY KEY, value NONE)")
End Sub


for example
sql1.ExecNonQuery("CREATE TABLE IF NOT EXISTS main(key TEXT PRIMARY KEY, index BIGINT, value NONE)")

?
 
Status
Not open for further replies.
Top