Today we’re going to see how to manage (keep and restore) state of Activities in Xamarin.Android application in order to keep the app consistent and reactive for configuration/state changes.
Why to keep and restore Activity’s state?
As I already described in my post about Android Activities, the OS may react to some “constant” state changes by calling lifecycle methods during Activity’s life, which may be overridden by the programmer in order to take some additional actions. However, there are some behaviors in Android apps that may change its configuration, after which the state of the Activity (for instance: arrangement or some UI elements or checkboxes’ selections) may be lost if not handled properly.
To give you a real example: in MoneyBack application there is a main screen with two tabs. I noticed that when the second tab is selected:
and the device is rotated:
first tab becomes selected. User is losing his input (selection of the 2nd tab).
Screen orientation change is one of the reasons why we should care about saving and restoring Activities’ states.
Keeping state changes – OnSaveInstanceState()
Each Activity allows us to override OnSaveInstanceState(Bundle outState) method, which is called when the current Activity is to be killed (e.g. when device’s screen orientation is changing or the Activity needs to be killed by Android in order to release some resources).
Please don’t confuse it with Activity Lifecycle Methods (which I already described here), like OnPause() or OnStop() which are always called by the OS when particular action occurs. OnSaveInstanceState() method is generally called after OnPause() and before OnStop(), but it’s not always the case. For instance, it won’t be called at all when user navigates back from ActivityB to ActivityA, as in this case ActivityB will never be killed. More details can be found in official Android documentation.
OnSaveInstanceState() is called with Bundle parameter provided. It represents a simple key-value dictionary, which is additionally serialized, so it should be used for storing simple values like strings, integers etc. There are other structures and techniques for storing more complex data on state changes events (see the link in “Summary” section of this post).
In order to save currently selected tab, we can implement the method as follows:
|protected override void OnSaveInstanceState(Bundle outState)|
|var tabSelectedPosition = this.ActionBar.SelectedNavigationIndex;|
Now, each screen orientation change will make this method called and the index of selected tab will be saved into Bundle outState dictionary under “selectedTabPosition” key.
Restoring state changes – OnRestoreInstanceState()
As soon as the Activity considered backs to life (is being resumed), OnRestoreInstanceState(Bundle savedInstanceState) method is called by the OS. It will only be called if there is a saved instance kept by calling OnSaveInstanceState() method before. As you can see, this method also comes with a Bundle parameter, which is the same key-value serialized dictionary we used for saving the Activity’s state.
In case of tabs selection restoring, we can implement this method as the following code presents:
|protected override void OnRestoreInstanceState(Bundle savedInstanceState)|
|var previouslySelectedTabPosition = savedInstanceState.GetInt("selectedTabPosition", 0);|
previouslySelectedTabPosition variable will be initialized with integer value from Bundle dictionary saved under “selectedTabPosition” key, or default value (0) if nothing saved for the key is found.
NOTE: Bundle parameter provided in OnRestoreInstanceState() is exactly the same, as the one available in OnCreate(Bundle bundle) method called on Activity’s creation. We could actually also retrieve our saved value there, but in many cases restoring the state (like selecting previously selected tab) requires some UI elements to be already initialized, which should be handled within OnCreate() method before. Because of that, I’d rather suggest to restore Activity’s state using OnRestoreInstanceState() as a general rule (of course there may be some exceptional cases 😉 ). OnRestoreInstanceState() will always be called after OnCreate().
Moreover, if we wanted to use Bundle parameter in OnCreate() method, we’d need to check each time if bundle is not NULL (as we don’t know whether OnSaveInstanceState() had been called before).
As a result, the same tab as previously is selected even after rotating the device on MoneyBack‘s MainActivity:
“Automatic” state changes handled by Android OS
Maybe you noticed in your Android application, that not every UI element needs to be handled “manually” by implementing the above-mentioned methods in order to save its state. Android performs some kind of “automatic” saving and restoration of basic UI elements’ states.
The rule here is that as long as UI element (like TextView or Button) has its android:id set in .axml layout file, OS will automatically manage those elements’ states (e.g. text entered into EditText).
For example, for EditText declared like that:
its properties (especially its Text property) will be automatically saved and restored on screen orientation changes.
We’ve seen how to handle Android Activities’ state changes caused by configuration modification (e.g. screen orientation change, Activity being killed by the OS in order to free some resources etc.) by implementing OnSaveInstanceState() and OnRestoreInstanceState(), where we added and retrieved key-value data to/from Bundle serialized dictionary. We just need to remember that those methods are not always called (e.g. when navigating between Activities using “back” button), so these should be used only for certain kind of state changes.
We’ve also seen that UI controls with their android:id defined in layout files have their states resistant for configuration changes (handled automatically by Android OS).
Bundle dictionary is serialized for better performance and memory utilization, so only simple-typed values should be stored in it (like strings or integers). In order to store more complex data, Android provides different possibilities of managing it (e.g. using OnRetainNonConfigurationInstance).