Saving Data Across Rotation
Android does a great job of providing alternative resources at the right time. However, destroying and re-creating activities on rotation can cause headaches, too, like GeoQuiz’s bug of reverting back to the first question when the device is rotated.
To fix this bug, the post-rotation QuizActivity needs to know the old value of mCurrentIndex. You need a way to save this data across a runtime configuration change, like rotation. One way to do this is to override the Activity method:
protected void onSaveInstanceState(Bundle outState)
This method is normally called by the system before onPause(), onStop(), and onDestroy().
The default implementation of onSaveInstanceState(...) directs all of the activity’s views to save their state as data in the Bundle object. A Bundle is a structure that maps string keys to values of certain limited types.
You have seen this Bundle before. It is passed into onCreate(Bundle):
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... }
When you override onCreate(...), you call onCreate(...) on the activity’s superclass and pass in the bundle you just received. In the superclass implementation, the saved state of the views is retrieved and used to re-create the activity’s view hierarchy.
Overriding onSaveInstanceState(Bundle)
You can override onSaveInstanceState(...) to save additional data to the bundle and then read that data back in onCreate(...). This is how you are going to save the value of mCurrentIndex across rotation.
First, in QuizActivity.java, add a constant that will be the key for the key-value pair that will be stored in the bundle.
Listing 3.5 Adding a key for the value (QuizActivity.java)
public class QuizActivity extends AppCompatActivity { private static final String TAG = "QuizActivity"; private static final String KEY_INDEX = "index"; private Button mTrueButton; ...
Next, override onSaveInstanceState(...) to write the value of mCurrentIndex to the bundle with the constant as its key.
Listing 3.6 Overriding onSaveInstanceState(...) (QuizActivity.java)
mNextButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mCurrentIndex = (mCurrentIndex + 1) % mQuestionBank.length; updateQuestion(); } }); updateQuestion(); } @Override public void onSaveInstanceState(Bundle savedInstanceState) { super.onSaveInstanceState(savedInstanceState); Log.i(TAG, "onSaveInstanceState"); savedInstanceState.putInt(KEY_INDEX, mCurrentIndex); }
Finally, in onCreate(...), check for this value. If it exists, assign it to mCurrentIndex.
Listing 3.7 Checking bundle in onCreate(...) (QuizActivity.java)
... if (savedInstanceState != null) { mCurrentIndex = savedInstanceState.getInt(KEY_INDEX, 0); } updateQuestion(); }
Run GeoQuiz and press Next. No matter how many device rotations you perform, the newly minted QuizActivity will “remember” what question you were on.
Note that the types that you can save to and restore from a Bundle are primitive types and classes that implement the Serializable or Parcelable interfaces. It is usually a bad practice to put objects of custom types into a Bundle, however, because the data might be stale when you get it back out. It is a better choice to use some other kind of storage for the data and put a primitive identifier into the Bundle instead.
Testing the implementation of onSaveInstanceState(...) is a good idea – especially if you are saving and restoring objects. Rotation is easy to test; testing low-memory situations is harder. There is information at the end of this chapter about how to simulate your activity being destroyed by Android to reclaim memory.