4

Quick note: I did my due diligence before asking the question: I know that this sounds like a dupe. But none of the existing questions ask the exact question I have, and none of the solutions on the existing questions work for me. Thanks.


TL;DR: When restoring a fragment that has a nested fragment, the childFragmentManager adds the nested fragment back, but does not restore its state. How do I make it do so?


I have a ViewPager that is backed by a FragmentStatePagerAdapter. The FSPA returns fragments, each of which in turn have one other fragment added when setUserVisibleHint is true. I'm basically avoiding the cost of loading one offscreen fragment which is the default ViewPager behaviour. The inner fragment is a list of items, so maintaining scroll position is important.

Here's the sequence of events:

  • Open page N, because it's visible, the inner fragment is added
  • Go to page N+2 or N-2: now the fragment at position N is destroyed because it's past the ViewPager's offScreenLimit of 1
  • Then when I come back to N, the fragment manager conveniently added the inner fragment back (because of savedInstanceState I'm guessing)
  • BUT: and here's the bug: the newly added child fragment's state is not saved or restored: the fragment is freshly created

This leads to a terrible user experience because when coming back to position N, the list is scrolled all the way back to top.

Is there any way to avoid this? I have tried:

  • manually saving the state using getChildFragmentManager().saveFragmentInstanceState(myInnerFragment)
    • but I don't know where or how to restore it because the child fragment manager added the fragment back for me
  • retaining the instance variable mInnerFragment and replacing the automatically added fragment with this
    • this is wrong for a lot of reasons, but it also looks bad because we're replacing the already added fragment with this one so there is a visible jumping when swiping back to position N

Note that I can't use setRetainInstanceState(true) in any of the fragments: outer or inner, because all this is nested in another fragment. A lot of the answers have suggested using reflection to hang on to and restore the mChildFragmentManager, but that seems risky, and in any case I can't use that because of the setRetainInstance restriction.


Code

OuterFragment.java

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    if (!isVisibleToUser) {
        return;
    }

    addInnerFragment();
}

private void addInnerFragment() {
    if (getInnerFragment() != null && getInnerFragment().isAdded()) {
        return;
    }

    FragmentTransaction transaction =
        getChildFragmentManager().beginTransaction();
    transaction.replace(
        R.id.fragment_container,
        getInnerFragment(),
        FRAGMENT_TAG);
    transaction.commit();
}

private Fragment getInnerFragment() {
    if (mInnerFragment != null) {
        return mInnerFragment;
    }

    // The fragment could have already been added if we're coming back from a savedInstanceState.
    Fragment fragment =
        getChildFragmentManager().findFragmentById(R.id.fragment_container);
    if (fragment != null) {
        // Verified using debugger that this condition is being hit when I come
        // back to `N`
        mInnerFragment = fragment;
        return mInnerFragment;
    }

    mInnerFragment = InnerFragment.newInstance();
    return mInnerFragment;
}
Rohan Dhruva
  • 1,194
  • 1
  • 10
  • 20
  • In its simplest form, what I could do is save the state of the inner fragment myself in outer fragment's `onSaveInstanceState`, and restore it before the fragment manager automatically adds the fragment back. While it's easy to do the first part, there is no callback available or a mechanism to hook into fragmentmanager to provide my own state. – Rohan Dhruva Jun 25 '16 at 08:29

0 Answers0