Stateful enumeration in Android

Whilst clever solutions to complex problems are always interesting and insightful, sometimes the answer is a lot more simple.

When gearing up to release our first app to the Google Play store we engaged in some real world testing. The results were generally positive but it made apparent one large oversight in our development - the Android system destroying our application process in its entirity.

When a user presses the home button, or recieves a phone call whilst utilising an app, in certain circumstances your activity can be destroyed by the system. For example on older devices with smaller amounts of memory.

The application in question is heavily fragment based and utilizes setRetainInstance and a custom backstack implementation to retain fragment state for the most common 'recreation' situations (e.g orientation changes).

The problem is that when an activity is destroyed by the system, its fragment instances are also destroyed. On the other hand, during a normal orientation change, the instances (and as such the properties) are retained. When everything is destroyed we need to restore our data and state appropriately.

Like their activity counterparts, the onCreate, onCreateView, and onActivityCreated lifecycle methods of a fragment take a bundle of saved instance state as one of their parameters. This bundle contains any data that you set in onSaveInstanceState - a method the system calls prior to destroying the relevant instance.

When I go back to my application the Android system will try and restore its state. How Android does this exactly is not completely transparent but one can guess that the system stores data about your application state in an efficient low overhead format somewhere secure on the system. It then uses this data to recreate your application. It seems reasonable to also assume that the bundle of data you save in onSaveInstanceState is similarly stored.

Stateful enumeration

Android essentially trys to restore what was on screen. That is to say it recreates the activities and the contained fragments and puts them in the correct places. When recreating fragments the system calls the no argument constructor. The problem with this is that it does not store/restore:

  • any data you passed into the instance using a factory
  • the data you downloaded from the server
  • the fact that a network service was still ongoing
  • the fact that a network issues screen was displayed

To resolve these issues in a way which works universally for all destruction/recreation situations I utilized the simplicity of enums !

All you need is something as simple as the below:

enum PAGE_STATE { INITIALIZING, LOADING, LOADED, NETWORK_ISSUES }

PAGE_STATE currentState = PAGE_STATE.INITIALIZING;

When your service is loading data you set the state to LOADING. If there are NETWORK_ISSUES.. ;)

This setup works for restoring the two situations that could occur. These are outlined below.

Detach and Attach

When you detach a fragment its onSaveInstanceState method is not called. This method is only called when the activity needs to save state (see documentation) i.e when it is destroyed.
If we then reattach the fragment its view will be recreated and our properties will be available and in tact - it is the same instance after all.

The problem however is that the state of our fragment means that we do not want the view displayed in its default state. For example, if the state is NETWORK_ISSUES we want to reshow the network issues screen when the view is recreated. We can simply utilize a switch statement based on the value of our currentState property and redisplay the appropriate views.

Likewise if the view was LOADED we can recreate our adapter and set it on our list.

Destroy and recreate

When our activity is destroyed onSaveInstanceState is called on our fragment. In this situation the fragment may well be completely destroyed and as such we may not retain our instance and its set properties. As such we need to save everything that we need to keep our state.

In the example above we may not mind reinitializing everything in which case we do not need to save any state. If however you have used the factory pattern to create the fragment in question and have passed in an identifier to send with your server request you will encounter issues. Specifically, you will encounter a NullPointerException as this property will not exist. To resolve this you need to save the server ID, and restore it when appropriate.

You could avoid requerying the server by saving the data that you have downloaded. In my use case after a server request returns a response, we utilize gson to convert our response JSON into POJOs. These objects implement the Parcelable interface, and as such I can save them in my state bundle and the system will store/restore them.

In my onActivityCreated method I check if I have a bundle of saved state. If I do, I restore the currentState property and execute the appropriate code to restore everything. This restoration involves restoring a list of data.. data which is also saved in the bundle.

There are more complex possibilities for example if the application is destroyed whilst the data is being downloaded.. In this situation the state would be LOADING and based on this you can restart your service appropriately. You could even add an additional state such as DESTROYED_DURING_LOAD and use this state to display a button such that a user must explicitly restart the service when the app is recreated.

Conclusion

Proper state restoration is imperative for a good user experience and is all the more relevant on Android where the range of devices mean consideration really must be given to the possibility of low memory.

Once you have got to grips with the concepts there really is no reason not to implement proper state management because:

  • As outlined.. it is really easy
  • Considering low memory application destruction is similar to the consideration associated with allowing orientation changes.. and surely you've considered that? ;)

If you have any questions, or anything needs clarifying feel free to ask away :)