I've been working on the Android SDK platform, and it is a little unclear how to save an application's state. So given this minor re-tooling of the 'Hello, Android' example:
package com.android.hello;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class HelloAndroid extends Activity {
private TextView mTextView = null;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mTextView = new TextView(this);
if (savedInstanceState == null) {
mTextView.setText("Welcome to HelloAndroid!");
} else {
mTextView.setText("Welcome back.");
}
setContentView(mTextView);
}
}
I thought it would be enough for the simplest case, but it always responds with the first message, no matter how I navigate away from the app.
I'm sure the solution is as simple as overriding onPause
or something like that, but I've been poking away in the documentation for 30 minutes or so and haven't found anything obvious.
When you rotate your device and the screen changes orientation, Android usually destroys your application's existing Activities and Fragments and recreates them. Android does this so that your application can reload resources based on the new configuration.
Just add android:configChanges="orientation|screenSize" in activity tab of manifest file. So, Activity won't restart when orientation change.
To turn off screen rotation on Android, tap Settings > Display > Auto-rotate screen > turn off Auto-rotate screen.
You need to override onSaveInstanceState(Bundle savedInstanceState)
and write the application state values you want to change to the Bundle
parameter like this:
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
// Save UI state changes to the savedInstanceState.
// This bundle will be passed to onCreate if the process is
// killed and restarted.
savedInstanceState.putBoolean("MyBoolean", true);
savedInstanceState.putDouble("myDouble", 1.9);
savedInstanceState.putInt("MyInt", 1);
savedInstanceState.putString("MyString", "Welcome back to Android");
// etc.
}
The Bundle is essentially a way of storing a NVP ("Name-Value Pair") map, and it will get passed in to onCreate()
and also onRestoreInstanceState()
where you would then extract the values from activity like this:
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
// Restore UI state from the savedInstanceState.
// This bundle has also been passed to onCreate.
boolean myBoolean = savedInstanceState.getBoolean("MyBoolean");
double myDouble = savedInstanceState.getDouble("myDouble");
int myInt = savedInstanceState.getInt("MyInt");
String myString = savedInstanceState.getString("MyString");
}
Or from a fragment.
@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
// Restore UI state from the savedInstanceState.
// This bundle has also been passed to onCreate.
boolean myBoolean = savedInstanceState.getBoolean("MyBoolean");
double myDouble = savedInstanceState.getDouble("myDouble");
int myInt = savedInstanceState.getInt("MyInt");
String myString = savedInstanceState.getString("MyString");
}
You would usually use this technique to store instance values for your application (selections, unsaved text, etc.).
The savedInstanceState
is only for saving state associated with a current instance of an Activity, for example current navigation or selection info, so that if Android destroys and recreates an Activity, it can come back as it was before. See the documentation for onCreate
and onSaveInstanceState
For more long lived state, consider using a SQLite database, a file, or preferences. See Saving Persistent State.
Note that it is not safe to use onSaveInstanceState
and onRestoreInstanceState
for persistent data, according to the documentation on Activity.
The document states (in the 'Activity Lifecycle' section):
Note that it is important to save persistent data in
onPause()
instead ofonSaveInstanceState(Bundle)
because the later is not part of the lifecycle callbacks, so will not be called in every situation as described in its documentation.
In other words, put your save/restore code for persistent data in onPause()
and onResume()
!
For further clarification, here's the onSaveInstanceState()
documentation:
This method is called before an activity may be killed so that when it comes back some time in the future it can restore its state. For example, if activity B is launched in front of activity A, and at some point activity A is killed to reclaim resources, activity A will have a chance to save the current state of its user interface via this method so that when the user returns to activity A, the state of the user interface can be restored via
onCreate(Bundle)
oronRestoreInstanceState(Bundle)
.
My colleague wrote an article explaining application state on Android devices, including explanations on activity lifecycle and state information, how to store state information, and saving to state Bundle
and SharedPreferences
. Take a look at it here.
The article covers three approaches:
[Code sample – Store state in state bundle]
@Override
public void onSaveInstanceState(Bundle savedInstanceState)
{
// Store UI state to the savedInstanceState.
// This bundle will be passed to onCreate on next call. EditText txtName = (EditText)findViewById(R.id.txtName);
String strName = txtName.getText().toString();
EditText txtEmail = (EditText)findViewById(R.id.txtEmail);
String strEmail = txtEmail.getText().toString();
CheckBox chkTandC = (CheckBox)findViewById(R.id.chkTandC);
boolean blnTandC = chkTandC.isChecked();
savedInstanceState.putString(“Name”, strName);
savedInstanceState.putString(“Email”, strEmail);
savedInstanceState.putBoolean(“TandC”, blnTandC);
super.onSaveInstanceState(savedInstanceState);
}
[Code sample – store state in SharedPreferences]
@Override
protected void onPause()
{
super.onPause();
// Store values between instances here
SharedPreferences preferences = getPreferences(MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit(); // Put the values from the UI
EditText txtName = (EditText)findViewById(R.id.txtName);
String strName = txtName.getText().toString();
EditText txtEmail = (EditText)findViewById(R.id.txtEmail);
String strEmail = txtEmail.getText().toString();
CheckBox chkTandC = (CheckBox)findViewById(R.id.chkTandC);
boolean blnTandC = chkTandC.isChecked();
editor.putString(“Name”, strName); // value to store
editor.putString(“Email”, strEmail); // value to store
editor.putBoolean(“TandC”, blnTandC); // value to store
// Commit to storage
editor.commit();
}
[Code sample – store object instance]
private cMyClassType moInstanceOfAClass; // Store the instance of an object
@Override
public Object onRetainNonConfigurationInstance()
{
if (moInstanceOfAClass != null) // Check that the object exists
return(moInstanceOfAClass);
return super.onRetainNonConfigurationInstance();
}
This is a classic 'gotcha' of Android development. There are two issues here:
Browsing across all these threads, I suspect that much of the time developers are talking about these two different issues simultaneously ... hence all the confusion and reports of "this doesn't work for me".
First, to clarify the 'intended' behavior: onSaveInstance and onRestoreInstance are fragile and only for transient state. The intended usage (as far as I can tell) is to handle Activity recreation when the phone is rotated (orientation change). In other words, the intended usage is when your Activity is still logically 'on top', but still must be reinstantiated by the system. The saved Bundle is not persisted outside of the process/memory/GC, so you cannot really rely on this if your activity goes to the background. Yes, perhaps your Activity's memory will survive its trip to the background and escape GC, but this is not reliable (nor is it predictable).
So if you have a scenario where there is meaningful 'user progress' or state that should be persisted between 'launches' of your application, the guidance is to use onPause and onResume. You must choose and prepare a persistent store yourself.
But - there is a very confusing bug which complicates all of this. Details are here:
Activity stack behaves incorrectly during the first run of an app when started from Eclipse (#36907463)
Marketplace / browser app installer allows second instance off app (#36911210)
Basically, if your application is launched with the SingleTask flag, and then later on you launch it from the home screen or launcher menu, then that subsequent invocation will create a NEW task ... you'll effectively have two different instances of your app inhabiting the same stack ... which gets very strange very fast. This seems to happen when you launch your app during development (i.e. from Eclipse or IntelliJ), so developers run into this a lot. But also through some of the app store update mechanisms (so it impacts your users as well).
I battled through these threads for hours before I realized that my main issue was this bug, not the intended framework behavior. A great write-up and workaround (UPDATE: see below) seems to be from user @kaciula in this answer:
Home key press behaviour
UPDATE June 2013: Months later, I have finally found the 'correct' solution. You don't need to manage any stateful startedApp flags yourself. You can detect this from the framework and bail appropriately. I use this near the beginning of my LauncherActivity.onCreate:
if (!isTaskRoot()) {
Intent intent = getIntent();
String action = intent.getAction();
if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && action != null && action.equals(Intent.ACTION_MAIN)) {
finish();
return;
}
}
onSaveInstanceState
is called when the system needs memory and kills an application. It is not called when the user just closes the application. So I think application state should also be saved in onPause
.
It should be saved to some persistent storage like Preferences
or SQLite.
Both methods are useful and valid and both are best suited for different scenarios:
onSaveInstanceState()
and onRestoreInstanceState()
is usually adequate.If you save the state data in a persistent manner, it can be reloaded in an onResume()
or onCreate()
(or actually on any lifecycle call). This may or may not be desired behaviour. If you store it in a bundle in an InstanceState
, then it is transient and is only suitable for storing data for use in the same user ‘session’ (I use the term session loosely) but not between ‘sessions’.
It is not that one approach is better than the other, like everything, it is just important to understand what behaviour you require and to select the most appropriate approach.
Saving state is a kludge at best as far as I'm concerned. If you need to save persistent data, just use an SQLite database. Android makes it SOOO easy.
Something like this:
import java.util.Date;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class dataHelper {
private static final String DATABASE_NAME = "autoMate.db";
private static final int DATABASE_VERSION = 1;
private Context context;
private SQLiteDatabase db;
private OpenHelper oh ;
public dataHelper(Context context) {
this.context = context;
this.oh = new OpenHelper(this.context);
this.db = oh.getWritableDatabase();
}
public void close() {
db.close();
oh.close();
db = null;
oh = null;
SQLiteDatabase.releaseMemory();
}
public void setCode(String codeName, Object codeValue, String codeDataType) {
Cursor codeRow = db.rawQuery("SELECT * FROM code WHERE codeName = '"+ codeName + "'", null);
String cv = "" ;
if (codeDataType.toLowerCase().trim().equals("long") == true){
cv = String.valueOf(codeValue);
}
else if (codeDataType.toLowerCase().trim().equals("int") == true)
{
cv = String.valueOf(codeValue);
}
else if (codeDataType.toLowerCase().trim().equals("date") == true)
{
cv = String.valueOf(((Date)codeValue).getTime());
}
else if (codeDataType.toLowerCase().trim().equals("boolean") == true)
{
String.valueOf(codeValue);
}
else
{
cv = String.valueOf(codeValue);
}
if(codeRow.getCount() > 0) //exists-- update
{
db.execSQL("update code set codeValue = '" + cv +
"' where codeName = '" + codeName + "'");
}
else // does not exist, insert
{
db.execSQL("INSERT INTO code (codeName, codeValue, codeDataType) VALUES(" +
"'" + codeName + "'," +
"'" + cv + "'," +
"'" + codeDataType + "')" );
}
}
public Object getCode(String codeName, Object defaultValue){
//Check to see if it already exists
String codeValue = "";
String codeDataType = "";
boolean found = false;
Cursor codeRow = db.rawQuery("SELECT * FROM code WHERE codeName = '"+ codeName + "'", null);
if (codeRow.moveToFirst())
{
codeValue = codeRow.getString(codeRow.getColumnIndex("codeValue"));
codeDataType = codeRow.getString(codeRow.getColumnIndex("codeDataType"));
found = true;
}
if (found == false)
{
return defaultValue;
}
else if (codeDataType.toLowerCase().trim().equals("long") == true)
{
if (codeValue.equals("") == true)
{
return (long)0;
}
return Long.parseLong(codeValue);
}
else if (codeDataType.toLowerCase().trim().equals("int") == true)
{
if (codeValue.equals("") == true)
{
return (int)0;
}
return Integer.parseInt(codeValue);
}
else if (codeDataType.toLowerCase().trim().equals("date") == true)
{
if (codeValue.equals("") == true)
{
return null;
}
return new Date(Long.parseLong(codeValue));
}
else if (codeDataType.toLowerCase().trim().equals("boolean") == true)
{
if (codeValue.equals("") == true)
{
return false;
}
return Boolean.parseBoolean(codeValue);
}
else
{
return (String)codeValue;
}
}
private static class OpenHelper extends SQLiteOpenHelper {
OpenHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS code" +
"(id INTEGER PRIMARY KEY, codeName TEXT, codeValue TEXT, codeDataType TEXT)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
}
A simple call after that
dataHelper dh = new dataHelper(getBaseContext());
String status = (String) dh.getCode("appState", "safetyDisabled");
Date serviceStart = (Date) dh.getCode("serviceStartTime", null);
dh.close();
dh = null;
I think I found the answer. Let me tell what I have done in simple words:
Suppose I have two activities, activity1 and activity2 and I am navigating from activity1 to activity2 (I have done some works in activity2) and again back to activity 1 by clicking on a button in activity1. Now at this stage I wanted to go back to activity2 and I want to see my activity2 in the same condition when I last left activity2.
For the above scenario what I have done is that in the manifest I made some changes like this:
<activity android:name=".activity2"
android:alwaysRetainTaskState="true"
android:launchMode="singleInstance">
</activity>
And in the activity1 on the button click event I have done like this:
Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
intent.setClassName(this,"com.mainscreen.activity2");
startActivity(intent);
And in activity2 on button click event I have done like this:
Intent intent=new Intent();
intent.setClassName(this,"com.mainscreen.activity1");
startActivity(intent);
Now what will happen is that whatever the changes we have made in the activity2 will not be lost, and we can view activity2 in the same state as we left previously.
I believe this is the answer and this works fine for me. Correct me if I am wrong.
onSaveInstanceState()
for transient data (restored in onCreate()
/onRestoreInstanceState()
), onPause()
for persistent data (restored in onResume()
).
From Android technical resources:
onSaveInstanceState() is called by Android if the Activity is being stopped and may be killed before it is resumed! This means it should store any state necessary to re-initialize to the same condition when the Activity is restarted. It is the counterpart to the onCreate() method, and in fact the savedInstanceState Bundle passed in to onCreate() is the same Bundle that you construct as outState in the onSaveInstanceState() method.
onPause() and onResume() are also complimentary methods. onPause() is always called when the Activity ends, even if we instigated that (with a finish() call for example). We will use this to save the current note back to the database. Good practice is to release any resources that can be released during an onPause() as well, to take up less resources when in the passive state.
Really onSaveInstanceState()
is called when the Activity goes to background.
Quote from the docs: "This method is called before an activity may be killed so that when it comes back sometime in the future it can restore its state." Source
To help reduce boilerplate I use the following interface
and class
to read/write to a Bundle
for saving instance state.
First, create an interface that will be used to annotate your instance variables:
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.FIELD
})
public @interface SaveInstance {
}
Then, create a class where reflection will be used to save values to the bundle:
import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;
import java.io.Serializable;
import java.lang.reflect.Field;
/**
* Save and load fields to/from a {@link Bundle}. All fields should be annotated with {@link
* SaveInstance}.</p>
*/
public class Icicle {
private static final String TAG = "Icicle";
/**
* Find all fields with the {@link SaveInstance} annotation and add them to the {@link Bundle}.
*
* @param outState
* The bundle from {@link Activity#onSaveInstanceState(Bundle)} or {@link
* Fragment#onSaveInstanceState(Bundle)}
* @param classInstance
* The object to access the fields which have the {@link SaveInstance} annotation.
* @see #load(Bundle, Object)
*/
public static void save(Bundle outState, Object classInstance) {
save(outState, classInstance, classInstance.getClass());
}
/**
* Find all fields with the {@link SaveInstance} annotation and add them to the {@link Bundle}.
*
* @param outState
* The bundle from {@link Activity#onSaveInstanceState(Bundle)} or {@link
* Fragment#onSaveInstanceState(Bundle)}
* @param classInstance
* The object to access the fields which have the {@link SaveInstance} annotation.
* @param baseClass
* Base class, used to get all superclasses of the instance.
* @see #load(Bundle, Object, Class)
*/
public static void save(Bundle outState, Object classInstance, Class<?> baseClass) {
if (outState == null) {
return;
}
Class<?> clazz = classInstance.getClass();
while (baseClass.isAssignableFrom(clazz)) {
String className = clazz.getName();
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(SaveInstance.class)) {
field.setAccessible(true);
String key = className + "#" + field.getName();
try {
Object value = field.get(classInstance);
if (value instanceof Parcelable) {
outState.putParcelable(key, (Parcelable) value);
} else if (value instanceof Serializable) {
outState.putSerializable(key, (Serializable) value);
}
} catch (Throwable t) {
Log.d(TAG, "The field '" + key + "' was not added to the bundle");
}
}
}
clazz = clazz.getSuperclass();
}
}
/**
* Load all saved fields that have the {@link SaveInstance} annotation.
*
* @param savedInstanceState
* The saved-instance {@link Bundle} from an {@link Activity} or {@link Fragment}.
* @param classInstance
* The object to access the fields which have the {@link SaveInstance} annotation.
* @see #save(Bundle, Object)
*/
public static void load(Bundle savedInstanceState, Object classInstance) {
load(savedInstanceState, classInstance, classInstance.getClass());
}
/**
* Load all saved fields that have the {@link SaveInstance} annotation.
*
* @param savedInstanceState
* The saved-instance {@link Bundle} from an {@link Activity} or {@link Fragment}.
* @param classInstance
* The object to access the fields which have the {@link SaveInstance} annotation.
* @param baseClass
* Base class, used to get all superclasses of the instance.
* @see #save(Bundle, Object, Class)
*/
public static void load(Bundle savedInstanceState, Object classInstance, Class<?> baseClass) {
if (savedInstanceState == null) {
return;
}
Class<?> clazz = classInstance.getClass();
while (baseClass.isAssignableFrom(clazz)) {
String className = clazz.getName();
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(SaveInstance.class)) {
String key = className + "#" + field.getName();
field.setAccessible(true);
try {
Object fieldVal = savedInstanceState.get(key);
if (fieldVal != null) {
field.set(classInstance, fieldVal);
}
} catch (Throwable t) {
Log.d(TAG, "The field '" + key + "' was not retrieved from the bundle");
}
}
}
clazz = clazz.getSuperclass();
}
}
}
public class MainActivity extends Activity {
@SaveInstance
private String foo;
@SaveInstance
private int bar;
@SaveInstance
private Intent baz;
@SaveInstance
private boolean qux;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Icicle.load(savedInstanceState, this);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Icicle.save(outState, this);
}
}
Note: This code was adapted from a library project named AndroidAutowire which is licensed under the MIT license.
Meanwhile I do in general no more use
Bundle savedInstanceState & Co
The life cycle is for most activities too complicated and not necessary.
And Google states itself, it is NOT even reliable.
My way is to save any changes immediately in the preferences:
SharedPreferences p;
p.edit().put(..).commit()
In some way SharedPreferences work similar like Bundles. And naturally and at first such values have to be read from preferences.
In the case of complex data you may use SQLite instead of using preferences.
When applying this concept, the activity just continues to use the last saved state, regardless of whether it was an initial open with reboots in between or a reopen due to the back stack.
To answer the original question directly. savedInstancestate is null because your Activity is never being re-created.
Your Activity will only be re-created with a state bundle when:
Android will destroy background activities when under memory pressure or after they've been in the background for an extended period of time.
When testing your hello world example there are a few ways to leave and return to the Activity.
In most cases if you're just pressing home and then launching the app again the activity won't need to be re-created. It already exists in memory so onCreate() won't be called.
There is an option under Settings -> Developer Options called "Don't keep activities". When it's enabled Android will always destroy activities and recreate them when they're backgrounded. This is a great option to leave enabled when developing because it simulates the worst case scenario. ( A low memory device recycling your activities all the time ).
The other answers are valuable in that they teach you the correct ways to store state but I didn't feel they really answered WHY your code wasn't working in the way you expected.
The onSaveInstanceState(bundle)
and onRestoreInstanceState(bundle)
methods are useful for data persistence merely while rotating the screen (orientation change).
They are not even good while switching between applications (since the onSaveInstanceState()
method is called but onCreate(bundle)
and onRestoreInstanceState(bundle)
is not invoked again.
For more persistence use shared preferences. read this article
Although the accepted answer is correct, there is a faster and easier method to save the Activity state on Android using a library called Icepick. Icepick is an annotation processor that takes care of all the boilerplate code used in saving and restoring state for you.
Doing something like this with Icepick:
class MainActivity extends Activity {
@State String username; // These will be automatically saved and restored
@State String password;
@State int age;
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Icepick.restoreInstanceState(this, savedInstanceState);
}
@Override public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Icepick.saveInstanceState(this, outState);
}
}
Is the same as doing this:
class MainActivity extends Activity {
String username;
String password;
int age;
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putString("MyString", username);
savedInstanceState.putString("MyPassword", password);
savedInstanceState.putInt("MyAge", age);
/* remember you would need to actually initialize these variables before putting it in the
Bundle */
}
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
username = savedInstanceState.getString("MyString");
password = savedInstanceState.getString("MyPassword");
age = savedInstanceState.getInt("MyAge");
}
}
Icepick will work with any object that saves its state with a Bundle
.
My problem was that I needed persistence only during the application lifetime (i.e. a single execution including starting other sub-activities within the same app and rotating the device etc). I tried various combinations of the above answers but did not get what I wanted in all situations. In the end what worked for me was to obtain a reference to the savedInstanceState during onCreate:
mySavedInstanceState=savedInstanceState;
and use that to obtain the contents of my variable when I needed it, along the lines of:
if (mySavedInstanceState !=null) {
boolean myVariable = mySavedInstanceState.getBoolean("MyVariable");
}
I use onSaveInstanceState
and onRestoreInstanceState
as suggested above but I guess i could also or alternatively use my method to save the variable when it changes (e.g. using putBoolean
)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With