Tek Eye Logo

Tek Eye

Saving Activity State in an App when it's Interrupted

Android developers soon become aware of the precarious existence of the Activities they code in their applications (apps). The Android operating system (OS) will pause, stop or kill Activities as required to maintain an efficient and smooth user experience. See the Android Activity Lifecycle on the Android developer site, and the article Testing Android's Activity Lifecycle for a lifecycle overview. Despite the possibility of activities being suspended or killed users expect apps to be robust. When the user returns to a screen, e.g. after taking a phone call, the screen is expected to be the same as when it was last viewed, that includes any values entered into any fields.

Mike Bugdroid the Android Logo

(This tutorial assumes that an app can be created and run in Android Studio. If not familiar with app programming in Studio then Tek Eye has beginners articles.)

What is Activity State?

The term state is used to describe the data an application has at a moment in time, what the app is displaying, and the work it is performing. For example a quiz app would know which questions have been asked, how many answers were correct, or incorrect, and which question was next. If a user running the app went and checked their email then returned to the quiz, the quiz should not have gone back to the beginning. For the quiz to continue from the same point its internal state must be preserved. Thus the answer to what is state? It is a snapshot of an apps data and configuration at a point in time.

Android has built in support for saving and restoring state, by overriding a couple of functions an Activity's state can be preserved. Let us start by examining how Android preserves state. Try the following test:

  • Start a new project in Android Studio (here called Restore State using example.com for the domain). Use an Empty Activity. (The app defaults are kept: Activity Name is MainActivity, Layout Name is activity_main.)
  • Drop a Number EditText on to the screen from the Palette in Design mode.
  • Run the app and enter a number.
  • Rotate the device (for the Android Emulator use the rotate icons or Ctrl and Right or Left keys).

As expected the number remains in the field when the screen swaps from portrait to landscape and vice versa. Now delete the id of the EditText (in Studio select the Number field then delete the ID value using the Properties list). Run the app again, enter a number and rotate the device. Notice that this time the number does not appear on the rotated screen.

A Data Entry View with No Id Does Not Preserve State

Switching from Portrait to Landscape with no Id

Switching the device from portrait to landscape causes Android to stop and restart the Activity, allowing Activities the opportunity to redraw a screen for the different dimensions. With stopping and starting an Activity a common occurrence users would be annoyed if input kept being lost. Android activites have a pair of methods called onSaveInstanceState(Bundle) and onRestoreInstanceState(Bundle) which are automatically used by input Views to save their data. These methods only work if the Views that take data can be identified, hence the need for the EditText (and all screen items) to have an id. A bonus is that this method pair can be overridden in an Activity so that state variables not associated with input fields can also be saved and restored.

An App to Demo Preserving State

When writing an app it is not long before the values of internal variables need preserving, for example holding the current high scores for a game session, or storing an alogrithm's internal values. This can be done using the same onSaveInstanceState(Bundle) and onRestoreInstanceState(Bundle) functions that the user interacting Views use. Here a simple app will show custom state saving in action. The example app is going to be used to display the next number in a sequence of numbers, in this case the Fibonacci sequence. Each number in the sequence is the sum of the previous two numbers. Starting at one there is no previous number so the sum equals one. The sum is then 1+1 giving two, then 1+2 giving 3 and so on:

1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377...

The alogrithm is simply Number = Number + Previous Number, and will be used in this state example app. Start by replacing the EditText with a TextView, here the textSize property has been set to 18dp (density pixels). Drop a Button next to the TextView, setting the text to Next. Change the Hello World! text to Next Fibonnaci Number.

Activity State Demo

The Example State App Code

Add a function to handle the Button's onCLick event, there are different ways to code Android event listeners, here the onClick attribute is used to link to a public function called HandleClick. Here is the code for our MainActivity.Java class.

package com.example.restorestate;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    TextView next_number;   //Current Fibonacci number on screen
    long number_state=0L;   //Stores previous Fibonacci number

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //Link next_number to screen TextView
        next_number = (TextView) findViewById(R.id.textView2);
    }
    //For Button presses (linked via onClick attribute)
    public void HandleClick(View arg0) {
        //Load previous number from the state
        long previous_number = number_state;
        //Update the state (from the screen) prior to next calculation
        number_state = Long.parseLong(next_number.getText().toString());
        //Calculate next Fibonacci number and update screen
        next_number.setText(Long.toString(number_state + previous_number));
    }
}

