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

Shaun

Member
Licensed User
Longtime User
Is there a way to tell if an object exists in the keyvaluestore database on startup? If it doesn't already exist I would create it.


Thanks
 

Shaun

Member
Licensed User
Longtime User
*On Edit*
I got it to work. Where I save the Map to the KeyValueStore, I neglected to capitalize the first letter of the Key.
DOH! :p

I have most of my program finished except loading and saving the database file. I just don't know where it is. I don't know enough about the Android file system I guess. It's a pain in the butt because I have to hard code it with dummy values to test with. I can't save my map object to my device.

Do I have to create the database manually? Do I create the datastore directory manually? I'm stuck.
 
Last edited:
D

Deleted member 103

Guest
Hi Erel,

I just use this class in my app since yesterday and already have 2 error messages.

1. crash:
java.lang.NullPointerException
in anywheresoftware.b4a.BA.<init>

java.lang.RuntimeException: Unable to create service fg.ItalyStopWatch.smtimer: java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
at android.app.ActivityThread.handleCreateService(ActivityThread.java:2049)
at android.app.ActivityThread.access$2500(ActivityThread.java:155)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1047)
at android.os.Handler.dispatchMessage(Handler.java:130)
at android.os.Looper.loop(SourceFile:351)
at android.app.ActivityThread.main(ActivityThread.java:3814)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:538)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:901)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:659)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
at fg.ItalyStopWatch.smtimer.onCreate(smtimer.java:33)
at android.app.ActivityThread.handleCreateService(ActivityThread.java:2039)
... 10 more
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:538)
at fg.ItalyStopWatch.smtimer.onCreate(smtimer.java:31)
... 11 more
Caused by: java.lang.RuntimeException: java.lang.NullPointerException
at fg.ItalyStopWatch.main.initializeProcessGlobals(main.java:2034)
... 14 more
Caused by: java.lang.NullPointerException
at anywheresoftware.b4a.BA.<init>(BA.java:100)
at fg.ItalyStopWatch.clsstatus.innerInitialize(clsstatus.java:12)
at fg.ItalyStopWatch.clsstatus._initialize(clsstatus.java:51)
at fg.ItalyStopWatch.main._process_globals(main.java:3291)
at fg.ItalyStopWatch.main.initializeProcessGlobals(main.java:2028)
... 14 more

1. crash:
java.lang.NullPointerException
in fg.ItalyStopWatch.keyvaluestore._vvvvvvvvvvvvvvvvvvvv2
vvvvvvvvvvvvvvvvvvvv2 = Public Sub ListKeys As List

java.lang.RuntimeException: Unable to create service fg.ItalyStopWatch.smtimer: java.lang.RuntimeException: java.lang.NullPointerException
at android.app.ActivityThread.handleCreateService(ActivityThread.java:2049)
at android.app.ActivityThread.access$2500(ActivityThread.java:155)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1047)
at android.os.Handler.dispatchMessage(Handler.java:130)
at android.os.Looper.loop(SourceFile:351)
at android.app.ActivityThread.main(ActivityThread.java:3814)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:538)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:901)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:659)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.RuntimeException: java.lang.NullPointerException
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:195)
at anywheresoftware.b4a.BA.raiseEvent(BA.java:153)
at fg.ItalyStopWatch.smtimer.onCreate(smtimer.java:42)
at android.app.ActivityThread.handleCreateService(ActivityThread.java:2039)
... 10 more
Caused by: java.lang.NullPointerException
at fg.ItalyStopWatch.keyvaluestore._vvvvvvvvvvvvvvvvvvvv2(keyvaluestore.java:254)
at fg.ItalyStopWatch.smtimer._service_create(smtimer.java:255)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:538)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:169)
... 13 more

On my device works without problems.

Here is the link of my app.
 
D

Deleted member 103

Guest
Hi Erel,

here is my code:

B4X:
Sub Service_Create
    n.Initialize
    n.Icon = "icon_36_36"
    n.SetInfo(Main.AppName,"",Main)
    n.Sound = False
    n.Vibrate = False

    intRunModus.Initialize

    'Ab Version v1.08 gibt es kein "Initfile" mehr deshalb muss mit diese Variable geprüft werden.
    If File.Exists(File.DirInternal, "Initfile.ini") OR Main.kvs.ListKeys.Size > 0 Then
        CallSub(Main,"goRead_Initfile")
    Else
        CallSub(Main,"goWrite_Initfile")
    End If
    CallSub(Main,"CreateFirstStopWatch")

    Try
        'Initiallisiere beeper
        beep.Initialize
        beep.Load(File.DirAssets,"beep.mp3")
    Catch
        ToastMessageShow(LastException.Message, False)
    End Try
   
    Try
        'Mediaplayer initiallisieren
        mPlayer.Initialize2("mPlayer")
        oldVolume=phone1.GetVolume(phone1.VOLUME_MUSIC)
    Catch
        ToastMessageShow(LastException.Message, False)
    End Try
       
    Timer1.Initialize("Timer1",10)
    Timer1.Enabled = False   
End Sub

B4X:
Sub Process_Globals
    'These global variables will be declared once when the application starts.
    'These variables can be accessed from all modules.

    Dim manager As AHPreferenceManager
    Dim screen As AHPreferenceScreen
    Dim PakageName As String = "fg.ItalyStopWatch"
    Dim AppName As String="StopWatch-4-all-Lite"
    Dim intStatus As clsStatus
    intStatus.Initialize
   
    Dim intRunModus As clsRunModus
    intRunModus.Initialize
   
    Dim strSpeedUnits As clsSpeedUnits
    strSpeedUnits.Initialize
   
    Dim intColor As clsColor
    intColor.Initialize
   
    Dim intSwimstyle As clsSchwimmgstyle
    intSwimstyle.Initialize
   
    Dim tColor As clsButtonColor
    tColor.Initialize
   
    Dim strSwimstyle(4) As String
    'Variablen für die Runde-Zeit-Berechnung
    Dim intLapDistance As Int=100
   
    'Diese Variable wird verhindert das die Externe Taste,
    'wenn gedrückt bleibt, weitere Zeichen sendet.
    Dim IsKeyVolumeDown As Boolean
    Dim IsExternKeyDown(6) As Boolean

    Dim IsTimeRelativeToLap As Boolean

    'Speichert den View's zustand
    Dim lstKeyValue As List
    Dim kvs As KeyValueStore
End Sub

But I think that this could be the problem!
B4X:
Sub Activity_Create(FirstTime As Boolean)
    Activity.LoadLayout("frmMain")
   
    'Serveice Initiallisieren und starten
    StartService(smTimer)

    If FirstTime Then       
        'Settings-Menu
        CreatePreferenceScreen
        If manager.GetAll.Size = 0 Then
            SetDefaults
        Else If manager.GetAll.Size = 3 Then    'Ab Version 2.03 Volume-Control
            manager.setString("lstVolume",10)
            manager.SetString("txtBeepCount",10)
        End If
        intLapDistance=manager.GetString("txtLapDistance")

        smTimer.lstClock.Initialize
       
        lstKeyValue.Initialize
        kvs.Initialize(File.DirInternal, "datastore")
    End If
    ...
    ...
End Sub

The service "sptimer" would later start, or what do you think?
 
D

Deleted member 103

Guest
I've found the problem, it was due to AdMob. I only have the code now moved down and now works.

Old-Code:
B4X:
Sub Activity_Create(FirstTime As Boolean)
    Activity.LoadLayout("frmMain")

    'Init Language-Modul
    mLang.InitLanguage

    'AdMob hinzufügen(Werbung)
    AdView1.Initialize2("AdView1","a14eef5a619dfe1",AdView1.SIZE_SMART_BANNER)
    Activity.AddView(AdView1, 0, pnlStatusleiste.Top - mBBL.GetAddViewHeight, 100%x, mBBL.GetAddViewHeight)
    AdView1.LoadAd
    AdView1.Visible=True
   
    'Serveice Initiallisieren und starten
    StartService(smTimer)

    If FirstTime Then       
        'Settings-Menu
        CreatePreferenceScreen
        If manager.GetAll.Size = 0 Then
            SetDefaults
        Else If manager.GetAll.Size = 3 Then    'Ab Version 2.03 Volume-Control
            manager.setString("lstVolume",10)
            manager.SetString("txtBeepCount",10)
        End If
        intLapDistance=manager.GetString("txtLapDistance")

        smTimer.lstClock.Initialize
       
        lstKeyValue.Initialize
        kvs.Initialize(File.DirInternal, "datastore")
    End If
    ...
    ...
End Sub

