Android navigation tabs: Restoring fragment view state

Janus Varmarken picture Janus Varmarken · Mar 16, 2013 · Viewed 10.8k times · Source

I'm trying to understand how to preserve fragment view state when fragments are used within navigation tabs. In my efforts, I've come across two problems that I cannot find any proper solutions to.

I have two tabs, Tab1 and Tab2. Tab1's layout is defined by FragmentA and Tab2's layout is defined by FragmentB. I've followed the approach given here (edit: documentation has changed since this question was asked).

The first problem: Even though my views have IDs, their states are not fully restored when a fragment is re-attached (after a tab switch rotation). In particular: an EditText with an ID does indeed save its entered text, but it does not save its enabled status. Also, buttons do not save if they are enabled or disabled even though they have IDs. I've found two possible workarounds for this problem:

  1. Use hide()/show() instead of attach()/detach() when switching tabs.
  2. in onPause(), save the current fragment view state in a View instance variable of the fragment via getView(). In onCreateView(Bundle savedInstanceState) check if this field is non-null and if that is the case return the value of this field. This solution seems hacky, and I've been told that it might also introduce a memory leak in my app.

The second problem: Consider the following user interaction: User starts on Tab1 and does some changes that put the view state of Tab1 in a different state than its default state (and we want the fragment to save this view state through tabswitches and device tilts). User then goes to Tab2. User then tilts her/his device (still at Tab2). User then swaps to Tab1 (at the new screen orientation). Now, the problem is: when the user initially swaps from Tab1 to Tab2, the fragment is detached and thereby its view discarded (even though the fragment instance still lives). When the user then tilts the device, the activity - and thereby both FragmentA and FragmentB associated with it - are destroyed. Since FragmentA at this point does no longer have a view (remember: it was detached), we cannot save the state of its view elements (e.g., what buttons are enabled/disabled) during the call to FragmentA.onSaveInstanceState(Bundle savedInstanceState). How do you recover fragment view state in a situation like this? Is the only viable solution to save every single view element's different status flags as SharedPreferences? This seems way too complicated for such an "everyday job".

Answer

antonyt picture antonyt · Mar 16, 2013

Problem 1:

Android does not save your view enabled state by default. It seems only things which are directly influenced by user actions (without additional code) are saved. For a normal View, no information is saved, and for a TextView, of which EditText is a subclass, the entered text is saved (if freezesText is set).

If you want to anything else to be saved, you will have to do it yourself. Here is an question with some answers that show how to implement custom view state saving. You can stick with attach/detach if you follow that approach.

Problem 2:

You are right in that Fragment.onSaveInstanceState(Bundle) can be called after your view has already been destroyed. However, this is not where you should be saving your view state. Android will call View.onSaveInstanceState() right before it destroys your views when detaching a fragment. It saves this state and gives it back to you when you attach the fragment again. This is exactly what happens when you flip between tabs normally with no rotations. Fragment.onSaveInstanceState(Bundle) is not called when detaching. Even if you rotate the device, the view state saved as a result of the detach will persist. If you implement View.onSaveInstanceState() as directed above, your view state will be saved and restored properly, even in the Tab1-Tab2-rotate-Tab1 scenario.

Side note: The example code in the docs seems to have some problems when you try to rotate. The lifetime of the TabListener is the same as that of the Activity - a new one is created every time you rotate. This means it also loses its internal reference to the fragment, every time you rotate. Added fragments are recreated automatically and so there is no need for the TabListener to try to create a new instance and add it, after rotation. Instead, of the internal reference, it should just try to find the fragment with the appropriate tag in the fragment manager. After rotation it will still exist.

The other problem is with that the selected tab is not saved, but this is noted at the bottom of the example. You could save this in Activity.onSaveInstanceState(Bundle).