A common complaint from Android developers using Fragments is that Fragments don’t seem to manage their saved state very well. This complaint is valid- we are used to Views and Activities that seem to manage their state very well. When an Activity is created, you can rely on
savedInstanceState being null if it is a new Activity and having saved state if it is being restored.
Fragments don’t seem to enjoy the same luxury at times. When the framework calls
onCreateView you might not have saved state even if the Fragment is being restored!
How do we deal with this? The first step is understanding why this happens.
Fragment.onSaveInstanceState() gives us this bit that explains why our Fragments don’t always save their state when we expect them to:
This corresponds to
Activity.onSaveInstanceState(Bundle)and most of the discussion there applies here as well.
Note however: this method may be called at any time before
onDestroy(). There are many situations where a fragment may be mostly torn down (such as when placed on the back stack with no UI showing), but its state will not be saved until its owning activity actually needs to save its state.
In other words, Fragments will only save their state when their containing Activity saves its state. Since our Activities’ lifecycles typically span that of multiple Fragments’ lifecycles but the saved state is tied together, we experience a disconnect.
When do we care?
Before we talk about a solution, we need to talk about when we care.
This disconnect primarily happens when a Fragment is removed from the FragmentManager. Any Fragments that are still in the FragmentManager (either directly visible or still on the back stack) will have their state saved when the Activity’s state is saved. With this default behavior, backgrounding the app then restoring it should bring you back to the same state with the same backstack.
However, if you
remove() a Fragment and don’t add the transaction to the backstack you run the risk of losing your Fragment’s state.
This is common in a tabbed interface, including when using bottom navigation. When you switch between taps, you are likely
replace()ing the shown Fragment, but you might also want to save the old tab’s state such as the current scroll position if the user leaves your app and comes back.
At some point I am hopeful that the Navigation Architecture Component will help out here. As of right now it doesn’t have good support for bottom navigation paradigms.
How to bridge the gap
We can solve this problem! I mentioned that tabbed interfaces are a common place to run into this problem. If you have used a
ViewPager with Fragments in the past, you have likely encountered
FragmentStatePagerAdapter. That adapter handles this problem for us! But what if you aren’t using
FragmentStatePagerAdapter? We can create similar behavior ourselves!
The first key is that we can save our Fragments’ state ourselves using
FragmentManager.saveInstanceState(Fragment). Before removing the Fragment, call this method to get a
Bundle containing your Fragment’s saved state!
The second key is restoring state. As it turns out, Fragments already have a mechanism that allows you to set the initial state of a Fragment as well! Calling
Fragment.setInitialSavedState(Bundle) will set the saved instance state Bundle that will then be passed to
Here’s a simple helper demonstrating this strategy:
Here’s a sample app demonstrating how to use this technique and when it is useful.