10

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

Community
  • 1
  • 1
Benoit Duffez
  • 11,839
  • 12
  • 77
  • 125
  • Right now the only option I have is: `isFromBackStack = getActivity().getSupportFragmentManager().getBackStackEntryCount() > 0`. I'd like to have something cleaner, this feels like a hack to me. Also, I don't get a `Bundle` from the previous fragment instance. – Benoit Duffez Jun 24 '13 at 07:11

1 Answers1

11

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.

Benoit Duffez
  • 11,839
  • 12
  • 77
  • 125
  • Wow. I am just learning android so I suspect I am missing something. Maybe I should read a book. I couldn't get data to persist across the life span on my fragment. But this does the trick. Thank you – terary May 08 '16 at 19:06
  • 1
    Instead of saving state in onDestroyView for the backstack case you can use FragmentManager's saveFragmentInstanceState() to before you remove/replace it. This will call onSaveInstanceState(). This allows you to keep state persistence code in once place. – Maurice Gavin May 30 '17 at 16:42
  • I have a similar approach to this where I need to save the list that was previously loaded when the `fragment` is replaced. Do you know if this might cause any memory issues? – jlively Jun 01 '17 at 11:42
  • saving it in the activity doesn't seem a good idea to me. Activities are always clogged with logic. – Alberto M Nov 09 '17 at 16:18