«

»

Feb 27

Print this Post

SavedState: Preserve data when your Activity is recreated – Part 1

One difference to between conventional programming and Android development is that the Android OS can at any time kill your Activity if it runs low on resources or in certain other conditions like configuration changes.

This can be very critical when it comes to data. On such an event, there is a possibility that data loss will occur and as programmer you want to avoid that under any circumstances.

Most of the standard widgets like EditText already preserve their state under certain circumstances. By default widgets/UI elements with an android:id attribute defined, will by default preserve their states when the application is destroyed and recreated.

But what if the you need to preserve data defined inside of your Activity or your own widgets?

In the first part of this tutorial, I’ll show you how to preserve data within the Activity when the activity gets destroyed. There are a few events when this can happen.

  • Configuration change
    • Screen orientation change (i.e. changing from portrait to landscape mode)
    • Localization change (i.e. switching from English to German language)
  • Low resources
    • Running out of RAM

In most cases, the developer has no control over such events and they are hardly predictable. This makes it important to always temporary save critical application data when the Activity lose it’s focus.

Every time the Activity change it’s states in which it could possibly killed, the onSaveInstanceState(Bundle outState) method is called. In this method the developers can temporary save all critical data which will preserved when the Activity is killed for any of the reasons mentioned above. If the application was killed in the meantime, it will be recreated and onRestoreInstanceState(Bundle savedInstanceState) will be called. The savedInstanceState will also be passed to the onCreate(Bundle savedInstaceState) method. At this point we can get the data and resume the operation.

onRestoreInstanceState(Bundle savedInstanceState) will only be called if the Activity was destroyed while will be called every time when there is a possibility that the Activity can be killed by the system.

onCreate(Bundle savedInstaceState) will only contain a valid savedInstanceState reference, when the Activity is being recreated. So the first time, your application is started, will be null! Remember that, when you’re reading the data inside the onCreate(Bundle savedInstaceState) method!

While you can initiate your saved data inside onCreate(Bundle savedInstaceState) method, it’s advised to move the more CPU intensive initializations inside the onRestoreInstanceState(Bundle savedInstanceState) method

A typical application

To demonstrate this, I’ve created a very simple example. It will consists of only 2 TextViews, one AutoCompleteTextView and one Button.

As usual you create a new Android Application in Eclipse or the IDE of your choice.

First, the XML layout file used for the application, main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
	<TextView
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		android:text="@string/howto"
		>
	</TextView>
	<TextView
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		android:text="@string/product"
		>
	</TextView>
	<AutoCompleteTextView
		android:id="@+id/productlist"
		android:completionThreshold="1"
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		>
	</AutoCompleteTextView>
	<Button
		android:id="@+id/btnShowSelected"
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		android:text="Show selected"
		>
	</Button>
	<TextView
		android:id="@+id/result"
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		>
	</TextView>
</LinearLayout>

It’s quite simple and nothing new in here. So I’ll skip it to the actual code of the Demo application.

SavedStateExample1.java:

package com.tseng.examples.SavedStateExample1;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;

public class SavedStateExample1 extends Activity {
	TextView lbResult;
	Button btnShowSelected;
	AutoCompleteTextView acProductList;

	final private static String products[] = new String[] {
		"Apple",
		"Ananas",
		"Apricot",
		"Banana"
	};
	private String selectedProduct;
	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Set Layout
        setContentView(R.layout.main);
        
        // Get references to the widgets we need
        acProductList = (AutoCompleteTextView)findViewById(R.id.productlist);
        lbResult = (TextView)findViewById(R.id.result);
        btnShowSelected = (Button)findViewById(R.id.btnShowSelected);
        btnShowSelected.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
		        lbResult.setText("Selected: " + selectedProduct);
			}
		});
        
        // Create adapter 
        ArrayAdapter<String> priceListAdapter = new ArrayAdapter<String>(this, 
        		android.R.layout.simple_dropdown_item_1line,
        		android.R.id.text1,
        		products);
        acProductList.setAdapter(priceListAdapter);
        acProductList.setOnItemClickListener(new OnItemClickListener() {
				@Override
				public void onItemClick(AdapterView<?> adapter, View v, int position, long id) {
					selectedProduct = (String)adapter.getItemAtPosition(position);
				}
			}
        );
    }
}

