Managing AppStates in Android

There are times when the state of the app must be retained even if the app is closed.

At first, I used to subclass the Application class and store the application's state variables there. The Android developer reference, however, says that's not required:

There is normally no need to subclass Application. In most situation, static singletons can provide the same functionality in a more modular way...

In the following example, we'll create an AppState class that will be serialized to JSON. This JSON representation will then be stored in the App's SharedPreferences.

Create the AppState singleton

First we create a singleton class as follows:

public class AppState {

    private static AppState instance;
    public static AppState getInstance() {
        if(instance == null) {
            instance = new AppState();
        }
        return instance;
    }

    private AppState() {

    }

    // State variables follows
}

Add state variables

Next, we add the state variables to the AppState class.

public boolean milk;
public boolean laundry;
public boolean bed;
public String description;

Properties that shouldn't be persisted, or cannot be serialized should be marked as transient. For example:

public transient boolean adShownSinceLaunch;

Committing the AppState

Add GSON as a gradle dependency:

compile 'com.google.code.gson:gson:2.4'

Let's create two helper functions to serialize and deserialize the AppState:

private String serialize() {
    try {
        return new Gson().toJson(this);
    } catch (JsonParseException e) {
        e.printStackTrace();
        return null;
    }
}

private AppState deserialize(String appStateJsonString) {
    try {
        return new Gson().fromJson(appStateJsonString, AppState.class);
    } catch (JsonParseException e) {
        e.printStackTrace();
        return null;
    }
}

Now, we write the public methods to commit (save) and restore the appstate. I'm storing the appstate in SharedPreferences – you may choose to save it anywhere else.

public boolean commit(Context context) {
    try {
        String serializedString = serialize();
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
        SharedPreferences.Editor editor = preferences.edit();
        editor.putString("MYPREFERENCENAME", serializedString); // TODO: convert preference name to a static final field
        return editor.commit();
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }
}

public void restore(Context context) {
    String prefString = PreferenceManager.getDefaultSharedPreferences(context).getString("MYPREFERENCENAME", "");  // TODO: convert preference name to a static final field
    AppState as = deserialize(prefString);
    if (as != null) {
        this.milk = as.milk;
        this.laundry = as.laundry;
        this.bed = as.bed;
        this.description = as.description;
    } /*else {
        // TODO: handle this
    }*/
}

Each time a non-transient state variable is added to the AppState class, you will have to assign the new field in AppState.restore(). You may avoid that by assigning the values using reflection instead - but I personally prefer doing it this way.

Using the AppState

The AppState can be accessed from anywhere as follows:

AppState.getInstance()

The AppState needs to be restored on Application start-up. I would do this in the dispatcher activity's onCreate():

AppState.getInstance().restore();

The AppState can be committed as follows:

AppState.getInstance().commit();

You would want to commit the AppState in the onPause() on Activities where the AppState has been updated.

Also, if you're going to be accessing the class from different threads, remember to ensure it's thread-safe.

I've created a simple working app which you can access from GitHub here.