How can I save an activity state using the save instance state?

Asked 2023-09-20 20:23:43 View 577,695

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 is savedInstanceState == null and when is it not null ? - anyone
  • You're explicitly destroying your activity by - as you said, navigating away from it, such as by pressing back. Actually, the scenario in which this 'savedInstanceState' is used, is when Android destroys your activity for recreation. For intance: If you change the language of your phone while the activity was running (and so different resources from your project need to be loaded). Another very common scenario is when you rotate your phone to the side so that the activity is recreated and displayed in landscape. - anyone
  • To get the second message, enable "Don't keep activities" in dev options. Press a home button and get back from recents. - anyone
  • this is quite helpful developer.android.com/training/basics/activity-lifecycle/… - anyone
  • you can do it with : onSaveInstanceState(Bundle savedInstanceState) - anyone

Answers

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

Answered   2023-09-20 20:23:43

  • Any chance this works on the phone, but not in the emulator? I cannot seem to get a non-null savedInstanceState. - anyone
  • CAREFUL: you need to call super.onSaveInstanceState(savedInstanceState) before adding your values to the Bundle, or they will get wiped out on that call (Droid X Android 2.2). - anyone
  • Careful: the official documentation states, that you should save important information within the onPause-Method because the onsaveinstance-method is not part of the android lifecycle. developer.android.com/reference/android/app/Activity.html - anyone
  • That fact effectively makes onSaveInstanceState almost useless except just for case of screen orientation changes. In almost all other cases, you can never rely on it and will need to manually save your UI state somewhere else. Or preventing your app from being killed by overriding BACK button behavior. I don't understand why they even implemented it like this in the first place. Totally unintuitive. And you can't have that Bundle the system give you to save things into except in this very particular method. - anyone
  • Note that saving / restoring UI state to / from the Bundle is automatically taken care of for Views that have been assigned ids. From the onSaveInstanceState docs: "The default implementation takes care of most of the UI per-instance state for you by calling onSaveInstanceState() on each view in the hierarchy that has an id, and by saving the id of the currently focused view (all of which is restored by the default implementation of onRestoreInstanceState(Bundle))" - anyone

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.

Answered   2023-09-20 20:23:43

  • When is savedInstanceState == null and when is it not null ? - anyone
  • savedInstanceState is null when the system is creating a new instance of your Activity and not null when it's restoring. - anyone
  • ... which raises the question of when does the system need to create a new instance of Activity. Some ways of exiting an app don't create a bundle, so a new instance must be created. This is the fundamental problem; it means one cannot rely on existence of bundle, and must do some alternative means of persistent storage. The benefit of onSave/onRestoreInstanceState is that it is a mechanism the system can do abruptly, without consuming much system resources. So it is good to support that, as well as have persistent storage for more graceful exit from app. - anyone

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 of onSaveInstanceState(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) or onRestoreInstanceState(Bundle).

Answered   2023-09-20 20:23:43

  • Just to nitpick: it's not unsafe either. This just depends on what you want to preserve and for how long, which @Bernard isn't entirely clear on in his original question. InstanceState is perfect for preserving the current UI state (data entered into controls, current positions in lists and so forth), whereas Pause/Resume is the only possibility for long term persistent storage. - anyone
  • This should be downvoted. It's not safe to use on(Save|Restore)InstanceState like lifecycle methods (i.e. do anything else in them than save / restore the state). They're perfectly good for saving / restoring state. Also, how do you want to save / restore state in onPause and onResume? You don't get Bundles in those methods that you can use, so you'd have to employ some other state-saving, in databases, files, etc. which is stupid. - anyone
  • We should not down vote this person at least he made efforts to go through the documentation and I think we people are here for actually building a knowledgeable community and help each other not to DOWN VOTE. so 1 vote up for the effort and I'll request you people not to down vote rather vote up or don't vote.... this person clear the confusion that one would like to have when going through documentation. 1 vote up :) - anyone
  • I dont think this answer deserves a downvote. Atleast he made an effort to answer and had quoted a section from doco. - anyone
  • This answer is absolutely correct and deserves UP vote, not down! Let me clarify difference between states for those guys who don't see it. A GUI state, like selected radio-buttons and some text in the input field, is much less important than the data state, like records added to a list displayed in a ListView. The latter must be stored to the database in onPause because it's the only guarantied call. If you put it in onSaveInstanceState instead, you risk loosing data if that is not called. But if the radio-button selection is not saved for the same reason - it's not a big deal. - anyone

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:

