Reputation: 2511
I am using ViewPager2 for my project. I need to use nested fragments inside a fragment with viewpager2. it works like charm until I try to navigate between fragments(not nested ones).
After the first time navigating between fragments, the application crash with the error explained below.
the fragment which contains nested fragments OnCreateView method:
View view = inflater.inflate(R.layout.orders_fragment, null);
ViewPager2 viewPager = view.findViewById(R.id.childViewPager);
TabLayout tabs = view.findViewById(R.id.tabs);
SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(getChildFragmentManager(),getLifecycle());
viewPager.setAdapter(sectionsPagerAdapter);
TabLayoutMediator.TabConfigurationStrategy tabConfigurationStrategy = (tab, position) -> {
String[] order_activity_tabs = getResources().getStringArray(R.array.situations);
for (int i=0; i<order_activity_tabs.length; i++) {
if(i==position)
tab.setText(order_activity_tabs[i]);
}
};
TabLayoutMediator tabLayoutMediator = new TabLayoutMediator(tabs, viewPager, tabConfigurationStrategy);
tabLayoutMediator.attach();
return view;
When I return the fragment that contains nested fragments it crashes with
java.lang.IllegalStateException: Fragment no longer exists for key f#0: unique id 4fbe17b8-5e22-4e07-a543-4a79445ad39c
at androidx.fragment.app.FragmentManagerImpl.getFragment(FragmentManagerImpl.java:365)
at androidx.viewpager2.adapter.FragmentStateAdapter.restoreState(FragmentStateAdapter.java:549)
at androidx.viewpager2.widget.ViewPager2.restorePendingState(ViewPager2.java:350)
at androidx.viewpager2.widget.ViewPager2.dispatchRestoreInstanceState(ViewPager2.java:375)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:4045)
at android.view.View.restoreHierarchyState(View.java:20253)
at androidx.fragment.app.Fragment.restoreViewState(Fragment.java:548)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:907)
at androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManagerImpl.java:1238)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:1303)
at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:439)
at androidx.fragment.app.FragmentManagerImpl.executeOps(FragmentManagerImpl.java:2079)
There are solutions for FragmentStatePagerAdapter but there is no for FragmentStateAdapter. And because I can't override methods in FragmentStateAdapter, implementing this solutions are impossible.
Thank you for reading this. Any help is appreciated.
Upvotes: 22
Views: 13477
Reputation: 1090
The accepted answer is not solving the problem. I have solved the issue, by keeping the view as variable outside of the onCreateView method, and only set the adapter and tabs, when the tabCount is 0. Then it saved the state, and fixed the crash. But be careful with reference of the view.
private var _binding: YourFragmentBinding? = null
val binding get() = _binding!!
val bindingSafe get() = _binding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
if (_binding != null)
return binding.root
_binding = inflate(inflater, container, false)
return binding.root
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (binding.yourTabs.tabCount == 0) {
...//the rest of the logic you want to do with the UI
}
}
If you want to do it without viewBinding, just replace the binding with simple View object.
Upvotes: -1
Reputation: 65
I just had the same issue. The cause is that when the containing fragment is recreated, it's childFragmentManager is as well. And the FragmentStateAdapter looks in the passed fragmentmanager to find fragments it restores state for.
My solution was to have it use the activity's fragmentManager instead of the fragment's, for example use the constructor that takes an activity like
class MyAdapter(f: Fragment) : FragmentStateAdapter(f.activity!!)
or when using the FragmentStateAdapter(FragmentManager, Lifecycle)
constructor, make sure to pass parentFragmentManager
and not childFragmentManager
Upvotes: -2
Reputation: 389
There is an issue with viewpager2 detach from recyclerview https://issuetracker.google.com/issues/154751401
As per my knowledge two fixes are there:
add this line in your xml viewpager2 component.
android:saveEnabled="false"
But the above one don't restore the current instance, which the fragment will recreate each time it popsBack.
The recommended solution is add the below line on your fragment
override fun onDestroyView() {
super.onDestroyView()
binding.pager.adapter = null
}
Upvotes: 2
Reputation: 13129
Using viewpager2 with FragmentStateAdapter
has a similar behaviour than FragmentStatePagerAdapter
Actually there are two ways of solving this problem
First one is seting setSaveEnabled()
to false into our viewpager2
viewpager2.setSaveEnabled(false)
The other one is overriding restoreState
at our adapter and return null
Check : https://developer.android.com/reference/androidx/viewpager2/adapter/FragmentStateAdapter
Please note that this should not be the recommended way of disabling the state, this should work for some use cases but for production apps consider saving the state of the fragment or re-emiting the state to the UI from a state holder like viewmodel
Upvotes: 26
Reputation: 582
All you have to do is to set your viewpager2 in the layout file with the following property
android:saveEnabled="true"
Upvotes: 0
Reputation: 2511
My problem solved with implementing new Navigation Component. It itself handles fragment transition. Anyone who across with this problem can change their navigation method.
Upvotes: -3