Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to persist fragment data after backstack transactions?

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

like image 572
Benoit Duffez Avatar asked Jun 22 '13 15:06

Benoit Duffez


People also ask

How do you resume an existing Backstack fragment?

Since you want only one back stack entry per Fragment , make the back state name the Fragment's class name (via getClass(). getName() ). Then when replacing a Fragment , use the popBackStackImmediate() method. If it returns true, it means there is an instance of the Fragment in the back stack.

How can I maintain fragment state when added to the back stack?

Solution: Save required information as an instance variable in calling activity. Then pass that instance variable into your fragment.

How do you save a fragment in savedInstanceState?

Here is an example: @Override protected void onCreate(Bundle savedInstanceState) { super. onCreate(savedInstanceState); if (savedInstanceState == null) { myFragment = MyFragment. newInstance(); getSupportFragmentManager() .

Are fragments reusable?

A fragment is a reusable class implementing a portion of an activity. A Fragment typically defines a part of a user interface.


1 Answers

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.

like image 108
Benoit Duffez Avatar answered Oct 14 '22 22:10

Benoit Duffez