How to persist fragment data after backstack transactions?

Benoit Duffez picture Benoit Duffez · Jun 22, 2013 · Viewed 9.5k times · Source

I've got an activity, containing fragment 'list', which upon clicking on one of its items will replace itself to a 'content' fragment. When the user uses the back button, he's brought to the 'list' fragment again.
The problem is that the fragment is in its default state, no matter what I try to persist data.

Facts:

  1. both fragments are created through public static TheFragment newInstance(Bundle args), setArguments(args) and Bundle args = getArguments()
  2. both fragments are on the same level, which is directly inside a FrameLayout from the parent activity (that is, not nested fragments)
  3. I do not want to call setRetainInstance, because my activity is a master/detail flow, which has a 2 pane layout on larger screens. 7" tablets have 1 pane in portrait and 2 panes in landscape. If I retain the 'list' fragment instance, it will (I think) fuck things up with screen rotations
  4. when the users clicks an item in the 'list' fragment, the 'content' fragment is displayed through FragmentTransaction#replace(int, Fragment, String), with the same ID but a different tag
  5. I did override onSaveInstanceState(Bundle), but this is not always called by the framework, as per the doc: "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."
  6. I'm using the support library

From the bullet 5 above, I guess that low-end devices that need to recover memory after a fragment transaction may call Fragment#onSaveInstanceState(Bundle). However, on my testing devices (Galaxy Nexus and Nexus 7), the framework doesn't call that method. So that's not a valid option.

So, how can I retain some fragment data? the bundle passed to Fragment#onCreate, Fragment#onActivityCreated, etc. is always null.

Hence, I can't make a difference from a brand new fragment launch to a back stack restore.

Note: possible related/duplicate question

Answer

Benoit Duffez picture Benoit Duffez · Jun 24, 2013

This doesn't seem right, but here's how I ended up doing:

public class MyActivity extends FragmentActivity {
    private Bundle mMainFragmentArgs;

    public void saveMainFragmentState(Bundle args) {
        mMainFragmentArgs = args;
    }

    public Bundle getSavedMainFragmentState() {
        return mMainFragmentArgs;
    }

    // ...
}

And in the main fragment:

public class MainFragment extends Fragment {
    @Override
    public void onActivityCreated(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Bundle args = ((MyActivity) getActivity()).getSavedMainFragmentState();

        if (args != null) {
            // Restore from backstack
        } else if (savedInstanceState != null) {
            // Restore from saved instance state
        } else {
            // Create from fragment arguments
            args = getArguments();
        }

        // ...
    }

    // ...

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        Bundle args = new Bundle();
        saveInstance(args);
        ((MyActivity) getActivity()).saveMainFragmentState(args);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        saveInstance(outState);
    }

    private void saveInstance(Bundle data) {
        // put data into bundle
    }
}

It works!

  • if back from backstack, the fragment uses the parameters saved in onDestroyView
  • if back from another app/process/out of memory, the fragment is restored from the onSaveInstanceState
  • if created for the first time, the fragment uses the parameters set in setArguments

All events are covered, and the freshest information is always kept.

It's actually more complicated, it's interface-based, the listener is un/registered from onAttach/onDetach. But the principles are the same.