Mobility

Handling internal variable state in Android development

William Francis explains how to master another technique required to successfully manage state in your Android apps: handling internal variables.

When it comes to learning Android development, I think state management is one of the most confusing concepts to grasp. Android's activity lifecycle can be a bit daunting even to seasoned developers.

While I appreciate Google's liberal market requirements for getting an app into the store, there are quite a few apps available for download that simply don't "make the grade" in my book. In most of the cases I come across, the problem isn't in the developer's application concept (though I've seen a handful of apps that are just bad ideas), the problem resides largely in programs that fail to correctly handle the various state changes that occur on the Android platform. The result of this failure is the dreaded "Activity has stopped unexpectedly" dialog, and ultimately, a forced close.

State management in an Android application happens at three layers. First there is the handling of the UI, which is particularly relevant during orientation changes. Next, there is the handling of internal application variables. Last but not least, there are special state handling techniques that must be taken into account when performing long-running I/O. I covered UI state management and orientation changes in a previous App Builder post. This tutorial focuses on the handling of internal variables.

When it comes to keeping up with internal variables across activity restarts, you should remember these points:

  • The default handler in the framework will handle saving and restoring values in your UI widgets automatically, providing that the widget in question has an Android "id" defined in its layout file.
  • Override the method onSaveInstance to preserve non-UI values. Call the parent implementation first, and then add your own internal variables to the bundle.
  • Unless you are sub-classing your activity or require your internal variables to be restored after your activity has been initialized in its entirety, do not override onRestoreInstanceState — use the savedInstanceState bundle parameter of the standard onCreate method.

Clear as mud? Don't worry, the bulleted items above should make more sense as we get into our sample application. Speaking of which, the good folks at TechRepublic are always nice enough to make a zip file available with each of my tutorials, so readers like yourself have the option of importing the project directly into Eclipse. This week is no exception, and you may download the project here. However, in this case, I highly recommend following the tutorial first. Most weeks it doesn't matter so much as it's the finished product we are interested in, but this week, the process is an important part of the takeaway. Only in building the app progressively will you come to truly appreciate how the state management is achieved.

1. Start a new Android project in Eclipse targeting version 1.6 or greater. We will attempt to create a very simple calculator type app for this demonstration. Figure A is a peek of what it will look, like so you have some idea of what we are building. Figure A

2. In your /layout folder, add the following xml.

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"

android:gravity="center">

<TextView

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="What's My State Again?"

android:paddingBottom="20dip"/>

<LinearLayout

android:orientation="horizontal"

android:layout_width="wrap_content"

android:layout_height="wrap_content">

<EditText

android:id="@+id/first_number"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="1"/>

<TextView

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text=" x "/>

<EditText

android:id="@+id/second_number"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="5"/>

<TextView

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text=" = "/>

<EditText

android:id="@+id/answer"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:enabled="false"/>

</LinearLayout>

<LinearLayout

android:orientation="horizontal"

android:layout_width="wrap_content"

android:layout_height="wrap_content">

<Button

android:id="@+id/calc_button"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="Multiply"/>

<Button

android:id="@+id/history_button"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="History"/>

</LinearLayout>

</LinearLayout>

3. Now we are ready to add the Main.java class to the /src folder. To begin, we are going to add a pretty standard on create that simply wires our buttons, as well as includes an array list for storing our "history" log.

Main.java
package com.authorwjf;
import java.util.ArrayList;
import android.app.Activity;
import android.app.AlertDialog;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.Toast;
private ArrayList<String> mHistory = new ArrayList<String>();

@Override

public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); public class Main extends Activity implements OnClickListener {

setContentView(R.layout.main);

View v = findViewById(R.id.calc_button);

v.setOnClickListener(this);

v = findViewById(R.id.history_button);

v.setOnClickListener(this);

}

}

4. The class implements an on click listener, which means we'll need to override an on click handler. The screen has two buttons: one to multiply the values together and one to display the contents of our array list. We can perform all the logic for the app inside the on click handler.

Main.java

@Override

public void onClick(View v) { if (v.getId() == R.id.calc_button) {

EditText et = (EditText)findViewById(R.id.first_number);

int n1 = Integer.parseInt(et.getText().toString());

et = (EditText)findViewById(R.id.second_number);

int n2 = Integer.parseInt(et.getText().toString());

et = (EditText)findViewById(R.id.answer);

int answer = n1*n2;
et.setText(Integer.toString(answer));

mHistory.add(Integer.toString(n1)+" x "+

Integer.toString(n2)+" = "+

Integer.toString(answer));

} else { AlertDialog.Builder builder = new AlertDialog.Builder(this); StringBuilder msg = new StringBuilder(); if (mHistory.size() > 0) { for (int i=0; i< mHistory.size(); i++) {

msg.append(mHistory.get(i)+"\n");

}

} else {

msg.append("Empty");

}

builder.setMessage(msg.toString());

AlertDialog alert = builder.create();

alert.show();

}

}
5. Let's go ahead and give our app a go. You should be able to multiply several numbers. Then go ahead and click the history button. Figure B Figure B

6. So far, so good, but what happens when we rotate the phone and then hit the history button? The last values in the fields get restored automatically by the framework, but our array list is empty (Figure C). Figure C

7.  Fortunately, not a lot is required to save the state of our array list. First, we must override the on save instance state method in Main.java like so.

Main.java

@Override

protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState);

outState.putSerializable("history", mHistory);

Toast.makeText(this, "History saved.", Toast.LENGTH_SHORT).show(); }

8. Then we just need to modify the original on create method to check the incoming bundle and pre-load our array list if appropriate.

Main.java

@Override

public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);

setContentView(R.layout.main);

if (savedInstanceState != null) { mHistory = (ArrayList<String>) savedInstanceState.getSerializable("history");

}

View v = findViewById(R.id.calc_button);

v.setOnClickListener(this);

v = findViewById(R.id.history_button);

v.setOnClickListener(this); }
9. Now let's try our little experiment again. Anything look different? (Figure D) Figure D

Success! If you're keeping track, we have now mastered two of the three techniques required to successfully manage state in our Android apps. Next time when we broach this subject, we'll be tackling long-running I/O. Until then — happy coding!

About

William J Francis began programming computers at age eleven. Specializing in embedded and mobile platforms, he has more than 20 years of professional software engineering under his belt, including a four year stint in the US Army's Military Intellige...

Editor's Picks