Before we continue, a few explanations for developers new to Java/Android development.

final private static String products[] = new String[] {
		"Apple",
		"Ananas",
		"Apricot",
		"Banana"
	};
	private String selectedProduct;

The products[] variable holds oure data for the AutoCompleteTextView which we will bind to the AutoCompleteTextView widget with the code below. selectedProduct will simply hold the String from the selected list item.

        // Create adapter 
        ArrayAdapter<string> priceListAdapter = new ArrayAdapter<string>(this, 
        		android.R.layout.simple_dropdown_item_1line,
        		android.R.id.text1,
        		products);
        acProductList.setAdapter(priceListAdapter);
        acProductList.setOnItemClickListener(new OnItemClickListener() {
				@Override
				public void onItemClick(AdapterView<?> adapter, View v, int position, long id) {
					selectedProduct = (String)adapter.getItemAtPosition(position);
				}
			}
        );

There we create an simple ArrayAdapter which will manage the underlying data. Also we add an OnItemClickListener which will trigger, when the user has selected an item from the auto-complete list. Implementing Listeners is already explain in my previous post “Implementing Listeners in Android/Java” and “How to implement your own Listeners in Android/Java

In your actual application, you’d like to get your data from a database, a ContentProvider or from a web server. For simplicity reasons, we simply use a predefined array.

Now we can launch our demo application and test it out.

01 02

First enter the first letter of the product you want to test, in the screenshots above the letter “A” and select one of the items, i.e. Ananas. Once selected it will appear on in the EditText View. If we click on “Show selected”, the text “Selected: Ananas” will appear, indicating us that the value was correctly saved.

