Fragment view state retention: A dirty solution

This is the last part of this 6 part series about Fragment Oriented Architecture in Android applications. In the previous post I talked about managing sessions in fragment oriented application. In this post I am going to talk about retaining view hierarchy of a Fragment after removing it from container and then coming back to it by popping the backstack.

(Sample application’s source code and README)

When a fragment gets replaced by another fragment and the transaction is added to back stack, the expectation after a popBackStack() is to return to the previous fragment with its UI state intact. Activity backstack takes care of this expectation quite cleanly until a low-memory situation occurs. But in case of fragments, this isn’t the default behaviour. In a vanilla implementation, the replaced fragment’s view-hierarchy would get recreated upon returning back to it. Reason is that during a replace operation, all the destructive life-cycle methods get called till onDestroyView(), which wipes out the view-hierarcy. Upon returning back, all the constructive lifecycle methods right from onCreateView() get called, thus, recreating the view-hierarchy totally afresh. Reason for this flow is to keep ‘Fragments’ memory friendly. Without the view-hierarchy, a fragment is just a java object with a bunch of instance variables.

fragment_lifecycle

So, the good news is we still have the instance variables intact. For instance, if user updates text in an EditText to “Some text”, we can keep this value in an instance variable before a replace operation and upon returning back, this value can be set as the text of EditText in new view hierarchy. Same can be done with scroll-state, switch state, etc. This is the recommended way and pretty clean. But this solution can get exponentially complicated with increase in complexity of fragment’s view-hierarchy.

Dirty solution is to retain the view-hierarchy upon a replace operation. This of course dumps the ‘memory-friendly’ tag but helps a great deal while implementing complicated views. This is done by keeping a reference to the root-view of fragment before replace operation and return it from onCreateView() instead of inflating a fresh view-hierarchy. This can be done by the following additional code in BaseFragment.

// PERSISTENT ROOT VIEW

public abstract class BaseFragment extends Fragment {
    public boolean hasInitializedRootView = false;
    private View rootView;

    public View getPersistentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState, int layout) {
	if (rootView == null) {
            // Inflate the layout for this fragment
            rootView = inflater.inflate(layout, null);
        } else {
            // Do not inflate the layout again.
            // The returned View of onCreateView will be added into the fragment.
            // However it is not allowed to be added twice even if the parent is same.
            // So we must remove rootView from the existing parent view group
            // (it will be added back).
            ((ViewGroup)rootView.getParent()).removeView(rootView);
        }
		
    return rootView;
}

getPersistentView() method in above code keeps a reference to the view-hierarchy and never inflates it again. This method can be called from onCreateView() callback methods of inheriting fragments that have a complex UI.

// UTILISING PERSISTENT ROOT VIEW IN FRAGMENT

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    return getPersistentView(inflater, container, savedInstanceState, R.layout.fragment_layout);
}


@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
		
    if(!hasInitializedRootView) {
	hasInitializedRootView = true;
        
        // Do initial setup of UI
        doInitialSetUpOfUI();
    }
}

In sample application, last two sections are using this technique to retain scroll position of list. NestedListFragment is an even more complex situation where the list is a nested child fragment. Handling this situation in recommended ways is quite a pain.

This, of course, is not an ideal solution. I’m still looking for a better one. Please direct me if you hit upon a better solution.

6 thoughts on “Fragment view state retention: A dirty solution

  1. Pingback: Advocating Fragment Oriented Applications in Android | Vinsol - Ruby on Rails, iOS, Android Consulting and Development

  2. I’m puzzled about how view state is handled by Android. I thought view state would be lost along with onDestroyView() but I changed your sample app to not use getPersistentView() and the list view scroll position was persisted anyway. Also the Inter Fragment Communication (Greetings) feature of the sample app persists the contents of the EditText across backstack transactions (even if I don’t keep an EditText instance attribute in the fragment); shouldn’t it be empty when the backstack is popped back? I wrote a test app which provides the same feature and its behavior is the same. I submitted a question on StackOverflow (http://stackoverflow.com/questions/28344678/fragments-ondestroyview-called-but-view-is-not-being-destroyed) but I also wonder what you think about this situation.

  3. Hello sir,

    How to prevent to call interface method when back to fragment.

  4. I’ve stumbled upon this approach to retain the fragment view start and was quite fascinated at the aspect of retaining the view of the fragment without much hassle until i faced weird behaviors on the UI elements after returning back to the fragment from the backstack. I happen to use view Injectios (robojuice or butterknife) the most common problem is some views are not being updated after view is retained, can this be perhaps of reinitializations by the injection libraries ? any idea ?

  5. What’s up, I would like to subscribe for this weblog to obtain most recent updates, thus
    where can i do it please assist.

Leave a Reply

Your email address will not be published. Required fields are marked *