Neu Code:
B4X:
Sub Activity_Create(FirstTime As Boolean)
    Activity.LoadLayout("frmMain")

    'Init Language-Modul
    mLang.InitLanguage
   
    'Serveice Initiallisieren und starten
    StartService(smTimer)

    If FirstTime Then       
        'Settings-Menu
        CreatePreferenceScreen
        If manager.GetAll.Size = 0 Then
            SetDefaults
        Else If manager.GetAll.Size = 3 Then    'Ab Version 2.03 Volume-Control
            manager.setString("lstVolume",10)
            manager.SetString("txtBeepCount",10)
        End If
        intLapDistance=manager.GetString("txtLapDistance")

        smTimer.lstClock.Initialize
       
        lstKeyValue.Initialize
        kvs.Initialize(File.DirInternal, "datastore")
    End If

    'AdMob hinzufügen(Werbung)
    AdView1.Initialize2("AdView1","a14eef5a619dfe1",AdView1.SIZE_SMART_BANNER)
    Activity.AddView(AdView1, 0, pnlStatusleiste.Top - mBBL.GetAddViewHeight, 100%x, mBBL.GetAddViewHeight)
    AdView1.LoadAd
    AdView1.Visible=True
    ...
    ...
End Sub
 

boten

Active Member
Licensed User
Longtime User
2 activities in the app.
Both activities need to store/restore maps of types using KeyValueStore
activity 1 handles a "large" (relatively speaking) amount of data
activity 2 handles a "medium" (again, relatively speaking) amount of data

What would be more "efficient" (execution-time)?
Using "large" AND "medium" in the same data store
or
Using a different store for each activity? (thus isolating each activity's data from the other activity)
 

luke2012

Well-Known Member
Licensed User
Longtime User
I trying this code :

dim KVMgr As KeyValueStore
KVMgr.Initialize(File.DirDefaultExternal, "datastore")

KVMgr.PutBitmap("test1",File.DirRootExternal,"foto.jpg")
KVMgr.GetBitmap("test1")


I got this error:

Dim buffer() As Byte = c.GetBlob2(0)
java.lang.IllegalStateException: Couldn't read row 0, col 0 from CursorWindow. Make sure the Cursor is initialclasskeyvaluestore_getbitmap (B4A line: 132)
ized correctly before accessing data from it.
at android.database.CursorWindow.nativeGetBlob(Native Method)
at android.database.CursorWindow.getBlob(CursorWindow.java:399)
at android.database.AbstractWindowedCursor.getBlob(AbstractWindowedCursor.java:45)
at anywheresoftware.b4a.sql.SQL$CursorWrapper.GetBlob2(SQL.java:409)
at luke2012.android.lclibrary.classkeyvaluestore._getbitmap(classkeyvaluestore.java:177)
at luke2012.android.lclibrary.classsqlitemanager._loadbitmapfromdb(classsqlitemanager.java:1147)
at luke2012.android.lclibrary.main._activity_create(main.java:254)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:525)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:169)
at luke2012.android.lclibrary.main.afterFirstLayout(main.java:89)
at luke2012.android.lclibrary.main.access$100(main.java:16)
at luke2012.android.lclibrary.main$WaitForLayout.run(main.java:74)
at android.os.Handler.handleCallback(Handler.java:730)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:5103)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:525)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:737)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
at dalvik.system.NativeStart.main(Native Method)
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
 

luke2012

Well-Known Member
Licensed User
Longtime User
This is not the code you are running. kvs.PutBitmap expects two parameters. The key and the bitmap.

Hi Erel,
I attached the code that make the following error :

Dim buffer() As Byte = c.GetBlob2(0)
java.lang.IllegalStateException: Couldn't read row 0, col 0 from CursorWindow. Make sure the Cursor is initialclasskeyvaluestore_getbitmap (B4A line: 132)
 

Attachments

  • kvs_test.zip
    5.6 KB · Views: 444

luke2012

Well-Known Member
Licensed User
Longtime User
Please upload the project (File - Export as zip). Or post the relevant code directly.

The maximum for the upload is 500kb but the project is 4mb.
I send you the project via eEmail.
 

luke2012

Well-Known Member
Licensed User
Longtime User
@Erel,
you can find the relevant code within the attached file.
 

Attachments

  • LCLibrary.zip
    10.6 KB · Views: 484
Last edited:
Status
Not open for further replies.
Top