So far everything is working. But what happens if you change the orientation of the screen (i.e. by pulling out the keyboard of the T-Mobile G1? Let’s test it out.

03

First thing we notice is that our result TextView (displaying “Selected: Ananas”) is gone. The reason is because by default it’s empty and changing the orientation by default forces the Activity to be destroyed and recreated with the new orientation.

It’s important to notice that only the Activity is destroyed and recreated, not your whole application! An Android application can consist of many Activities, Services and ContentProviders! If the application is closed (for example by pressing the “Back” Button, then all values will be gone. savedInstaceState is only there to preserve data temporary when an Activity is destroyed/recreated, not the application itself.

If you want to preserve data permanently, you need to save it either as Preferences or in a ContentProvider/database. But that’s another topic!

04

I could get it simply back, by pressing “Show selected” again or set it programmatically, you may think now. Try it out, you’ll be surprised. Instead of showing “Selected: Ananas”, it shows only “Selected: null”.

What happened?

When the screen orientation was changed, the Activity was destroyed and recreated. So the selectedProduct variable wasn’t initiated after the recreation.

In worst case, this could lead to data loss or your application/Activity could throw an Exception and crash. As developer you need to prevent such cases. In order to prevent this to happen, we’ll need to save our important data in an temporary savedInstanceState object and get it when it was recreated.

So first, we’re creating a static variable, which will used as key to save and retrieve the variable.

final private static String PRODUCT_TITLE = "title";

You could also directly use a string to define the key, but it’s much better and cleaner to define an final static string so if you later need to change it, it’s enough to change it at one single place.

There won’t be any negative performance effects if you use the final and static keywords together. The compiler compile it inline, so it’s same as writing “title” on every place you need to use the key.

Next we need to Override two Activity-methods: onSaveInstanceState and onRestoreInstanceState and add our code to handle the saving of the savedInstanceState.

	@Override
	protected void onRestoreInstanceState(Bundle savedInstanceState) {
		selectedProduct =  savedInstanceState.getString(PRODUCT_TITLE);
		if(!TextUtils.isEmpty(selectedProduct)) {
			// Update lbResult
	        lbResult.setText("Selected: " + selectedProduct);
		}
		super.onRestoreInstanceState(savedInstanceState);
	}

	@Override
	protected void onSaveInstanceState(Bundle outState) {
		/**************************************************
		 * Here we do _temporary_ save all critical data, 
		 * like our selectedProduct.
		 **************************************************/
		outState.putString(PRODUCT_TITLE, selectedProduct);
		super.onSaveInstanceState(outState);
	}

As we can see, it’s very easy to save temporary data which will be preserved when the Activity is destroyed. Additionally in the onRestoreInstanceState-method we’re updating the lbResult TextView field.

If we now start our demo application and repeat the steps from above, we’ll see that this time the “Selected: Ananas” remains visible after screen orientation change and it’s not “Selected: null” anymore, like in the screenshot below:

05

You should only store data inside the savedInstaceState Bundle object, which is important and for the application/Activity to run. For example:

  • Session data
  • Cursor positions
  • Calculated values

You should NOT save values, which may be invalid or outdated when the Activity is recreated (i.e. the Activity was in background for a long time). For example:

  • Current location (this can get outdated very quickly)
  • Current time
  • Socket/Http Connection objects (instead only save the cookie or session data and recreate a new Socket/Http Connection when the Activity is restored)

As always, the full code of SavedStateExample1.java:

package com.tseng.examples.SavedStateExample1;

import android.app.Activity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;

public class SavedStateExample1 extends Activity {
	TextView lbResult;
	Button btnShowSelected;
	AutoCompleteTextView acProductList;

	final private static String PRODUCT_TITLE = "title";

	final private static String products[] = new String[] {
		"Apple",
		"Ananas",
		"Apricot",
		"Banana"
	};
	private String selectedProduct;
	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Set Layout
        setContentView(R.layout.main);
        
        // Get references to the widgets we need
        acProductList = (AutoCompleteTextView)findViewById(R.id.productlist);
        lbResult = (TextView)findViewById(R.id.result);
        btnShowSelected = (Button)findViewById(R.id.btnShowSelected);
        btnShowSelected.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
		        lbResult.setText("Selected: " + selectedProduct);
			}
		});
        
        // Create adapter 
        ArrayAdapter<String> priceListAdapter = new ArrayAdapter<String>(this, 
        		android.R.layout.simple_dropdown_item_1line,
        		android.R.id.text1,
        		products);
        acProductList.setAdapter(priceListAdapter);
        acProductList.setOnItemClickListener(new OnItemClickListener() {
				@Override
				public void onItemClick(AdapterView<?> adapter, View v, int position, long id) {
					selectedProduct = (String)adapter.getItemAtPosition(position);
				}
			}
        );
    }

	@Override
	protected void onRestoreInstanceState(Bundle savedInstanceState) {
		selectedProduct =  savedInstanceState.getString(PRODUCT_TITLE);
		if(!TextUtils.isEmpty(selectedProduct)) {
			// Update lbResult
	        lbResult.setText("Selected: " + selectedProduct);
		}
		super.onRestoreInstanceState(savedInstanceState);
	}

	@Override
	protected void onSaveInstanceState(Bundle outState) {
		/**************************************************
		 * Here we do _temporary_ save all critical data, 
		 * like our selectedProduct.
		 **************************************************/
		outState.putString(PRODUCT_TITLE, selectedProduct);
		super.onSaveInstanceState(outState);
	}
}

Next time, I’ll tell you how to preserve SavedStates for widgets, as it’s quite differently than that one of an Activity. The Activity accepts Bundle-object which will hold the saved values. In your own widgets however, you’ll have to implement your own SavedState class.

Permanent link to this article: http://tseng-blog.nge-web.net/blog/2009/02/27/savedstate-preserve-data-when-application-is-recreated/

6 comments

Skip to comment form

  1. Arbour

    Of course, what a great site and informative posts, I will add backlink – bookmark this site? Regards, Reader.

  2. chris

    THX 😀 Worked perfektly

  3. ez

    thank you

  4. Manas

    Nice explanation , very helpful.

  5. Sivaraj

    Very Nice Explanation,It is very useful to me Thanks…

  6. pave beads

    Hi, can any body help me how to get this video tutorial from this web page, I have watched and listen it at this time but desire to down load it.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>