Android Tutorial StateManager - Helps managing Android applications settings and state

Status
Not open for further replies.
Edit: StateManager was written in 2011. I don't recommend using it for new projects. Use B4XPages + KVS2 instead.

StateManager is a code module which takes care of handling the application UI state and settings.

Settings
Settings are the application configurable settings. These settings should be permanently kept.

The methods for handling settings are pretty simple:
StateManager.GetSetting (Key As String) As String: gets the value associated with the given key. An empty string will return if the key is not available. The settings will be loaded from a file if they were not loaded before.

StateManager.GetSetting2 (Key As String, DefaultValue As String) As String: similar to GetSetting. The DefaultValue will return if the key was not found.

StateManager.SetSetting(Key As String, Value As String): Associates the given value with the gives key. Note that there is no need to call SaveSettings after each call to SetSetting.

StateManager.SaveSettings: saves the settings to a file. Usually you will want to call this method in Activity_Pause.

UI State
The UI state is a bit more interesting. In some cases Android may destroy our activity and then recreate it when needed. This happens for example when the user changes the screen orientation. If the user has entered some text in an EditText view then we want to keep this text. So instead of resetting the UI we are first saving the state and then we will restore it.

Not all the elements are saved. Only elements which the user interacts with (like EditText text, Spinner chosen item, SeekBar value...).
Using StateManager to handle the state is simple:
B4X:
Sub Activity_Create(FirstTime As Boolean)

    ...
    'Load the previous state
    If StateManager.RestoreState(Activity, "Main", 60) = False Then
        'set the default values
        EditText1.Text = "Default text"
        EditText2.Text = "Default text"
    End If
End Sub

Sub Activity_Pause (UserClosed As Boolean)
    If UserClosed Then
        StateManager.ResetState("Main")
    Else
        StateManager.SaveState(Activity, "Main")
    End If
    StateManager.SaveSettings
End Sub
When the activity is paused we check if the user chose to close the activity (by pressing on the Back key). In that case we reset the state. The string parameter is the ActivityName value. StateManager can manage the state of multiple activities so the name is used to differentiate between the activities.
If UserClosed=False then we want to save the state.
The settings are saved in both cases.

When the activity is created we call: StateManager.RestoreState. The last parameter is the validity period for this state. The state will not be loaded if more than the specified minutes have passed. Pass 0 for an unlimited period.
RestoreState returns a boolean value. It returns true if the state was loaded. If the state wasn't loaded it is your responsibility to set the default value. This will be the case when the user runs the application for the first time.

To use StateManager you should choose Project - Add Existing Module and add StateManager.bas which is included in the attached example. You should also add a reference to the RandomAccessFile library and Reflection library.

statemanager_1.png


Version 1.11 is attached. It adds support for saving and restoring TabHost views with their internal views.
 

Attachments

  • StateManager.zip
    11.7 KB · Views: 1,849
Last edited:

Felix Maria

Member
Licensed User
Longtime User
Yes. I have a Main Activity. Under the Activity, I have 4 layout (.bal) which i load during the runtime with activity.loadlayout
When I navigate from say first.bal to sec.bal and then come back to first.bal....The status of the first.bal is initialised.
how do we preserve the state of the layout first.bal like retaining the textbox entries and the spinner list etc....
Thank U!
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
A layout file is nothing more than a table of views that will be added to a panel or activity.

StateManager can only save and restore the state if the views were added in the exact same order. So if your program loads everything in Activity_Create and then only changes the visible panel then it will work. If you are loading and removing views at runtime it will not work.
 

Felix Maria

Member
Licensed User
Longtime User
Thank U! Have never experienced a Support quicker than this.
I have changed all the layouts to panels. Now it seems to be fine with the visible property.
 

Mark Zraik

Member
Licensed User
Longtime User
StateManager is a code module which takes care of handling the application UI state and settings.
Just wanted to say thank you for the hard work on this. Playing with it now.


Respectfully,
Coroner
 

Mark Zraik

Member
Licensed User
Longtime User
Hello All,

I have been fartin' around with creating/re-creating a bunch of apps in B4A. I started using the StateManager and it really does the job if you think it through.
The 1 thing I couldn't determine for sure is whether I wanted files kept on the DirInternal or DirExternal or even DirRootExternal path.
So, I abused the StateManager code module, added a sub to handle this, and thought I would throw it up here for all to continue to abuse. :)

The way it is setup is: It defaults to the DirExternal, but if that's not available, then DirRootExternal(Psuedo External, I think), and then DirInternal.
By simply re-ordering the if statements, you can easily change the order of preference.
You could also change the DirXXX to anything else you wanted.
For my apps and the way I code them, I prefer to use the DirExternal for state and settings, but if that's not available, then it falls to the other possible
and existing directory choices.
I have DirRootExternal commented out, but it does work, I just didn't need it for my coding.

If there is a better way to do what I have done, by all means share!
NOTE:I changed the name of the StateManager code module to IXStorage, so it could be used side by side.
Then, I did the following as posted in the UPDATE below.

UPDATE: 09/07/2014
After considerable research, reading post from here and all over the net, it appears that
the only reason to use Dir...External would be to allow access to your file/s from outside your program by putting them in the public or sharable directory of your app (determined by the OS, ofcourse), which on occasion you may want to do. DirDefaultExternal may/usually/actually points to a partition on the Internal Storage card that the OS considers Public and sharable. Android does post on the developer site, that starting with level 18 / Android 4.4, that it will no longer be necessary to add a manifest permission to write to the External Storage (for backward compatibility one should add this version of the permission: AddPermission(android.permission.WRITE_EXTERNAL_STORAGE android:maxSdkVersion="18"), but they didn't state (that I could find), whether or not a developer had physical access to the physical storage sdcard. So, even choosing File.DirRootExternal may not get you to the physical sdcard you spent extra money on due to symbolic links the OS may be using. I made a few adjustments to my original code mods to Erels StateManager, renamed this new version ShareSettings.

From http://developer.android.com/guide/topics/data/data-storage.html
Annotated-
Every Android-compatible device supports a shared "external storage" that you can use to save files. This can be a removable storage media (such as an SD card) or an internal (non-removable) storage. Files saved to the external storage are world-readable and can be modified by the user when they enable USB mass storage to transfer files on a computer.

Caution: External storage can become unavailable if the user mounts the external storage on a computer or removes the media, and there's no security enforced upon files you save to the external storage. All applications can read and write files placed on the external storage and the user can remove them.

Getting access to external storage

In order to read or write files on the external storage, your app must acquire the READ_EXTERNAL_STORAGE or WRITE_EXTERNAL_STORAGE system permissions. For example:

<manifest ...>
<uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
...</manifest>

If you need to both read and write files, then you need to request only the WRITE_EXTERNAL_STORAGE permission, because it implicitly requires read access as well.

Note: Beginning with Android 4.4, these permissions are not required if you're reading or writing only files that are private to your app. For more information, see the section below about saving files that are app-private.
Saving files that are app-private

If you are handling files that are not intended for other apps to use (such as graphic textures or sound effects used by only your app), you should use a private storage directory on the external storage by calling getExternalFilesDir(). This method also takes a type argument to specify the type of subdirectory (such as DIRECTORY_MOVIES). If you don't need a specific media directory, pass null to receive the root directory of your app's private directory.

Beginning with Android 4.4, reading or writing files in your app's private directories does not require the READ_EXTERNAL_STORAGE or WRITE_EXTERNAL_STORAGE permissions. So you can declare the permission should be requested only on the lower versions of Android by adding the maxSdkVersion attribute:

<manifest ...>
<uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="18"/>
...</manifest>

Note: When the user uninstalls your application, this directory and all its contents are deleted. Also, the system media scanner does not read files in these directories, so they are not accessible from the MediaStore content provider. As such, you should not use these directories for media that ultimately belongs to the user, such as photos captured or edited with your app, or music the user has purchased with your app—those files should be saved in the public directories.

Sometimes, a device that has allocated a partition of the internal memory for use as the external storage may also offer an SD card slot. When such a device is running Android 4.3 and lower, the getExternalFilesDir() method provides access to only the internal partition and your app cannot read or write to the SD card. Beginning with Android 4.4, however, you can access both locations by calling getExternalFilesDirs(), which returns a File array with entries each location. The first entry in the array is considered the primary external storage and you should use that location unless it's full or unavailable. If you'd like to access both possible locations while also supporting Android 4.3 and lower, use the support library's static method, ContextCompat.getExternalFilesDirs(). This also returns a File array, but always includes only one entry on Android 4.3 and lower.

Caution Although the directories provided by getExternalFilesDir() and getExternalFilesDirs() are not accessible by the MediaStore content provider, other apps with the READ_EXTERNAL_STORAGE permission can access all files on the external storage, including these. If you need to completely restrict access for your files, you should instead write your files to the internal storage
.​

In this version I changed the If File.IsDirectory test to If File.ExternalWritable = True, which is a solid test to see if you can write to External Storage in the first place. If it fails, then it defaults to DirInternal.

Next, I'm going to look into splitting the code in such a way to have a private state.dat and a shared settings.properties file. I think it could come in handy and save a few keystrokes if you needed it.

ANOTHER NOTE: The research from the developer site also pointed out that DirDefaultExternal along with DirInternal will be deleted when the app is uninstalled. This has impact if the user has content that belongs to the user and not the app and should remain after uninstall. It appears that DirRootExternal might be the place to put these types of files so they remain after uninstall... I'm looking into that, and maybe someone else already knows and could shed some light on this for the rest of us.

UPDATE2: I did the split -attached as ShareSettingsOnly! Private DirInternal for state.dat, DirDefaultExternal for settings.properties. It will still fall back to internal, if External is not writable!
 

Attachments

  • IXStorage.zip
    9.1 KB · Views: 422
  • ShareSettings.zip
    2.8 KB · Views: 394
  • ShareSettingsOnly.zip
    2.8 KB · Views: 371
Last edited:

kabron

Member
Licensed User
Longtime User
StateManager does not work with runtime created objects

Everything works fine in the StateManager example if the Activity is filled in the Activity_Create Sub.
But if it is altered at runtime, the info about new object is lost somewhere.
I created a Sub to add a button on ToggleButton1 click and it works fine until screen rotate. I also Log (Activity.NumberOfViews)
and it is seen that NumberOfViews was increased after AddButton event and in SaveState Sub, but it decreased in RestoreState.
I modified innerSaveState and innerRestoreState to work with Buttons. It does not help.
Also I noticed that code for Buttons is executed in innerSaveState and innerRestoreState, even if there is no any Buttons.
The code attached.
I have no idea, any help appreciated
 

Attachments

  • StateMan.zip
    11.2 KB · Views: 384

kabron

Member
Licensed User
Longtime User
Thanks for answer, Erel
If I understand correctly, StateManager is not usefull with objects, created during runtime. Are there any other solutions?
 

kabron

Member
Licensed User
Longtime User
Unfortunately my objects are creating and destroying randomly at runtime.
But, how it works, say in games? It is quite the same case.
 

kabron

Member
Licensed User
Longtime User
My requirements are very simple: a few buttons, labels with text created and modifying during runtime and some drawings on canvas.
 

Mark Zraik

Member
Licensed User
Longtime User
My requirements are very simple: a few buttons, labels with text created and modifying during runtime and some drawings on canvas.
Hi kabron,
I had some thoughts, but after a time(and not being a game coder, I decided not to join in just yet.
I don't mean to take up your time and anyone elses, but I couldn't find the cancel button.

Good luck in your game coding!
 

vinnythepooh71

Member
Licensed User
Longtime User
I am using the statemanager and i get these warnings :

return type ( in sub signature ) should be set exsplicitly

'Gets the setting value associated with the given key.
'Returns an empty string if the key was not found.
Sub GetSetting(Key As String)
Return GetSetting2(Key, "") <--------------------------this line
End Sub

return type ( in sub signature ) should be set exsplicitly

'Gets the setting value associated with the given key.
'Returns the DefaultValue parameter if the key was not found.
Sub GetSetting2(Key As String, DefaultValue As String)
If settings.IsInitialized = False Then
'load the stored settings
If File.Exists(File.DirInternal, settingsFileName) Then
settings = File.ReadMap(File.DirInternal, settingsFileName)
Else
Return DefaultValue <-----------------------this line
End If
End If
Dim v As String
v = settings.GetDefault(Key.ToLowerCase, DefaultValue)
Return v <--------------------------- and this line
End Sub

return value si missing ( using default )

'Loads the stored state (if such is available)
'ActivityName - Should match the value use in SaveState
'ValidPeriodInMinutes - The validity period of this state measured in minutes. Pass 0 for an unlimited period.
'Returns true if the state was loaded
Sub RestoreState(Activity As Activity, ActivityName As String, ValidPeriodInMinutes As Int) As Boolean
Try
loadStateFile
If states.IsInitialized = False Then
Return False
End If
Dim list1 As List
list1 = states.Get(ActivityName.ToLowerCase)
If list1.IsInitialized = False Then Return <------------------------------ this line
Dim time As Long
time = list1.Get(0)
If ValidPeriodInMinutes > 0 AND time + ValidPeriodInMinutes * DateTime.TicksPerMinute < DateTime.Now Then
Return False
End If
listPosition = 0
For i = 0 To Activity.NumberOfViews - 1
innerRestoreState(Activity.GetView(i), list1)
Next
Return True
Catch
Log("Error loading state.")
Log(LastException.Message)
Return False
End Try
End Sub


Sorry for the verbosity, but should i worry ???
 

Fusseldieb

Active Member
Licensed User
Longtime User
Which command I need to use to save the entire screen (all visible items, such as listviews, scrollview, labels, etc...)?
 

chrjak

Active Member
Licensed User
Longtime User
Hey Fusseldieb

2 possibilitys:
B4X:
StateManager.SaveState(Activity, "Main")
StateManager.SaveSettings
This saves whole Activity

but if that doesn't work you can try it in a loop! e.g:
B4X:
For i = 0 to Activity.NumberOfViews - 1
'Test and backup here every view on activity
next
 
Status
Not open for further replies.
Top