And for completeness the layout XML file, notice android:onClick="HandleClick" for the Button :

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.restorestate.MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Next Fibonnaci Number"
        android:id="@+id/textView" />

    <TextView
        android:text="1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/textView2"
        android:layout_alignBaseline="@+id/button"
        android:layout_alignBottom="@+id/button"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignRight="@+id/textView"
        android:layout_alignEnd="@+id/textView"
        android:textSize="18dp" />

    <Button
        android:text="Next"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button"
        android:layout_marginLeft="17dp"
        android:layout_marginStart="17dp"
        android:layout_below="@+id/textView"
        android:layout_toRightOf="@+id/textView2"
        android:layout_toEndOf="@+id/textView2"
        android:onClick="HandleClick" />

</RelativeLayout>

Why is the TextView Reset on Screen Rotation?

Run the program and for every press of the NEXT button the next number in the Fibonnaci sequence is shown. The app appears to work. However, if the screen is rotated the sequence resets. Because rotating the screen causes the Activity to reload and everthing is reset. But wait a minute that didn't happen for the EditText! Why? Remember that earlier it was emphasized that input Views automatically saved state if they had the ID property set. A TextView is not an input View therefore its state is not automatically saved. It is possible to get a TextView to call onSaveInstanceState(Bundle) and onRestoreInstanceState(Bundle). Either is must be flagged as being selectable (for copying purposes), android:textIsSelectable="true", or, if the text must remain unselectable, the attribute freezesText must be set to true, android:freezesText="true". Try that, set freezesText to true, either in the XML or by checking the property for the TextView in the Properties list.

...
android:textSize="18dp"
android:freezesText="true" />
...

Run the app again and now when the screen rotates the TextView retains its value. However, the Fibonacci sequence is then calculated incorrectly on subsequent button presses. This time the internal storage of the previous number, stored in the number_state value is lost. This can be solve by preserving the number_state variable. Add the following code to the MainActivity class, just before the last closing brace (notice the calls to the super class methods that must be performed).

public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putLong("PREVIOUS", number_state);
}
public void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    number_state=savedInstanceState.getLong("PREVIOUS");
}

Run the updated app and repeat the test and this time the sequence is calculated after the screen rotates.

It is strightforward to support saving state in Android, there are plenty of helpful methods on the Bundle class. Notice that the onCreate(Bundle) method for the Activity also has access to the same Bundle, however if accessing the Bundle in onCreate the code will need to check for null, for when onSaveInstanceState(Bundle) was not called (e.g. when a user exits an application normally). In the example above the overridden function onRestoreInstanceState(Bundle savedInstanceState) can be removed and its code placed at the end of the onCreate(Bundle savedInstanceState) function:

if(savedInstanceState!=null)  
    number_state=savedInstanceState.getLong("PREVIOUS");

onSaveInstanceState(Bundle) and onPause()

Use onPause() to save data that should be preserved long term (between each run of an app), use onSaveInstanceState(Bundle) for transient states. For example in a game the current score could be saved in onSaveInstanceState(Bundle) and only saved in onPause() if it was good enough for the High Score table, in which case the High Score table (in SharedPreferences, a database table, or a file) is updated.

onSaveInstanceState and onRestoreInstanceState and the Android Lifecycle

The SDK documentation used to say that onSaveInstanceState(Bundle) may run just before or just after onPause(), now it runs between onPause() and onStop(). As for onRestoreInstanceState(Bundle) that occurs after onStart() and before onResume().

Example Project

Download the full example code for the demo in this article ready for importing into an Android Studio. An instructions.txt is provided how to import the project into Studio. The code can also be accessed via the Android Example Projects page. A version of this article was produced for the Android Cookbook.

Author:  Published:  Updated:  

ShareSubmit to TwitterSubmit to FacebookSubmit to Google+Submit to LinkedInSubmit to redditPrint Page

markdown CMS Small Logo Icon ↓markdown↓ CMS is fast and simple. Build websites quickly and publish easily. For beginner to expert.



Articles on:

Android, HTML, VPS, Computing, IT, Computer History, ↓markdown↓ CMS



Free Android Sample Projects:

Android Examples, Android UI Examples