B4A Class KeyValues - Extended KeyValueStore

derez

Expert
Licensed User
Working on an application with Erel's KeyValueStore, I had the need to store more than one value per key.
Of course, it is always possible to combine several values to one (create a type or combine to comma separated string) but then you get all of them, have to break the package and use the value you need.
The attached class is an extension of the KeyValueStore, enabling storing of many values of different types per one key, storing and retrieving each value alone or as an array of values.
for almost each method in KVS there are two methods here:
- for handling a single value - use the method ending with 1
- for handling an array of values - use the method ending with 2
The index added to the methods type 1 is the number of the value in the key-values line, starting with 0.
Also added GetKeyBySimpleValue which is missing in regular maps.
The demo holds, in addition to new demo methods, a commented part of the original demo of KVS (with addition of index).

Edit: ver 1.1 , improved GetKeyBySimpleValue.

Edit:
Sometimes you'll want to store values that are related to two keys, like for example the statistics of a basketball player - you'll have name and statistics type as the keys.
Or, several velues per two keys - if you want to store several teams data- you'll have team and player as keys, array of values for all types of statistics.
So I added two more store classes- both with two keys, one with single value, the other with single or array of values.
The structure of these classes is the same as the previously published so there is no demo.
 

Attachments

Last edited:

derez

Expert
Licensed User
Sometimes you'll want to store values that are related to two keys, see the first post for additional classes.
 

schimanski

Well-Known Member
Licensed User
First, thanks for this amazing lib.....

I only have one problem:

in line

B4X:
 If c.RowCount = 0 Then Return ""
of the sub 'Public Sub GetSimple1(Key As String, index As Int) As String' in the keyvalue-class, i get the following exception, when I start my app after a few minutes again:

B4X:
android.database.CursorWindowAllocationException: Cursor window allocation of 2048 kb failed. # Open Cursors=601 (# cursors opened by this proc=601)
   at android.database.CursorWindow.<init>(CursorWindow.java:108)
   at android.database.AbstractWindowedCursor.clearOrCreateWindow(AbstractWindowedCursor.java:198)
   at android.database.sqlite.SQLiteCursor.clearOrCreateWindow(SQLiteCursor.java:301)
   at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:139)
   at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:133)
   at anywheresoftware.b4a.sql.SQL$CursorWrapper.getRowCount(SQL.java:313)
   at de.sekutor.eis.keyvalues._getsimple1(keyvalues.java:839)
   at de.sekutor.eis.ladeservice._uploadtimer_tick(ladeservice.java:2757)
   at java.lang.reflect.Method.invoke(Native Method)
   at java.lang.reflect.Method.invoke(Method.java:372)
   at anywheresoftware.b4a.BA.raiseEvent2(BA.java:187)
   at anywheresoftware.b4a.objects.Timer$TickTack.run(Timer.java:105)
   at android.os.Handler.handleCallback(Handler.java:739)
   at android.os.Handler.dispatchMessage(Handler.java:95)
   at android.os.Looper.loop(Looper.java:145)
   at android.app.ActivityThread.main(ActivityThread.java:5834)
   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:1388)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1183)
I always check, if the keyvalue is initialized, before I initialize it.
 

derez

Expert
Licensed User
I don't know, I read
window allocation of 2048 kb failed.
Maybe it is a problem of memory, what size of object is expected as a returned value ? Try to close the aplication before re-activating (using the menu).
If it is not solved - please show the calling sub.
 

schimanski

Well-Known Member
Licensed User
Thanks, derez for your response. I have tested more and I think, the problem is my code. I have a service-modul with a timer. The exception is always raised, after the timer is raised 19 times, even the timer is 2000 oder 20000 ms.


B4X:
'Service-Modul'
Sub Process_Globals
     Private UploadTimer As Timer 
     Public Gesendet As KeyValues 
     ...
