fragment-state-save-restore-deep-dive
fragment-state-save-restore-deep-dive copied to clipboard
Deep dive into Fragment state saving and restoring in Android
This repo was originally meant to act as a companion for a blog series, but I now feel the repo is comprehensive and blog posts are not needed.
We take a deep dive into how Fragment
s in Android save/restore their state during configuration changes. This may seem trivial at first. The first solution that comes to mind is:
- Save state in
onSaveInstanceState
. - In
onCreateView
, inflate your view hierarchy and get a handle to your views (usingfindViewById
, Butterknife, data-binding, whatever) - Also in
onCreateView
, retrieve previously saved state from theBundle
parameter passed in to this method. - Apply the state from step 3 into views from step 2.
However, there are few subtleties to be aware of, especially when a Fragment
is placed on the backstack. We will use a sample app to explore the issues that can crop up, and see how to fix them.
TL;DR
If you are too lazy to go through the source code and posts, here's an executive summary of the take-aways:
- When a
Fragment
is not at the top of the stack, i.e., it is on the back stack, theActivity
still gives it a chance to save state (for example when being rotated).- The
onSaveInstanceState
is called, followed byonDestroy
. - Later, after rotation,
onCreate
is called. - However, none of the view-related methods are called.
onCreateView
and later methods are not called.
- The
- The above causes issues with state saving if you are not careful
- You might try to save state for a view assuming it exists, however since
onCreateView
has not been called, you run the risk ofNullPointerException
- A simple null check doesn't get the job done since this results in no state being saved at all.
- You might try to save state for a view assuming it exists, however since
Recipe
Based on these learnings, I recommend to split up the "retrieve saved state and apply it to views" process that you normally follow in onCreateView
into two steps. The suggested recipe for saving/restoring state when it comes to Fragment
s is:
- Retrieve state in
onCreate
. Save the retrieved state in a member variable - In
onCreateView
, after inflating your layout, finding your views etc, then restore the state of the views using the state you retrieve inonCreate
- In
onSaveInstanceState
, if the views are present, then ask the views for their saved state (by calling the view'sonSaveInstanceState
for example). Save this in theouState
parameter. - In
onSaveInstanceState
,if the views are not present, then save the state you retrieved inonCreate
into theoutState
paremeter.
This way, you relay the state you had received in onCreate
over to the system in onSaveInstanceState
. So, even if your Fragment
is on the back stack while it is rotated, it is still able to save state.
Sample Description
The sample just contains an Activity
with two Fragment
s inside it. The first Fragment
shows a multiple choice list of Android flagship devices. The second Fragment
simply contains a TextView
with a message.
Our goal is to go through the following use cases and see how the Fragment
life-cycle and the save/restore methods need to be understood in order to achieve all the use cases.
The basic-example-scenario tag corresponds to the initial state of the sample. You can use git checkout basic-example-scenario
to see the initial code.
Use Case 1:
- Launch the app and hit "Populate"
- Select one or more items in the list
- Hit "Next" to go to the next screen.
- Press the Android Back button to return to the list screen
We expect that the items and choices from Step 2 are remembered.
The tag basic-scenario-without-savestate contains the solution to Use Case 1. Use git checkout basic-scenario-without-savestate
to see the corresponding code.
Use Case 2:
- Launch the app and hit "Populate"
- Select one or more items in the list
- Rotate the device
We expect that the items and choices from Step 2 are remembered.
The tag use-case-2 contains the code to achieve Use Case 2. The command to use is git checkout use-case-2
Use Case 3:
- Launch the app and hit "Populate"
- Select one or more items in the list
- Hit "Next" to go to the next screen.
- Rotate the device - multiple times
- Press the Android Back button to return to the list screen
We expect that the items and choices from Step 2 are remembered.
The tag use-case-3-solution shows how to split up your onCreateView
method and move the state retrieval code to onCreate
. The command to use is git checkout use-case-3-solution
.
Bonus: The tag use-case-3-solution-debuglog adds debug logging so that you can clearly see what life cycle methods are called when a device is rotated and the fragment is on the back stack. Use git checkout use-case-3-solution-debuglog
.