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

boten

Active Member
Licensed User
Longtime User
I want to store images in KVS.
Images are created in an activity (using RSCROP), saved as temp JPG file, read to bitmap, temp file deleted, and bitmap is saved in KVS using
kvs.PutBitmap(...)

Now, while the JPG are failrly small (500x500 image takes about 30K), the bitmaps are stored "expanded" in the KVS, each new entry enlarges the KVS by over 200KB.
Is there a way to stored a "compressed" bitmap in the KVS?
 

socialnetis

Active Member
Licensed User
Longtime User
But is it ok to use it in a listview (ULV), or is better to put and a get a Map object (which is going to be in memory) of the KeyValueStore?
 

socialnetis

Active Member
Licensed User
Longtime User
Its a dynamic list downloaded from internet. It will start with 50 items, and if the user hits the bottom, it will load 25 more.
I just want to put and retrieve some booleans (for a "like" system), and because I have a listview wich recycles the items,
I need the recycle to be fast
 

boten

Active Member
Licensed User
Longtime User
kvs.PutBitmap saves the image in PNG format. You have two options. You can save the file directly in the store with kvs.PutInputStream or you can change KVS.PutBitmap to use JPEG instead (see the code it is simple).

but of course :D
 

choliz

Member
Licensed User
Longtime User
Hi,

My app works Ok while in debug mode, but when I release the app, the data is not being stored. I'm deploying to a Nexus 4 running 4.4.2.

B4X:
Sub Activity_Create(FirstTime As Boolean)
    Activity.LoadLayout("ScannerServicio")
    Label1.Text = mResult
    If FirstTime Then
      kvs.Initialize(File.DirDefaultExternal, "pcmall")
      txtClave.text = kvs.GetSimple("clave")
      txtServidor.Text = kvs.GetSimple("servidor")
      txtLogin.Text = kvs.GetSimple("tecnico")
  End If
End Sub

Sub btnGuardarConf_Click
    kvs.PutSimple("clave", txtClave.text)
    kvs.PutSimple("servidor", txtServidor.text)
    kvs.PutSimple("tecnico", txtLogin.Text)
    ToastMessageShow("Datos guardados con éxito", True)
End Sub

Any ideas?
 

choliz

Member
Licensed User
Longtime User
I get no error in release mode (no error shows in screen). Beside that, the app runs fine.
 

choliz

Member
Licensed User
Longtime User
I moved the label assignment outside the FirstTime condition block and worked like a charm. Thank you so much Erel!
 

LucaMs

Expert
Licensed User
Longtime User
Conversion error - 0 to boolean (it is not the "original" message, obv).

I guess from String to Boolean

[EDIT]
Just wait a moment... i'm trying:
the error message is:

java.lang.RuntimeException: Cannot parse: 0 as boolean

but I get it from a routine that I added to the class:

B4X:
Public Sub GetSimple2(Key As String, Default As Object) As String
    Dim c As Cursor = getCursor(Key)
    If c.RowCount = 0 Then
        c.Close
        PutSimple(Key, Default)
        Return Default
    End If
    c.Position = 0
    Dim res As String = c.GetString2(0)
    c.Close
    Return res
End Sub

(I could change the original routine, but I wanted to add it to maintain compatibility).

I completed the test.
I used PutSimple with False as value and it is read as a String in GetSimple2 (but this is almost identical to GetSimple)
 
Last edited:

LucaMs

Expert
Licensed User
Longtime User
You are correct. I see it too. SQLite converts the boolean value to 0. I've updated the documentation. You should use PutObject / GetObject instead.


... then add a GetObject2 routine ;).

Thanks


P.S.
B4X:
Public Sub GetObject2(Key As String, Default As Object) As Object
    Dim Res As Object
    Res = getObjectInternal(Key, False, "")
    If Res = Null Then
        Res = Default
        PutObject(Key, Default)
    End If
    Return Res
End Sub
 
Last edited:

LucaMs

Expert
Licensed User
Longtime User
I am attempting to unify some routines in this class.

I could, for example, test the type of value passed as a parameter instead of choose to use GetObject or GetSimple.

The first thing I did was look at what GetType returns:
B4X:
Public Sub TestType(Value As Object)
    Log(Value & " - " & GetType(Value))
End Sub

B4X:
    Dim TestString As String = "TestString"
Dim TestInt As Int = 1
Dim TestFloat As Float = 1.5
Dim TestArrInt(1) As Int
TestArrInt(0) = 2

kvs.TestType(TestString)
kvs.TestType(TestInt)
kvs.TestType(TestFloat)
kvs.TestType(TestArrInt)

The result of these tests:

TestString - java.lang.String
1 - java.lang.Integer
1.5 - java.lang.Float
[I@417ef0a8 - [I

It would be useful something like: IsArray (and maybe IsMap, etc.).

This is already part of B4A?

At this time I remember that I read something about the square brackets.
[I means array of Int and probably [S array of String,
but I struggled to write the question, then I public anyway the post :p

I'll have to re-read the basic manual :rolleyes:

[UPDATED]
Surely I have read somewhere an explanation about arrays, with square brackets and the distinction between upper and lower case ([I [i [B ...) and did not relate B4A, since it is not in the basic pdf of this language.

Where I've read all this ... I do not remember.

If anyone knows anything ... thanks in advance
 
Last edited:
Status
Not open for further replies.
Top