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:

luke2012

Well-Known Member
Licensed User
Longtime User
It is recommended to move all the "non-ui" objects to Process Globals instead of Globals and only initialize them when FirstTime is True.

I don't see the problem in your code. However try to create a simple program that reproduces the same issue.

Ok I follow your suggestion. On which B4A version and Android version did you try the code?
 

luke2012

Well-Known Member
Licensed User
Longtime User
in the current implementation is possible to retrieve a list with all values stored in the KeyValueStore?
 

boten

Active Member
Licensed User
Longtime User
Strange thing with kvs:
if I Getobject of type list/map and the key is not in the kvs, then an uninitialised object is returned, that's ok. If i use this object later an error should occur.
the thing is that some users report they get "uninitialised" error, many don't. Myself, i can't reproduce this error on any of my 4 devices (galaxy s, galaxy s4, asus tablet and a noname tablet) even though i follow the actions that complaining users do. Any ideas?
 

boten

Active Member
Licensed User
Longtime User
You will get the uninitialized error when every you call a method of an uninitialized object. The solution should be to avoid fetching an invalid key.
I know that, and I know I should check for uninitialized objects. I used unintialised. I was wondering why the error occurs to some users and not to others, even tho the sequence of actions should lead to the same usage.
 

Shadow&Max

Active Member
Licensed User
Longtime User
Sorry to be dense here, but I'm new to B4A, but learning very fast...

I'm trying to get this to run, and it's erroring out on the emulator, even with the simplest of two lines...

The two sections of code:

Sub Process_Globals
'These global variables will be declared once when the application starts.
'These variables can be accessed from all modules.
Dim HoldingString As String
Dim Message1 As String

Private kvs As KeyValueStore

End Sub


Sub Activity_Create(FirstTime AsBoolean)
'Do not forget to load the layout file created with the visual designer. For example:
If FirstTime Then
kvs.Initialize(File.DirDefaultExternal, "agsam")
End If​

Just running this alone generates the Log:

** Activity (main) Create, isFirst = true **
Error occurred on line: 10 (keyvaluestore)
mainafterFirstLayout (java line: 98)
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 java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:525)
at anywheresoftware.b4a.shell.Shell.runVoidMethod(Shell.java:520)
at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:235)
at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:174)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:525)
at anywheresoftware.b4a.ShellBA.raiseEvent2(ShellBA.java:93)
at com.twodogapps.myfirsttest.main.afterFirstLayout(main.java:98)
at com.twodogapps.myfirsttest.main.access$100(main.java:16)
at com.twodogapps.myfirsttest.main$WaitForLayout.run(main.java:76)
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 **

I know this has something to do with opening the file... What am I doing wrong here? I need this and am really stuck! And I've tried other file names including "datastore". Fails every time, and right now don't have an actual device I can test on other than the emulator. Help please! Thanks in advance...
 
Last edited:

Shadow&Max

Active Member
Licensed User
Longtime User
I think I have it now... DirInternal? Seems to work without erroring out... More in a bit...

As Emily Litella said... "Nevermind!"

GOT IT working... am tickled pink!
 
Last edited:

Shadow&Max

Active Member
Licensed User
Longtime User
Thanks Erel...

In Android programming, where's the best place to save what should end up being a moderate sized file? Internal or External, and does External mean the SD card?
 

fredo

Well-Known Member
Licensed User
Longtime User
KVS is great, I use it a lot since a few month now.

But I get an compiling error "unkown member: kvs",
when trying do use "main.kvs.getsimple(...." in a codemodule.

Am I missing something here? It's not a view and has no events....
 

Attachments

  • kvs_codemodule.png
    kvs_codemodule.png
    65.5 KB · Views: 454
Status
Not open for further replies.
Top