Smartphones

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...

4 comments
lak2013
lak2013

I have read many questions and explanation on how to manage state in Android apps. I would like to get more clear definition based on concrete use cases ,

privateArrayList<String> mHistory =newArrayList<String>();

1) mHistory should be retained only in single activity ( answer:: Bundle ??)

2) mHistory should be retained in specific 3 activities in a sequence (??)

3) mHistory should be retained in specific 3 activities but NOT in a sequence ( ?? )

4) mHistory should be retained in all activities involved in the app ( answer:: application context ??)

cooljaini
cooljaini

package com.example.helloandroid; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.EditText; import android.widget.Button; import android.widget.Toast; public class HelloAndroid1 extends Activity { /** Called when the activity is first created. */ private EditText text1; private EditText text2; private EditText text3; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); text1 = (EditText) findViewById(R.id.txt1); text2 = (EditText) findViewById(R.id.txt2); text3 = (EditText) findViewById(R.id.txt3); } public void myClickHandler(View view) { switch (view.getId()) { case R.id.btn1: Button btn1=(Button)findViewById(R.id.btn1); int inputValue7 = Integer.parseInt(text1.getText().toString()); int inputValue8 = Integer.parseInt(text2.getText().toString()); if (btn1.isClickable()) { text3.setText(String .valueOf(division(inputValue7,inputValue8))); break; } case R.id.bnt2: Button btn2=(Button)findViewById(R.id.bnt2); int inputValue1 = Integer.parseInt(text1.getText().toString()); int inputValue2 = Integer.parseInt(text2.getText().toString()); if (btn2.isClickable()) { text3.setText(String .valueOf(addition(inputValue1,inputValue2))); break; } case R.id.btn3: Button btn3=(Button)findViewById(R.id.btn3); int inputValue3 = Integer.parseInt(text1.getText().toString()); int inputValue4 = Integer.parseInt(text2.getText().toString()); if (btn3.isClickable()) { text3.setText(String .valueOf(substraction(inputValue3,inputValue4))); } break; case R.id.btn4: Button btn4=(Button)findViewById(R.id.btn4); int inputValue5 = Integer.parseInt(text1.getText().toString()); int inputValue6 = Integer.parseInt(text2.getText().toString()); if (btn4.isClickable()) { text3.setText(String .valueOf(multiply(inputValue5,inputValue6))); } break; } } private int addition(int l,int m) { return(l+m); } private int multiply(int l,int m) { return(l*m); } private int division(int l,int m) { return(l/m); } private int substraction(int l,int m) { return(l-m); } }

Dr.Dre'del
Dr.Dre'del

I have yet to get a clear answer on this from anyone. I generally just store my vars up in the Application, which, as far as I understand, lives along with the Activities in perpetuity. Why go through all the hassle of packing and unpacking things into the Bundle if the live data is available to you at all times? It also makes for very convenient sharing of data between Activities. What am I overlooking? Is there some danger of memory leak? Or can the application vars be released unexpectedly? (I have ever to see that happen)

authorwjf
authorwjf

In my experience extending an instance of the Application class, and using it to store some global variables, *is* an acceptable approach in various scenarios. Whether it is the right approach depends largely on your application and the specific circumstances. Remember that Android is an embedded and small footprint environment. Keeping track of some application state-wide variables up at the Application object so it can be referenced via multiple activities is probably reasonable. Storing bitmaps being manipulated in an activity probably isn't. The idea being that when a call comes in and your activity is paused or ended to free up other resources, the big chunks of data you are storing in your extended application instance are not freed up. So if you are not responsible in the choices you make in your implementation, then your app can negatively impact the user's phone over all. To my knowledge it *is* possible for the kernel to kill your entire application under extreme resource starved conditions, but like you I've yet to see it happen. So I guess the short answer to your question is I see nothing wrong with your approach, so long as it is used with discretion. At the same time, I don't see packing and unpacking activity level variables as a major hassle or concession, and, I see scenarios where doing so would be beneficial over your approach (such as those I discussed earlier where the variable I am trying to keep up with is large enough to have a negative impact on the OS overall, or, as I will discuss in the future, when the variable is a pointer to an activity specific async task that is performing long running i/o on a background thread). I hope that answers your question. And thanks again for taking the time to share it. You made a really good point.