Fragment in ViewPager not restored after popBackStack
After a lengthy investigation it turns out to be a problem with the fragment manager.
When using a construct like the one above the fragment transaction to reattach the fragment to the page list is silently discarded. It is basically the same problem that causes a
java.lang.IllegalStateException: Recursive entry to executePendingTransactions
when trying to alter the fragments inside the FragmentPager.
The same solution, as for problems with this error, is also applicable here. When constructing the FragmentStatePagerAdapter supply the correct child fragment manager.
Instead of
viewPager.setAdapter(new SimpleFragmentStatePagerAdapter(getFragmentManager(),mParentString));
do
viewPager.setAdapter(new SimpleFragmentStatePagerAdapter(getChildFragmentManager(),mParentString));
See also: github
ViewPager Fragments are not recreated after FragmentTransaction.replace() followed by back button
After struggling with this for a few days I realized that the problem actually was that I passed the wrong FragmentManager
to the ViewPager
. It is indeed the FragmentManager
returned by Fragment.getChildFragmentManager()
that should be used. This makes sense, since it is the Fragment
itself that stores the state of the child Fragment
s in the ViewPager
. I am not sure why I couldn't make that work before, but now the LifeCycle of the app works fine with the following setup in my main Fragment
's onCreate()
method:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.mm_main_fragment, container, false);
mMMActivity = (MMActivity) container.getContext();
mMMActivity.getSupportActionBar().setTitle(getString(R.string.mm_toolbar_title_main_fragment));
// Setup ViewPager.
mViewPager = (ViewPager) view.findViewById(R.id.mm_pager);
mAdapter = new MMPagerAdapter(getChildFragmentManager(), mMMActivity); // <-- This is the key
mViewPager.setAdapter(mAdapter);
mViewPager.setOffscreenPageLimit(PagerConstants.OFFSCREEN_PAGE_LIMIT);
mViewPager.setCurrentItem(PagerConstants.PAGE_FILTER_RECIPES);
// Setup TabLayout.
TabLayout tabLayout = (TabLayout) view.findViewById(R.id.mm_tablayout);
tabLayout.setupWithViewPager(mViewPager);
return view;
}
This was also the solution of another question. As a side note, I use this solution to get the Fragment
s in my ViewPager
and it works just fine with the LifeCycle.
Test Application:
In the research of this issue I created a test application for this. The ones interested can clone it from here. It has ugly colors.
ViewPager holds fragment's instance even after setting a new adapter
I found the solution.
The solution is very simple. I just had to set manually null
to the Child's adapter. With this, the ViewPager
is forced to destroy every fragment.
So into the onDestroyView
of Fragment
's father I added:
@Override
public void onDestroyView() {
super.onDestroyView();
mViewpagerChild.removeOnPageChangeListener(mOnPaymentMethodsPageChangeListener);
mViewpagerChild.setAdapter(null); // <-- This is what I added
}
ViewPager swallows onBackPressed so FragmentContainerView cannot be navigated
As per the Fragment Manager guide:
Consider the navigation structure as a series of layers, with the activity as the outermost layer, wrapping each layer of child fragments underneath. Each layer must have a single primary navigation fragment. When the Back event occurs, the innermost layer controls navigation behavior. Once the innermost layer has no more fragment transactions from which to pop back, control returns to the next layer out, and this process repeats until you reach the activity.
So while app:defaultNavHost="true"
is setting the NavHostFragment
inside your FragmentA
as the primary navigation fragment in its layer (the childFragmentManager
layer of FragmentA
), FragmentA
must also be set as the primary navigation fragment in order for any system back button events to make it to FragmentA
at all.
That same page brings up how to do that:
To define the primary navigation fragment inside of a fragment transaction, call the
setPrimaryNavigationFragment()
method on the transaction, passing in the instance of the fragment whosechildFragmentManager
should have primary control.
While ViewPager2
has APIs to allow you to set the primary navigation fragment to the current page, ViewPager1 has no such API (one of the many reasons why you should absolutely move to ViewPager2).
As a workaround for this in ViewPager1, FragmentA
could set itself as the primary navigation fragment in onResume()
:
public fun onResume() {
super.onResume()
parentFragmentManager
.beginTransaction()
.setPrimaryNavigationFragment(this)
.commit()
}
Related Topics
Generate Javadocs with Android Gradle Plugin
How to Use a Library Project in Android Studio
Android Studio and Gradle Build Error
How to Create Listview Inside Dialog
How to Print a Double with Two Decimals in Android
How to Change System Navigation Bar Color
Backup/Restore Sqlite Db in Android
App Links Intent Filters in Assetlinks.JSON Not Working on Android
Set View Text Align at Center in Spinner in Android
How Disable/Remove Android Activity Label and Label Bar
Android: Radio Button in Custom List View
Multiple Notifications to The Same Activity
Get Context of Test Project in Android Junit Test Case
How to Get/Set Media Volume (Not Ringtone Volume) in Android