end sub
Sub UploadTimer_Tick   
        If Gesendet.IsInitialized=False Then Gesendet.Initialize(File.DirInternal, "Gesendet.eis", 2)
       
        If Gesendet.ListKeys.Size>0 Then
            For i=0 To Gesendet.ListKeys.Size-1
                If Gesendet.GetSimple1(Gesendet.ListKeys.Get(i),0)<>Null AND Gesendet.GetSimple1(Gesendet.ListKeys.Get(i),0)="False" Then
                       ......
                       ......
                End If
            Next
        End If
End Sub
 

derez

Expert
Licensed User
I don't see here the problem.
The fact that it happens after 19 times calls for memory problem.

A comment:
B4X:
If Gesendet.GetSimple1(Gesendet.ListKeys.Get(i),0)<>Null AND Gesendet.GetSimple1(Gesendet.ListKeys.Get(i),0)="False" Then
The first part is covered by the second, so it is redundant:
B4X:
If Gesendet.GetSimple1(Gesendet.ListKeys.Get(i),0)="False" Then
 

schimanski

Well-Known Member
Licensed User
Ok...thanks. I will try something more...
But when it is a memory-problem, it would be raised by the lines above. Before this code, i had two maps to hold the same keys and values in the identical subs and that was no problem.
 
Last edited:

derez

Expert
Licensed User
The code above shows use of just index 0 so there is no need of this class, if you do more with it and want help you must show it.
I believe you are loading data, not only checking its existence.
 

schimanski

Well-Known Member
Licensed User
Thank for help!

There is no need to show more code, derez. The following sample-code is enough to raise the exception after about 10 seconds:

B4X:
Sub Process_Globals
    Private UploadTimer As Timer
    Dim Gesendet As KeyValues
End Sub

Sub Globals
   
End Sub

Sub Activity_Create(FirstTime As Boolean)
    If Gesendet.IsInitialized=False Then Gesendet.Initialize(File.DirInternal, "Gesendet.eis", 2)   
    UploadTimer.Initialize("UploadTimer", 100)
End Sub

Sub Activity_Resume
    Gesendet.PutSimple1("A1", 0, "True")
    Gesendet.PutSimple1("A2", 0, "False")
    Gesendet.PutSimple1("A1", 1, "True")
    Gesendet.PutSimple1("A2", 1, "False")

    UploadTimer.Enabled=True
End Sub

Sub UploadTimer_Tick

    Log ("Timer raised!")
   
    If Gesendet.IsInitialized=False Then Gesendet.Initialize(File.DirInternal, "Gesendet.eis", 2)
    Log(Gesendet.ListKeys.Size)
    If Gesendet.ListKeys.Size>0 Then
        For i=0 To Gesendet.ListKeys.Size-1
            If Gesendet.GetSimple1(Gesendet.ListKeys.Get(i),0)="False" Then
    
            End If
        Next
    End If
End Sub
I have mentioned, that the exception comes, after the timer is raised 86 times with the code
B4X:
If Gesendet.GetSimple1(Gesendet.ListKeys.Get(i),0)<>Null AND Gesendet.GetSimple1(Gesendet.ListKeys.Get(i),0)="False" Then
and after 126 times with the line
B4X:
if Gesendet.GetSimple1(Gesendet.ListKeys.Get(i),0)="False" Then

B4X:
android.database.CursorWindowAllocationException: Cursor window allocation of 2048 kb failed. # Open Cursors=609 (# cursors opened by this proc=609)
    at android.database.CursorWindow.<init>(CursorWindow.java:108)
    at android.database.AbstractWindowedCursor.clearOrCreateWindow(AbstractWindowedCursor.java:198)
    at android.database.sqlite.SQLiteCursor.clearOrCreateWindow(SQLiteCursor.java:301)
    at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:139)
    at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:133)
    at anywheresoftware.b4a.sql.SQL$CursorWrapper.getRowCount(SQL.java:313)
    at b4a.example.keyvalues._vvvv1(keyvalues.java:464)
    at b4a.example.main._uploadtimer_tick(main.java:386)
    at java.lang.reflect.Method.invoke(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:372)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:187)
    at anywheresoftware.b4a.objects.Timer$TickTack.run(Timer.java:105)
    at android.os.Handler.handleCallback(Handler.java:739)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:145)
    at android.app.ActivityThread.main(ActivityThread.java:5834)
    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:1388)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1183)
