How can I properly save and restore an activity’s state using the Android save instance state mechanism?
I’m developing an Android application and having trouble with saving the application’s state. I’ve modified the basic ‘Hello, Android’ example as follows:
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 expected this to work for the simplest case, but the activity always displays the initial “Welcome to HelloAndroid!” message regardless of how I navigate away from and back to the app.
I believe the solution involves overriding methods like onPause(), but I haven’t been able to find a clear explanation in the documentation after searching for about 30 minutes. What’s the proper way to save and restore an activity’s state in Android?
The Android save instance state mechanism requires you to override both onSaveInstanceState() to save your data and then properly handle the restored data in onCreate() or onRestoreInstanceState(). Your current code only checks for the saved state but never actually saves any state to restore, which is why you always see the initial message.
Contents
- Understanding the Android State Saving Mechanism
- Proper Implementation of State Saving
- Key Methods in the Activity Lifecycle
- Common Pitfalls and Best Practices
- Advanced State Management
Understanding the Android State Saving Mechanism
Android’s save instance state mechanism is designed to handle temporary configuration changes and interruptions without losing important user data. When the system needs to destroy an activity (due to screen rotation, memory constraints, or user navigation), it automatically calls onSaveInstanceState() to persist critical data. This data is then passed back to the activity when it’s recreated.
The process works as follows:
- Save Phase: The system calls
onSaveInstanceState(Bundle outState)before destroying the activity - Destruction: The activity is destroyed
- Restore Phase: The activity is recreated, and the system passes the saved Bundle to
onCreate(Bundle savedInstanceState)andonRestoreInstanceState(Bundle savedInstanceState)
Your current implementation only checks for the presence of savedInstanceState but never saves any data, so the Bundle is always null when the activity is recreated.
Proper Implementation of State Saving
Here’s the corrected version of your code with proper state saving and restoration:
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;
private static final String KEY_MESSAGE = "message";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mTextView = new TextView(this);
// Check if we have a saved state
if (savedInstanceState == null) {
// First time - create new state
mTextView.setText("Welcome to HelloAndroid!");
} else {
// Restored state - restore previous message
String savedMessage = savedInstanceState.getString(KEY_MESSAGE);
mTextView.setText(savedMessage != null ? savedMessage : "Welcome back.");
}
setContentView(mTextView);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Save the current state of the activity
String currentMessage = mTextView.getText().toString();
outState.putString(KEY_MESSAGE, currentMessage);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
// Additional restoration can be done here if needed
String savedMessage = savedInstanceState.getString(KEY_MESSAGE);
if (savedMessage != null) {
mTextView.setText(savedMessage);
}
}
}
Key improvements:
- Added
onSaveInstanceState()to save the current text - Used a constant key (
KEY_MESSAGE) for reliable data storage - Implemented proper null checks for restored data
- Added
onRestoreInstanceState()as an alternative restoration point
Key Methods in the Activity Lifecycle
onSaveInstanceState(Bundle outState)
This method is called before the activity is potentially destroyed. It’s your opportunity to save data that should be restored if the activity is recreated.
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Save your data here
outState.putString("key", "value");
outState.putInt("counter", mCounter);
}
onCreate(Bundle savedInstanceState)
Called when the activity is first created or recreated. The savedInstanceState parameter contains the data saved in onSaveInstanceState().
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState != null) {
// Restore saved data
String savedText = savedInstanceState.getString("key");
int savedCounter = savedInstanceState.getInt("counter");
}
}
onRestoreInstanceState(Bundle savedInstanceState)
Called after onStart() but before onResume() when the activity is being restored from a saved state.
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
// Additional restoration logic
mTextView.setText(savedInstanceState.getString("key"));
}
Common Pitfalls and Best Practices
Common Mistakes
-
Not calling super methods:
java// WRONG - Always call super @Override protected void onSaveInstanceState(Bundle outState) { // Missing super call outState.putString("key", "value"); } // CORRECT @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putString("key", "value"); } -
Saving non-persistent data: Don’t save references to views, bitmaps, or other objects that can’t be serialized.
-
Over-reliance on onSaveInstanceState: This is for temporary state only, not for long-term data persistence.
Best Practices
-
Use constants for Bundle keys:
javaprivate static final String USER_ID_KEY = "user_id"; private static final String USER_NAME_KEY = "user_name"; outState.putInt(USER_ID_KEY, userId); outState.putString(USER_NAME_KEY, userName); -
Save complex data as primitives or simple objects:
java// Save ArrayList as Parcelable outState.putParcelableArrayList("items", mItems); // Save custom objects as Parcelable outState.putParcelable("user", mUser); -
Handle null values gracefully:
javaString savedText = savedInstanceState != null ? savedInstanceState.getString("key") : "default text";
Advanced State Management
Using ViewModel for Complex State
For more complex state management, consider using Android Architecture Components:
public class MyViewModel extends ViewModel {
private MutableLiveData<String> currentText = new MutableLiveData<>();
public LiveData<String> getCurrentText() {
return currentText;
}
public void setText(String text) {
currentText.setValue(text);
}
}
// In Activity
private MyViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewModel = new ViewModelProvider(this).get(MyViewModel.class);
// Observe data changes
viewModel.getCurrentText().observe(this, text -> {
mTextView.setText(text);
});
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("current_text", viewModel.getCurrentText().getValue());
}
Handling Configuration Changes
For specific configuration changes where you want to keep the activity alive:
<!-- AndroidManifest.xml -->
<activity
android:name=".MyActivity"
android:configChanges="orientation|screenSize"
android:windowSoftInputMode="adjustResize" />
Using SharedPreferences for Persistent Data
For data that needs to survive app restarts:
// Save data
SharedPreferences prefs = getSharedPreferences("MyPrefs", MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putString("key", "value");
editor.apply();
// Restore data
String savedValue = prefs.getString("key", "default");
Conclusion
-
Always implement both saving and restoration: Override
onSaveInstanceState()to save data and checksavedInstanceStateinonCreate()to restore it. -
Use proper Bundle keys: Define constants for your Bundle keys to avoid typos and ensure consistency.
-
Handle null values gracefully: Always check if
savedInstanceStateis null before trying to access it. -
Choose the right storage mechanism: Use
onSaveInstanceState()for temporary state, SharedPreferences for app-level persistence, and ViewModel for configuration changes. -
Call super methods: Always call the superclass implementation of lifecycle methods to ensure proper framework behavior.
By implementing these patterns correctly, your Android application will maintain its state properly across configuration changes and interruptions, providing a seamless user experience.