Store local variable/UI control data for application lifetime (i.e. temporarily) using an instance state bundle

[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);
}

Store local variable/UI control data between application instances (i.e. permanently) using shared preferences

[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();
}

Keeping object instances alive in memory between activities within application lifetime using a retained non-configuration instance

[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();
}

Answered   2023-09-20 20:23:43

  • @MartinBelcher-Eigo Article says about data in SharedPreferences that "This data is written to the database on the device.." I believe that the data is stored in a file in the app's directory of the file system. - anyone
  • @Tom SharefPrefs data is written to xml file. Is xml a kind of database? I'd say it is ;) - anyone

This is a classic 'gotcha' of Android development. There are two issues here:

  • There is a subtle Android Framework bug which greatly complicates application stack management during development, at least on legacy versions (not entirely sure if/when/how it was fixed). I'll discuss this bug below.
  • The 'normal' or intended way to manage this issue is, itself, rather complicated with the duality of onPause/onResume and onSaveInstanceState/onRestoreInstanceState

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:

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;
    }
}

Answered   2023-09-20 20:23:43

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.

Answered   2023-09-20 20:23:43

  • Sorry, that's not quite correct. onSaveInstanceState gets called before the activity needs to be re-made. i.e. every time the user rotates the device. It is meant for storing transient view states. When android forces the application to close, onSaveInstanceState is actually NOT called (which is why it's unsafe for storing important application data). onPause, however is guaranteed to be called before the activity is killed, so it should be used to store permanent info in preferences or Squlite. Right answer, wrong reasons. - anyone

Both methods are useful and valid and both are best suited for different scenarios:

  1. The user terminates the application and re-opens it at a later date, but the application needs to reload data from the last session – this requires a persistent storage approach such as using SQLite.
  2. The user switches application and then comes back to the original and wants to pick up where they left off - save and restore bundle data (such as application state data) in 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.

Answered   2023-09-20 20:23:43

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;

Answered   2023-09-20 20:23:43

  • Because it takes too long to load an SQLite database, considering that this is on the critical path to showing the user the app's UI. I have not actually timed it, so I'm happy to be corrected, but surely loading and opening a database file won't be fast? - anyone
  • Thank you so much for providing a solution a newbie can cut and paste into their app and use right away! @Tom As far as speed goes it takes about seven seconds to store 1000 pairs, but you can do it in an AsyncTask. However, you need to add a finally { cursor.close() } or it will crash from memory leak while doing this. - anyone
  • I came across this and while it seems neat I'm hesitant to try utilizing this on Google Glass, which is the device I'm working on/with lately. - anyone

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.

Answered   2023-09-20 20:23:43

  • @bagusflyer care to be more specific??? Your comment isn't helpful and no one can can help you based on that. - anyone
  • This is an answer to a different situation: two activities within the same app. OP is about leaving the app (e.g. home button, or other means to switch to a different app). - anyone
  • This is exactly the answer I was looking for! - anyone

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.

Answered   2023-09-20 20:23:43

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

Answered   2023-09-20 20:23:43

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();
        }
    }

}

Example usage:

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.

Answered   2023-09-20 20:23:43

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.

Answered   2023-09-20 20:23:43

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:

  • Configuration changes such as changing the orientation or phone language which may requires a new activity instance to be created.
  • You return to the app from the background after the OS has destroyed the activity.

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.

  • When you press the back button the Activity is finished. Re-launching the app is a brand new instance. You aren't resuming from the background at all.
  • When you press the home button or use the task switcher the Activity will go into the background. When navigating back to the application onCreate will only be called if the Activity had to be destroyed.

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.

Answered   2023-09-20 20:23:43

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

Answered   2023-09-20 20:23:43

  • In your case onCreate and onRestoreInstanceState are not being called because the Activity is not destroyed at all when you switch apps, so there is no need to restore anything. Android calls onSaveInstanceState just in case the Activity gets destroyed later (which happens with 100% certainty when rotating the screen because the entire device configuration has changed and the Activity must be re-created from scratch). - anyone

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 onSaveInstanceStateand 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)

Answered   2023-09-20 20:23:43

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.

Answered   2023-09-20 20:23:43

When an activity is created it's onCreate() method is called.

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

savedInstanceState is an object of Bundle class which is null for the first time, but it contains values when it is recreated. To save Activity's state you have to override onSaveInstanceState().

   @Override
    protected void onSaveInstanceState(Bundle outState) {
      outState.putString("key","Welcome Back")
        super.onSaveInstanceState(outState);       //save state
    }

put your values in "outState" Bundle object like outState.putString("key","Welcome Back") and save by calling super. When activity will be destroyed it's state get saved in Bundle object and can be restored after recreation in onCreate() or onRestoreInstanceState(). Bundle received in onCreate() and onRestoreInstanceState() are same.

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

          //restore activity's state
         if(savedInstanceState!=null){
          String reStoredString=savedInstanceState.getString("key");
            }
    }

or

  //restores activity's saved state
 @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
      String restoredMessage=savedInstanceState.getString("key");
    }

Answered   2023-09-20 20:23:43

There are basically two ways to implement this change.

  1. using onSaveInstanceState() and onRestoreInstanceState().
  2. In manifest android:configChanges="orientation|screenSize".

I really do not recommend to use second method. Since in one of my experience it was causing half of the device screen black while rotating from portrait to landscape and vice versa.

Using first method mentioned above , we can persist data when orientation is changed or any config change happens. I know a way in which you can store any type of data inside savedInstance state object.

Example: Consider a case if you want to persist Json object. create a model class with getters and setters .

class MyModel extends Serializable{
JSONObject obj;

setJsonObject(JsonObject obj)
{
this.obj=obj;
}

JSONObject getJsonObject()
return this.obj;
} 
}

Now in your activity in onCreate and onSaveInstanceState method do the following. It will look something like this:

@override
onCreate(Bundle savedInstaceState){
MyModel data= (MyModel)savedInstaceState.getSerializable("yourkey")
JSONObject obj=data.getJsonObject();
//Here you have retained JSONObject and can use.
}


@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//Obj is some json object 
MyModel dataToSave= new MyModel();
dataToSave.setJsonObject(obj);
oustate.putSerializable("yourkey",dataToSave); 

}

Answered   2023-09-20 20:23:43

Here is a comment from Steve Moseley's answer (by ToolmakerSteve) that puts things into perspective (in the whole onSaveInstanceState vs onPause, east cost vs west cost saga)

@VVK - I partially disagree. Some ways of exiting an app don't trigger onSaveInstanceState (oSIS). This limits the usefulness of oSIS. Its worth supporting, for minimal OS resources, but if an app wants to return the user to the state they were in, no matter how the app was exited, it is necessary to use a persistent storage approach instead. I use onCreate to check for bundle, and if it is missing, then check persistent storage. This centralizes the decision making. I can recover from a crash, or back button exit or custom menu item Exit, or get back to screen user was on many days later. – ToolmakerSteve Sep 19 '15 at 10:38

Answered   2023-09-20 20:23:43

Kotlin code:

save:

override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState.apply {
        putInt("intKey", 1)
        putString("stringKey", "String Value")
        putParcelable("parcelableKey", parcelableObject)
    })
}

and then in onCreate() or onRestoreInstanceState()

    val restoredInt = savedInstanceState?.getInt("intKey") ?: 1 //default int
    val restoredString = savedInstanceState?.getString("stringKey") ?: "default string"
    val restoredParcelable = savedInstanceState?.getParcelable<ParcelableClass>("parcelableKey") ?: ParcelableClass() //default parcelable

Add default values if you don't want to have Optionals

Answered   2023-09-20 20:23:43

To get activity state data stored in onCreate(), first you have to save data in savedInstanceState by overriding SaveInstanceState(Bundle savedInstanceState) method.

When activity destroy SaveInstanceState(Bundle savedInstanceState) method gets called and there you save data you want to save. And you get same in onCreate() when activity restart.(savedInstanceState wont be null since you have saved some data in it before activity get destroyed)

Answered   2023-09-20 20:23:43

Kotlin

You must override onSaveInstanceState and onRestoreInstanceState to store and retrieve your variables you want to be persistent

Life cycle graph

Store variables

public override fun onSaveInstanceState(savedInstanceState: Bundle) {
    super.onSaveInstanceState(savedInstanceState)

    // prepare variables here
    savedInstanceState.putInt("kInt", 10)
    savedInstanceState.putBoolean("kBool", true)
    savedInstanceState.putDouble("kDouble", 4.5)
    savedInstanceState.putString("kString", "Hello Kotlin")
}

Retrieve variables

public override fun onRestoreInstanceState(savedInstanceState: Bundle) {
    super.onRestoreInstanceState(savedInstanceState)

    val myInt = savedInstanceState.getInt("kInt")
    val myBoolean = savedInstanceState.getBoolean("kBool")
    val myDouble = savedInstanceState.getDouble("kDouble")
    val myString = savedInstanceState.getString("kString")
    // use variables here
}

Answered   2023-09-20 20:23:43

Not sure if my solution is frowned upon or not, but I use a bound service to persist ViewModel state. Whether you store it in memory in the service or persist and retrieve it from a SQLite database depends on your requirements. This is what services of any flavor do, they provide services such as maintaining application state and abstract common business logic.

Because of memory and processing constraints inherent on mobile devices, I treat Android views in a similar way to a web page. The page does not maintain state, it is purely a presentation layer component whose only purpose is to present application state and accept user input. Recent trends in web app architecture employ the use of the age-old Model, View, Controller (MVC) pattern, where the page is the View, domain data is the model, and the controller sits behind a web service. The same pattern can be employed in Android with the View being, well ... the View, the model is your domain data, and the Controller is implemented as an Android bound service. Whenever you want a view to interact with the controller, bind to it on start/resume and unbind on stop/pause.

This approach gives you the added bonus of enforcing the Separation of Concern design principle in that all of you application business logic can be moved into your service which reduces duplicated logic across multiple views and allows the view to enforce another important design principle, Single Responsibility.

Answered   2023-09-20 20:23:43

Simple quick to solve this problem is using IcePick

First, setup the library in app/build.gradle

repositories {
  maven {url "https://clojars.org/repo/"}
}
dependencies {
  compile 'frankiesardo:icepick:3.2.0'
  provided 'frankiesardo:icepick-processor:3.2.0'
}

Now, let's check this example below how to save state in Activity

public class ExampleActivity extends Activity {
  @State String username; // This will be automatically saved and restored

  @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);
  }
}

It works for Activities, Fragments or any object that needs to serialize its state on a Bundle (e.g. mortar's ViewPresenters)

Icepick can also generate the instance state code for custom Views:

class CustomView extends View {
  @State int selectedPosition; // This will be automatically saved and restored

  @Override public Parcelable onSaveInstanceState() {
    return Icepick.saveInstanceState(this, super.onSaveInstanceState());
  }

  @Override public void onRestoreInstanceState(Parcelable state) {
    super.onRestoreInstanceState(Icepick.restoreInstanceState(this, state));
  }

  // You can put the calls to Icepick into a BaseCustomView and inherit from it
  // All Views extending this CustomView automatically have state saved/restored
}

Answered   2023-09-20 20:23:43

  • @ralphspoon yes, it works for Fragment and Custom View. Please check the example code. I edited my answer. I suggest you to go official docs here github.com/frankiesardo/icepick to find more code sample. - anyone
  • @ChetanMehra you mean custom view class, right? If it is custom view, we can override onSaveInstanceState and onRestoreInstanceState like above example of CustomView. - anyone
  • I mean class object inside view class for example : class CustomView extends View{ @State ClassA a;} or class CustomView extends View { @ State Inner class{}} - anyone
  • @THANNPhearum Should i ask it as another question? - anyone
  • I see. If so, your ClassA should be Parcelable. As it mentioned that It works for Activities, Fragments or any object that needs to serialize its state on a Bundle - anyone

Now Android provides ViewModels for saving state, you should try to use that instead of saveInstanceState.

Answered   2023-09-20 20:23:43

  • This is not true. From the documentation: "Unlike saved instance state, ViewModels are destroyed during a system-initiated process death. This is why you should use ViewModel objects in combination with onSaveInstanceState() (or some other disk persistence), stashing identifiers in savedInstanceState to help view models reload the data after system death." - anyone
  • Just ran into this with permissions changing in the background. - anyone
  • I agree, from the doc "if you need to handle system-initiated process death, you may want to use onSaveInstanceState() as backup." - anyone

There is a way to make Android save the states without implementing any method. Just add this line to your Manifest in Activity declaration:

android:configChanges="orientation|screenSize"

It should look like this:

<activity
    android:name=".activities.MyActivity"
    android:configChanges="orientation|screenSize">
</activity>

Here you can find more information about this property.

It's recommended to let Android handle this for you than the manually handling.

Answered   2023-09-20 20:23:43

  • This has nothing to do with saving the state ,you just giving up orientation changes, keep in mind you app can be restarted and paused and resumed at any time for different events - anyone
  • This answer is for those that wants to save the state when orientation changed and wants to avoid comprehension and implementation of a complex way - anyone
  • fair enough I see your point ,I think most people who struggle to save state are using fragments because activities actually save stat of UI components as long as they have an ID ,but fragments are more special ,I used fragments once but I will never use them again the save instance stat was a pain to deal with - anyone

Kotlin Solution: For custom class save in onSaveInstanceState you can be converted your class to JSON string and restore it with Gson convertion and for single String, Double, Int, Long value save and restore as following. The following example is for Fragment and Activity:

For Activity:

For put data in saveInstanceState:

override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)

        //for custom class-----
        val gson = Gson()
        val json = gson.toJson(your_custom_class)
        outState.putString("CUSTOM_CLASS", json)

        //for single value------
        outState.putString("MyString", stringValue)
        outState.putBoolean("MyBoolean", true)
        outState.putDouble("myDouble", doubleValue)
        outState.putInt("MyInt", intValue)
    }

Restore data:

 override fun onRestoreInstanceState(savedInstanceState: Bundle) {
    super.onRestoreInstanceState(savedInstanceState)

    //for custom class restore
    val json = savedInstanceState?.getString("CUSTOM_CLASS")
    if (!json!!.isEmpty()) {
        val gson = Gson()
        testBundle = gson.fromJson(json, Session::class.java)
    }

  //for single value restore

   val myBoolean: Boolean = savedInstanceState?.getBoolean("MyBoolean")
   val myDouble: Double = savedInstanceState?.getDouble("myDouble")
   val myInt: Int = savedInstanceState?.getInt("MyInt")
   val myString: String = savedInstanceState?.getString("MyString")
 }

You can restore it on Activity onCreate also.

For fragment:

For put class in saveInstanceState:

 override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        val gson = Gson()
        val json = gson.toJson(customClass)
        outState.putString("CUSTOM_CLASS", json)
    }

Restore data:

 override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)

        //for custom class restore
        if (savedInstanceState != null) {
            val json = savedInstanceState.getString("CUSTOM_CLASS")
            if (!json!!.isEmpty()) {
                val gson = Gson()
                val customClass: CustomClass = gson.fromJson(json, CustomClass::class.java)
            }
        }

      // for single value restore
      val myBoolean: Boolean = savedInstanceState.getBoolean("MyBoolean")
      val myDouble: Double = savedInstanceState.getDouble("myDouble")
      val myInt: Int = savedInstanceState.getInt("MyInt")
      val myString: String = savedInstanceState.getString("MyString")
    }

Answered   2023-09-20 20:23:43

using Android ViewModel & SavedStateHandle to persist the serializable data

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
        binding.setViewModel(new ViewModelProvider(this).get(ViewModel.class));
        binding.setLifecycleOwner(this);
        setContentView(binding.getRoot());
    }

    public static class ViewModel extends AndroidViewModel {

        //This field SURVIVE the background process reclaim/killing & the configuration change
        public final SavedStateHandle savedStateHandle;

        //This field NOT SURVIVE the background process reclaim/killing but SURVIVE the configuration change
        public final MutableLiveData<String> inputText2 = new MutableLiveData<>();


        public ViewModel(@NonNull Application application, SavedStateHandle savedStateHandle) {
            super(application);
            this.savedStateHandle = savedStateHandle;
        }
    }
}

in layout file

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="viewModel"
            type="com.xxx.viewmodelsavedstatetest.MainActivity.ViewModel" />
    </data>

    <LinearLayout xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity">


        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:autofillHints=""
            android:hint="This field SURVIVE the background process reclaim/killing &amp; the configuration change"
            android:text='@={(String)viewModel.savedStateHandle.getLiveData("activity_main/inputText", "")}' />

        <SeekBar
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:max="100"
            android:progress='@={(Integer)viewModel.savedStateHandle.getLiveData("activity_main/progress", 50)}' />

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="This field SURVIVE the background process reclaim/killing &amp; the configuration change"
            android:text='@={(String)viewModel.savedStateHandle.getLiveData("activity_main/inputText", "")}' />

        <SeekBar
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:max="100"
            android:progress='@={(Integer)viewModel.savedStateHandle.getLiveData("activity_main/progress", 50)}' />

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="This field NOT SURVIVE the background process reclaim/killing but SURVIVE the configuration change"
            android:text='@={viewModel.inputText2}' />

    </LinearLayout>
</layout>

Test:

1. start the test activity
2. press home key to go home
3. adb shell kill <the test activity process>
4. open recent app list and restart the test activity

Answered   2023-09-20 20:23:43

What to save and what not to?

Ever wondered why the text in the EditText gets saved automatically while an orientation change? Well, this answer is for you.

When an instance of an Activity gets destroyed and the System recreates a new instance (for example, configuration change). It tries to recreate it using a set of saved data of old Activity State (instance state).

Instance state is a collection of key-value pairs stored in a Bundle object.

By default System saves the View objects in the Bundle for example.

  • Text in EditText
  • Scroll position in a ListView, etc.

If you need another variable to be saved as a part of instance state you should OVERRIDE onSavedInstanceState(Bundle savedinstaneState) method.

For example, int currentScore in a GameActivity

More detail about the onSavedInstanceState(Bundle savedinstaneState) while saving data

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    // Save the user's current game state
    savedInstanceState.putInt(STATE_SCORE, mCurrentScore);

    // Always call the superclass so it can save the view hierarchy state
    super.onSaveInstanceState(savedInstanceState);
}

So by mistake if you forget to call super.onSaveInstanceState(savedInstanceState);the default behavior will not work ie Text in EditText will not save.

Which to choose for restoring Activity state?

 onCreate(Bundle savedInstanceState)

OR

onRestoreInstanceState(Bundle savedInstanceState)

Both methods get the same Bundle object, so it does not really matter where you write your restoring logic. The only difference is that in onCreate(Bundle savedInstanceState) method you will have to give a null check while it is not needed in the latter case. Other answers have already code snippets. You can refer them.

More detail about the onRestoreInstanceState(Bundle savedinstaneState)

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
    // Always call the superclass so it can restore the view hierarchy
    super.onRestoreInstanceState(savedInstanceState);

    // Restore state members from the saved instance
    mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
}

Always call super.onRestoreInstanceState(savedInstanceState); so that System restore the View hierarchy by default

Bonus

The onSaveInstanceState(Bundle savedInstanceState) is invoked by the system only when the user intends to come back to the Activity. For example, you are using App X and suddenly you get a call. You move to the caller app and come back to the app X. In this case the onSaveInstanceState(Bundle savedInstanceState) method will be invoked.

But consider this if a user presses the back button. It is assumed that the user does not intend to come back to the Activity, hence in this case onSaveInstanceState(Bundle savedInstanceState) will not be invoked by the system. Point being you should consider all the scenarios while saving data.

Relevant links:

Demo on default behavior
Android Official Documentation.

Answered   2023-09-20 20:23:43