android.database.CursorWindowAllocationException: Cursor window allocation of 2048 kb failed. # Open Cursors=609 (# cursors opened by this proc=609)
I'm using a samsung galaxy S5....
 

schimanski

Well-Known Member
Licensed User
O.k, I have tesed the same code with Erel's keyvaluestore-class. It's the same problem:(
 
Last edited:

derez

Expert
Licensed User
I've made some changes in your sub and with this on nexus 5 emulator it runs up to 987 times while with your code it went up to 329.
B4X:
Sub UploadTimer_Tick
    count = count + 1
    Log ("Timer raised! " & count)
  
    If Gesendet.IsInitialized = False Then
        Gesendet.Initialize(File.DirInternal, "Gesendet.eis", 2)
    End If

    For Each k As String In Gesendet.ListKeys
        If Gesendet.GetSimple1(k,0)="False" Then

        End If
    Next

End Sub
It is a problem of memory-cpu relations in the device, like the memory is saying to itself:
Enough of this application, checking all the time on me to see how many keys I have and then let us check them one by one !
And the worst is that it doesn't even do anything with it, just let me roll and roll,
like I've said before - enough of that, I quit...
 

schimanski

Well-Known Member
Licensed User
Thanks for that! Now i understand the problem. With your changes, it is much better, but it seems, that a keyvaluestore is not the best solution for a 'timer-check'. Here it is better to use a map....
 

Peter Simpson

Expert
Licensed User
Hello,
I don't actually need this solution, but I spotted it and just thought that I would give it a go to see what it's like. Anyway I ran your code(KeyValues_1.1.zip) without making any changes to it and BOOOOOOM.

B4X:
Error saving object: (SQLiteException) android.database.sqlite.SQLiteException: table main has 2 columns but 3 values were supplied (code 1): , while compiling: INSERT INTO main VALUES(?,?,?)
Here is the whole log in full:
B4X:
** Activity (main) Create, isFirst = true **
** Activity (main) Resume **
Error saving object: (SQLiteException) android.database.sqlite.SQLiteException: table main has 2 columns but 3 values were supplied (code 1): , while compiling: INSERT INTO main VALUES(?,?,?)
Error occurred on line: 44 (main)
java.lang.NullPointerException
    at b4a.example.main._activity_resume(main.java:414)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:636)
    at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:305)
    at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:238)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at anywheresoftware.b4a.ShellBA.raiseEvent2(ShellBA.java:121)
    at anywheresoftware.b4a.BA.raiseEvent(BA.java:171)
    at b4a.example.main.afterFirstLayout(main.java:106)
    at b4a.example.main.access$100(main.java:17)
    at b4a.example.main$WaitForLayout.run(main.java:78)
    at android.os.Handler.handleCallback(Handler.java:733)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:157)
    at android.app.ActivityThread.main(ActivityThread.java:5356)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1265)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1081)
    at dalvik.system.NativeStart.main(Native Method)
 

derez

Expert
Licensed User
I downloaded it and it runs fine. I read in your post:
Error occurred on line: 44 (main)
in the program line 44 is
B4X:
Log(res(0) & DateTime.Time(res(1)))
This line has nothing to do with inserting data, so there must be something you have changed that causes it to fail.
will you be so kind as to give some more details ?
 

derez

Expert
Licensed User
Rolling it in my head while walking I thought I found it but no. May be ver 4.3 added something so the row in your file is
B4X:
kvs.PutSimple2("Time",val)
I have followed it with no DB existence and it works ok. The three values are
key - "Time"
val(0) - "The Time now: "
val(1) - DateTime.Now
 
Top