B4J Library [B4X] KeyValueStore 2 - Simple & Powerful Local Datastore

Discussion in 'B4J Libraries & Classes' started by Erel, Feb 17, 2016.

  1. Erel

    Erel Administrator Staff Member Licensed User

    KeyValueStore is a persistent key/value based data store. It is similar to the useful Map collection, however unlike Map which stores the data in memory, KVS stores the data in a database. This means that you don't need to worry about losing data or saving the state when the program ends.

    This version replaces the older version. It is not backwards compatible. You cannot use it with databases created with the previous version.

    The main differences between v2 and v1:
    The new version is based on B4XSerializator to serialize the values and on B4XCipher to encrypt it.
    This means that the data can be shared between B4A, B4J and B4i. For example you can create the data store in B4J and embed it in your mobile app.
    B4XSerializator is also faster and simpler to use.

    Using KVS is similar to using a Map. You initialize it once (use the Starter service in B4A) and then you can put or get items with Put, Get or GetDefault methods.
    You can use PutEncrypted to encrypt the value before it is stored. Use GetEncrypted to get an encrypted value.
    If you want to put bitmaps then use PutBitmap and GetBitmap.

    The supported types of objects are:

    Lists, Maps, Strings, primitives (numbers), user defined types and arrays (only arrays of bytes and arrays of objects are supported).
    Custom types should be declared in the main module.
    Including combinations of these types (a list that holds maps for example).

    KeyValueStore depends on the following libraries: SQL, RandomAccessFile and B4XEncryption (iEncryption on B4i).
    Note that in B4J you need to download the bouncy castle jar and add the following two lines to the main module:
    Code:
    #AdditionalJar: sqlite-jdbc-3.7.2
    #AdditionalJar: bcprov-jdk15on-154
    The required SQL version for B4A is v1.30+: https://www.b4x.com/android/forum/threads/updates-to-internal-libraries.59340/#post-401376 (make sure to copy it to the internal libraries folder).

    A B4A example is attached. The class module is compatible with B4A, B4J and B4i.

    Tip: Check CloudKVS for an auto-synchronizing solution: https://www.b4x.com/android/forum/threads/b4x-cloudkvs-synchronized-key-value-store.63536/#content

    Updates:

    - V2.20 - Adds support for GetMapAsync and PutMapAsync. These methods allow to asynchronously insert or retrieve multiple items.
    Examples:
    Code:
    'getting all items:
    Wait For (Starter.kvs.GetMapAsync(Starter.kvs.ListKeys)) Complete (Result As Map)
    Log(Result)
    'getting specific items:
    Wait For (Starter.kvs.GetMapAsync(Array("Key1""Key2""Key3")) Complete (Result As Map)
    Log(Result)
    Note that starting from B4A v8.0 KeyValueStore2 is included as an internal library.
     

    Attached Files:

    Last edited: Mar 19, 2018
  2. stevel05

    stevel05 Expert Licensed User

    Brilliant, thanks Erel. I've just been looking at this. It makes it simple to store all manner of appdata without having to create multiple map saves to the appdata folder.

    Also, if you don't want to use encryption, comment out the two Encryption subs (or better still use conditional compilation) and there is no need to load the additional bouncy castle jar.

    I particularly like the basic get and put methods, which are the same as using a standard map.
     
    Phayao, Erel and lemonisdead like this.
  3. Douglas Farias

    Douglas Farias Expert Licensed User

  4. postasat

    postasat Member Licensed User

    Hi,

    is it mandatory to use the Starter service to initialize kvs ?
    I need to record a long series of list (with different key), how can I do with the new code ?

    Thank you.
     
  5. Mahares

    Mahares Well Known Member Licensed User

    Until @Erel gets back with you Sunday, i am going to take a stab at this and your next question:
    I do not think it is essential that you use the starter service. You can declare and initialize the kvs in any module.

    I hope I understood your question, but here is my interpretation code wise:
    Code:
    Sub Activity_Create(FirstTime As Boolean)
       
        Starter.kvs.DeleteAll   
    'start with a new datastore.
       
        
    Dim list2 As List
        list2.Initialize
        list2.AddAll(
    Array As String("USA","CHINA","ANGOLA","FINLAND"))
        Starter.kvs.Put(
    "lstCountry", list2)
       
        list2.Clear
        list2.AddAll(
    Array As String("AMERICA","ASIA","AFRICA","EUROPE"))
        Starter.kvs.Put(
    "lstContinent", list2)
       
        list2.Clear
        list2.AddAll(
    Array As String("WASHINGTON","BEIJING","LUANDA","HELSINKI"))
        Starter.kvs.Put(
    "lstCapital", list2)


        
    Dim MyList As List =  Starter.kvs.Get("lstCapital")
        
    For i=0 To MyList.Size -1
            
    Log(MyList.Get(i))  'displays all capitals in a list
        Next

    End Sub
     
    Phayao, pesquera, Erel and 1 other person like this.
  6. postasat

    postasat Member Licensed User

    Hi,
    thank you for your reply.

    I'm using a timer to store bluetooth data, every second, in a kvs file.
    The data are in a list.
    The code I use to store the data every second is:
    Code:
    Sub memorizza_dati
    'Record data
       
        
    If recordsequence > 0 Then
            list1.Add(
    "Item #" & recordsequence) '0
            list1.Add(DateTime.time(DateTime.Now)) '1 orario (oo:mm:ss)
            list1.Add(brm(1))         
            list1.Add(
    "0"' spare
            list1.Add("0"' spare
            list1.Add("0"' spare
           
            kvs1.PutObject(
    "Item #" & recordsequence, list1)
            list1.Clear
        
    Else
            kvs1.Initialize(
    File.DirRootExternal & "/Download/Hearts/Lezioni/" & Main.nome_classe,Main.nome_nuova_lezione & ".les")
            list1.Initialize
        
    End If   
           
        recordsequence = recordsequence + 
    1

    End Sub
    To convert code from kvs v1 to kvs v2 I must only change .getobject and .putobject to .get e .put ?
     
  7. Erel

    Erel Administrator Staff Member Licensed User

    Yes.

    It is a good practice to declare and initialize all global non-ui objects in the starter service. You can read more about it here: Starter Service - Consistent & Single Entry Point
     
    postasat likes this.
  8. asales

    asales Well-Known Member Licensed User

    I can change the name of this class to KeyValueStore2.bas and uses together with KeyValueStore version 1?
    Like this?:
    Code:
    Public kvs As KeyValueStore     'version 1

    Public kvs2 As KeyValueStore2     'version 2
     
  9. DonManfred

    DonManfred Expert Licensed User

    No. KVS2 and KVS1 are NOT compatible.
     
    mangojack and lemonisdead like this.
  10. asales

    asales Well-Known Member Licensed User

    Yes, but I want to use the 2 versions with differents files.
    The old file with KVS1 (to mantain compatibility in my app) and the new file to KVS2, with the new options that I will put in my app.
    It's only rename this class to KeyValueStore2.bas to make this?
     
  11. DonManfred

    DonManfred Expert Licensed User

    if you use the old file with kvs1 and only the new files with kvs2 it should work (renaming the new class)

    Just give it a shoot and you´ll know
     
  12. postasat

    postasat Member Licensed User

    Hi,

    is it possible to convert old kvs created datastore files to new kvs2 ?
     
  13. Mahares

    Mahares Well Known Member Licensed User

    Here is how I did it to convert a version1 datastore ta a version2 datastore:
    Create a project with both classes. Then I used below code:

    Code:
    Sub Process_Globals
        
    Private kvs As KeyValueStore   'version 1
        Private kvs2 As KeyValueStoreTwo  'version 2
    End Sub
    Sub Activity_Create(FirstTime As Boolean)
        
    'Store items in datastore1
        If FirstTime Then
            kvs.Initialize(
    File.DirRootExternal, "datastore1")
        
    End If
        kvs.PutSimple(
    "time"DateTime.time(DateTime.Now) )
        kvs.PutBitmap(
    "bitmap1"LoadBitmap(File.DirAssets, "smiley.gif"))  'or whatever file in assetts
        Dim list1 As List
        list1.Initialize
        
    For i = 1 To 10
            list1.Add(
    "Item #" & i)
        
    Next
        kvs.PutObject(
    "list1", list1)
     
        
    'Store items in datastore2
        If FirstTime Then
            kvs2.Initialize(
    File.DirRootExternal, "datastore2")
        
    End If
        kvs2.Put(
    "time",  kvs.GetSimple("time"))
        kvs2.PutBitmap(
    "bitmap1", kvs.GetBitmap("bitmap1"))
        kvs2.Put(
    "list1",kvs.GetObject("list1"))

        
    'Delete datastore1
        kvs.Close
        
    File.Delete(File.DirRootExternal,"datastore1")
     
        
    'Retrieve items stored in datastore2
        Log(kvs2.Get("time"))
        
    Activity.SetBackgroundImage(kvs2.GetBitmap("bitmap1"))
        
    Dim MyList As List =  kvs2.Get("list1")
        
    For i=0 To MyList.Size -1
            
    Log("Items in list: " & MyList.Get(i))  'displays all items in the list
        Next
         
        
    For i=0 To kvs2.ListKeys.Size-1
            
    Log("keys are: " & kvs2.ListKeys.Get(i) )
        
    Next 
    End Sub
    If everyone thinks it is the proper way to convert a version1 to version2 store, I can create a code snippet for this. Awaiting feedback.
     
  14. Erel

    Erel Administrator Staff Member Licensed User

    Looks good.
     
    lemonisdead likes this.
  15. welu1805

    welu1805 Active Member Licensed User

    Hi Erel,

    I wanted to use the new class in my first B4J project with encrypted values, but I get an error:

    Code:
    Sub GetSettings
      transferDir = kvs.GetEncrypted(
    "K1", KeyPW)
    End Sub

    Sub PutSettings
      kvs.PutEncrypted(
    "K1", KeyPW, transferDir)
    End Sub
    Program started.
    Error occurred on line: 56 (KeyValueStore)
    org.bouncycastle.crypto.InvalidCipherTextException: pad block corrupted
    at org.bouncycastle.crypto.paddings.PKCS7Padding.padCount(Unknown Source)
    at org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher.doFinal(Unknown Source)
    at anywheresoftware.b4x.object.B4XEncryption.Decrypt(B4XEncryption.java:73)
    at de.wedersoft.MusicBookTransfer.keyvaluestore._getencrypted(keyvaluestore.java:80)
    at de.wedersoft.MusicBookTransfer.main._getsettings(main.java:130)
    at de.wedersoft.MusicBookTransfer.main._appstart(main.java:102)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:607)
    at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:228)
    at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:158)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:93)
    at anywheresoftware.b4a.ShellBA.raiseEvent2(ShellBA.java:90)
    at anywheresoftware.b4a.BA.raiseEvent(BA.java:84)
    at de.wedersoft.MusicBookTransfer.main.start(main.java:36)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$163(LauncherImpl.java:863)
    at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$176(PlatformImpl.java:326)
    at com.sun.javafx.application.PlatformImpl.lambda$null$174(PlatformImpl.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$175(PlatformImpl.java:294)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$149(WinApplication.java:191)
    at java.lang.Thread.run(Thread.java:745)

    Can you help?
    Lutz
     
  16. Anser

    Anser Well-Known Member Licensed User

    I believe that the password should be the past parameter ?:confused:
    Code:
    Sub PutSettings
      kvs.PutEncrypted(
    "K1",  transferDir, KeyPW)
    End Sub
     
    lemonisdead likes this.
  17. welu1805

    welu1805 Active Member Licensed User

    You are right! Thanks!
     
  18. LucaMs

    LucaMs Expert Licensed User

    I tried to save a Map and I got this error message:

    java.lang.RuntimeException: Cannot serialize object: android.graphics.Bitmap@21e63b48

    The Map contains few string items and one bitmap, for example (not real code):

    M as Map
    MyBmp as Bitmap

    M.Put("Name", "sdfkj")
    M.Put("xxx", "zzz")
    M.Put("bmp", MyBitmap)


    kvs.Put("data", M)
     
  19. Douglas Farias

    Douglas Farias Expert Licensed User

    put i think its only for objetcs
    need use kvs.PutBitmap
     
    LucaMs likes this.
  20. LucaMs

    LucaMs Expert Licensed User

    There are probably limits to the content of Maps
     
Loading...
  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice