Android - TabScreen orientation causes all my screens to be reset? [duplicate] - android
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.
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
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).
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();
}
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:
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:
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.
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.
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();
}
}
}
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.
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:
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.
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
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)
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.
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");
}
There are basically two ways to implement this change.
using onSaveInstanceState() and onRestoreInstanceState().
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);
}
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
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
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)
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
}
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.
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
}
Now Android provides ViewModels for saving state, you should try to use that instead of saveInstanceState.
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.
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")
}
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 & 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 & 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
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.
Related
Android - Saved Activity/Fragment State Never Clears
I have an Android app, composed of Fragments, that I have saving state correctly. The problem is it works a bit too well ;-). I'll enter some input into a couple of EditText elements that I then save via SharedPrefs in the "onSaveInstanceState()" method, and then hit the "Task Manager" or "Switch App" button on the phone (there's two overlapping rectangles as the icon) and swipe left to close my application. If I then go to the App Drawer and re-run the application, that saved input will still be there. I am clearing the saved instance state in the "onDestroy()" method too, but apparently that is not being called when "closing" the app from that Task Manager (confirmed via logging). Any suggestions here? Other apps I have do not exhibit this behavior. I'd like for the saved input to be cleared when a user closes the app via Task Manager as well as probably after a set amount of time. Any ideas of what the standard practice for state handling is here? I tested some apps I have and noticed the default Contacts app actually saves a new contact if you start a new one and switch to another app before explicitly saving. I guess I could do this but I'd rather not. Below is some relevant code for a particular Fragment; thank you very much in advance for any assistance. #Override public void onSaveInstanceState(Bundle savedInstanceState) { super.onSaveInstanceState(savedInstanceState); Log.v(Tag, "onSaveInstanceState()"); saveInstanceState(); } #Override public void onResume() { super.onResume(); Log.v(Tag, "onResume()"); restoreInstanceState(); } #Override public void onDestroy() { super.onDestroy(); Log.v(Tag, "onDestroy()"); clearInstanceState(); } private void saveInstanceState() { Log.v(Tag, "saveInstanceState()"); // get entered data String name = mTxtName.getText().toString(); String notes = mTxtNotes.getText().toString(); // save data in Shared Prefs PreferenceManager.getDefaultSharedPreferences(mContext) .edit() .putInt(KeyAmmunitionId, mAmmunitionId) .putString(KeyName, name) .putString(KeyNotes, notes) .putString(StringUtils.CurrentFragmentKey, Tag) .commit(); } private void restoreInstanceState() { Log.v(Tag, "restoreInstanceState()"); mTxtName = (EditText)getActivity().findViewById(R.id.frag_manage_ammunition_txtName); mTxtNotes = (EditText)getActivity().findViewById(R.id.frag_manage_ammunition_txtNotes); if (PreferenceManager.getDefaultSharedPreferences(mContext).contains(KeyName)) { String ammunitionName = PreferenceManager.getDefaultSharedPreferences(mContext).getString(KeyName, StringUtils.EMPTY_STRING); mTxtName.setText(ammunitionName); } if (PreferenceManager.getDefaultSharedPreferences(mContext).contains(KeyNotes)) { String ammunitionNotes = PreferenceManager.getDefaultSharedPreferences(mContext).getString(KeyNotes, StringUtils.EMPTY_STRING); mTxtNotes.setText(ammunitionNotes); } } private void clearInstanceState() { Log.v(Tag, "clearInstanceState()"); PreferenceManager.getDefaultSharedPreferences(mContext) .edit() .remove(KeyAmmunitionId) .remove(KeyName) .remove(KeyNotes) .commit(); }
instead of using SharedPrefs, I've found that an internal headless fragment that retains its state is much easier to implement/cleaner. Inside your fragment class you have this class.... /** * "Headless" Fragment that retains state information between * configuration changes. */ public class RetainedFragment extends Fragment { /** * internal storage to be kept put here */ // assuming the only 'view' in the parent fragment is this editText with some string value. String editTextValue; public RetainedFragment(){ editTextValue = ""; //init values on first time in. } /** * Hook method called when a new instance of Fragment is * created. * * #param savedInstanceState * object that contains saved state information. */ #Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Ensure the data survives runtime configuration changes. setRetainInstance(true); } // have getter/setter methods Then in your outer fagment just 'create' the UI based off the values stored in the inner headless fragment. OR... You can use the RetainedFragmentManager that we figured out. Its a bit wonky and can be a bit confusing to figure out at first, but its a headless fragment that allows you to store java objects in hashmap-like manner, and they will persist across configuration changes, but won't exist if your app is fully closed, etc. https://github.com/douglascraigschmidt/POSA-15/blob/master/ex/ImageDownloads/src/vandy/mooc/common/RetainedFragmentManager.java
Retaining Activity state [duplicate]
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.
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 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).
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(); }
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: 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: 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. 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.
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(); } } } 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.
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: 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.
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
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)
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.
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"); }
There are basically two ways to implement this change. using onSaveInstanceState() and onRestoreInstanceState(). 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); }
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
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
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)
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 }
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.
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 }
Now Android provides ViewModels for saving state, you should try to use that instead of saveInstanceState.
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.
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") }
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 & 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 & 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
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.
Simple persisting doesn't work
private static final String KEY = "qaz"; private String aString; #Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView) findViewById(R.id.txt); final String saved; if (savedInstanceState != null) { saved = savedInstanceState.getString(KEY); report("[" + saved + "]"); } else { saved = null; report("[NULL]"); } if (aString==null && saved!=null) { aString = saved; report("A"); } } #Override public void onSaveInstanceState(Bundle savedInstanceState) { report("s"); savedInstanceState.putString(KEY, aString); super.onSaveInstanceState(savedInstanceState); } #Override protected void onPause() { report("p"); super.onPause(); } #Override public void onBackPressed() { report("b"); super.onBackPressed(); } #Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_main, menu); return true; } private void report(String s) { aString += " " + s; Log.v("report", aString); textView.setText(aString); }
onSaveInstance is intended to persist a value from one instance of your activity to the next, for example when the activity is destroyed and recreated on an orientation change. Just remember that when your activity is recreated, it is an entirely new instance of the class you've defined to extend the Activity class and therefore, all local fields (variables) will be re-initialised. As you've already discovered, SharedPreferences are a good way to persist a value beyond the Activity lifecycle and of course, there are others. The values saved in onSaveInstance and retrieved in onCreate are intended for you to be able to initialise a new instance of your activity to the same state it was before it was destroyed. Imagine for example that you have a messaging application and your user has already entered several lines of text. They then accidentally flip the orientation (we've all done that right?) then flip it back. How frustrated would they be to see that their text had disappeared?! So a good developer would save the current content of the TextView in onSaveInstance and retrieve it in onCreate. If any value is retrieved, it is passed to the setText() method so that the user can continue from where they were. Take a look here. Learning the Activity life cycle, how to control it and how to pass values between Activities (either 2 different Activities or 2 instances of the same Activity) is one of the keys to unlocking the Android kingdom. http://developer.android.com/reference/android/app/Activity.html#ActivityLifecycle The key point is to understand when each callback is called and, even more importantly, what may or may not be called by Android. For example, when your app is in the background,there are NO guarantees that your activity will be called again if Android decides to kill your app. Good luck.
Android - persisting activity instance
I am using instances of two activies (A and B) among my application. Now I am facing problem of persisting each of them. When I use sharedpreferences, I can get only to persisting A.class and B.class with SharedPreferences, but when I use instance A again. It's persistent state in SharedPreferences is overriden. I think, I should use Bundle wtih onSavedInstanceState and onRestoredInstanceState. But how to pass saved Bundle into onCreate()? Goal is to be able to persist activity instances. Thanks
When launching activity A. You can use intent.putExtra( String name, Bundle value ) to pass the bundle to your activity and then in Activity A's onCreate use getIntent.getBundleExtra( String name ) to get your bundle again.
You need to consider what state you need to persist. For example; which checkboxes have been ticked? what level is the user on? what is the position of their cursor As an example, I just finished an application where a user can view different levels of flashcards. I care about; which level of flashcard they are looking at (maybe they're at level 2) which card are they looking at (maybe they're on the 4th card) Here's the code. // a variable to store the level private final static String CARD_LEVEL_STATE = "currentCardLevel"; // a variable to store the current card private final static String CURRENT_CARD_STATE = "currentCardNumber"; #Override protected void onSaveInstanceState(Bundle outState) { // save the level outState.putInt(CARD_LEVEL_STATE, flashCardList.currentLevel()); // save the current card outState.putInt(CURRENT_CARD_STATE, flashCardList.currentCardNumber()); // do the default stuff super.onSaveInstanceState(outState); } #Override protected void onRestoreInstanceState(Bundle savedInstanceState) { // ignore if we have no saved state if (savedInstanceState != null) { // record the level if (savedInstanceState.containsKey(CARD_LEVEL_STATE)) { int currentCardLevel = savedInstanceState.getInt(CARD_LEVEL_STATE); flashCardList.setCardLevel(currentCardLevel); } // recover the current card if (savedInstanceState.containsKey(CURRENT_CARD_STATE)) { int currentCardNumber = savedInstanceState.getInt(CURRENT_CARD_STATE); flashCardList.setCurrentCardNumber(currentCardNumber); } // refresh the view refreshCard(); } super.onRestoreInstanceState(savedInstanceState); };
How to use onSavedInstanceState example please
I'm confused when it comes down to saving a state. So I know that onSaveInstanceState(Bundle) is called when the activity is about to be destroyed. But how do you store your information in it and bring it back to its original state in onCreate(Bundle savedInstanceState)? I don't understand how this bundle will restore information. It would be helpful if someone can provide an example. The Dev guide doesn't do a good job of explaining this. public class Conversation extends Activity { private ProgressDialog progDialog; int typeBar; TextView text1; EditText edit; Button respond; private String name; private String textAtView; private String savedName; public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.dorothydialog); text1 = (TextView)findViewById(R.id.dialog); edit = (EditText)findViewById(R.id.repsond); respond = (Button)findViewById(R.id.button01); if(savedInstanceState != null){ savedInstanceState.get(savedName); text1.setText(savedName); } else{ text1.setText("Hello! What is your name?"); respond.setOnClickListener(new View.OnClickListener() { #Override public void onClick(View v) { name = edit.getText().toString(); text1.setText("Nice to meet you "+ name); } }); } } #Override public void onSaveInstanceState(Bundle outState){ super.onSaveInstanceState(outState); outState.putString(savedName, name); } }
The Bundle is a container for all the information you want to save. You use the put* functions to insert data into it. Here's a short list (there are more) of put functions you can use to store data in the Bundle. putString putBoolean putByte putChar putFloat putLong putShort putParcelable (used for objects but they must implement Parcelable) In your onCreate function, this Bundle is handed back to the program. The best way to check if the application is being reloaded, or started for the first time is: if (savedInstanceState != null) { // Then the application is being reloaded } To get the data back out, use the get* functions just like the put* functions. The data is stored as a name-value pair. This is like a hashmap. You provide a key and the value, then when you want the value back, you give the key and the function gets the value. Here's a short example. #Override public void onSaveInstanceState(Bundle outState) { outState.putString("message", "This is my message to be reloaded"); super.onSaveInstanceState(outState); } #Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState != null) { String message = savedInstanceState.getString("message"); Toast.makeText(this, message, Toast.LENGTH_LONG).show(); } } Your saved message will be toasted to the screen.
One major note that all new Android developers should know is that any information in Widgets (TextView, Buttons, etc.) will be persisted automatically by Android as long as you assign an ID to them. So that means most of the UI state is taken care of without issue. Only when you need to store other data does this become an issue. From Android Docs: The only work required by you is to provide a unique ID (with the android:id attribute) for each widget you want to save its state. If a widget does not have an ID, then it cannot save its state
A good information: you don't need to check whether the Bundle object is null into the onCreate() method. Use the onRestoreInstanceState() method, which the system calls after the onStart() method. The system calls onRestoreInstanceState() only if there is a saved state to restore, so you do not need to check whether the Bundle is null
Store information: static final String PLAYER_SCORE = "playerScore"; static final String PLAYER_LEVEL = "playerLevel"; #Override public void onSaveInstanceState(Bundle savedInstanceState) { // Save the user's current game state savedInstanceState.putInt(PLAYER_SCORE, mCurrentScore); savedInstanceState.putInt(PLAYER_LEVEL, mCurrentLevel); // Always call the superclass so it can save the view hierarchy state super.onSaveInstanceState(savedInstanceState); } If you don't want to restore information in your onCreate-Method: Here are the examples: Recreating an Activity Instead of restoring the state during onCreate() you may choose to implement onRestoreInstanceState(), which the system calls after the onStart() method. The system calls onRestoreInstanceState() only if there is a saved state to restore, so you do not need to check whether the Bundle is null public void onRestoreInstanceState(Bundle savedInstanceState) { // Always call the superclass so it can restore the view hierarchy super.onRestoreInstanceState(savedInstanceState); // Restore state members from saved instance mCurrentScore = savedInstanceState.getInt(PLAYER_SCORE); mCurrentLevel = savedInstanceState.getInt(PLAYER_LEVEL); }
Basically onSaveInstanceState(Bundle outBundle) will give you a bundle. When you look at the Bundle class, you will see that you can put lots of different stuff inside it. At the next call of onCreate(), you just get that Bundle back as an argument. Then you can read your values again and restore your activity. Lets say you have an activity with an EditText. The user wrote some text inside it. After that the system calls your onSaveInstanceState(). You read the text from the EditText and write it into the Bundle via Bundle.putString("edit_text_value", theValue). Now onCreate is called. You check if the supplied bundle is not null. If thats the case, you can restore your value via Bundle.getString("edit_text_value") and put it back into your EditText.
This is for extra information. Imagine this scenario ActivityA launch ActivityB. ActivityB launch a new ActivityAPrime by Intent intent = new Intent(getApplicationContext(), ActivityA.class); startActivity(intent); ActivityAPrime has no relationship with ActivityA. In this case the Bundle in ActivityAPrime.onCreate() will be null. If ActivityA and ActivityAPrime should be the same activity instead of different activities, ActivityB should call finish() than using startActivity().
If Data Is not Loaded From savedInstanceState use following code. The problem is url call is not to complete fully so, check if data is loaded then to show the instanceState value. //suppose data is not Loaded to savedInstanceState at 1st swipe if (savedInstanceState == null && !mAlreadyLoaded){ mAlreadyLoaded = true; GetStoryData();//Url Call } else { if (listArray != null) { //Data Array From JsonArray(ListArray) System.out.println("LocalData " + listArray); view.findViewById(R.id.progressBar).setVisibility(View.GONE); }else{ GetStoryData();//Url Call } }