FragmentManager is already executing transactions. When is it safe to initialise pager after commit?
AndroidAndroid FragmentsAndroid Problem Overview
I have an activity hosting two fragments. The activity starts off showing a loader while it loads an object. The loaded object is then passed to both fragments as arguments via newInstance methods and those fragments are attached.
final FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
trans.replace(R.id.container1, Fragment1.newInstance(loadedObject));
trans.replace(R.id.container2, Fragment2.newInstance(loadedObject));
trans.commit();
The second fragment contains a android.support.v4.view.ViewPager and tabs. onResume we initialise it like follows
viewPager.setAdapter(adapter);
viewPager.setOffscreenPageLimit(adapter.getCount()); //the count is always < 4
tabLayout.setupWithViewPager(viewPager);
The problem is android then throws
> java.lang.IllegalStateException: FragmentManager is already executing > transactions
With this stack trace: (I took android.support
out of the package names just for brevity)
> v4.app.FragmentManagerImpl.execSingleAction(FragmentManager.java:1620) > at > v4.app.BackStackRecord.commitNowAllowingStateLoss(BackStackRecord.java:637) > at > v4.app.FragmentPagerAdapter.finishUpdate(FragmentPagerAdapter.java:143) > at v4.view.ViewPager.populate(ViewPager.java:1235) > at v4.view.ViewPager.populate(ViewPager.java:1083) > at > v4.view.ViewPager.setOffscreenPageLimit(ViewPager.java:847)
The data shows if setOffscreenPageLimit(...);
is removed. Is there another way to avoid this issue?
When in the lifecycle is the fragment transaction complete so that I can wait to setup my pager?
Android Solutions
Solution 1 - Android
Simply use childFragmentManger()
for viewpager
inside a Fragment
mPagerAdapter = new ScreenSlidePagerAdapter(getChildFragmentManager());
mPager.setAdapter(mPagerAdapter);
Solution 2 - Android
I had this exception when quickly replacing 2 fragments AND using executePendingTransactions()
. Without calling this there was no exception.
What was my case?
I open a fragment A and in its onResume()
(under a condition) I ask the activity to replace the fragment with fragment B. At that point the exception occurs.
My solution was to use a Handler.post(runnable)
which places the query on the end of the thread queue instead of running it immediately. This way we ensure that the new transaction will be executed after any previous transactions are completed.
So my solution was as simple as:
Handler uiHandler = new Handler(Looper.getMainLooper());
uiHandler.post(new Runnable()
{
@Override
public void run()
{
openFragmentB(position);
}
});
Solution 3 - Android
If you're targeting sdk 24 and above you can use:
FragmentTransaction.commitNow()
instead of commit()
If you're targeting older versions, try calling:
FragmentManager.executePendingTransactions()
after the call to commit()
Solution 4 - Android
I Had a similar issue,
A mainAcitivity adding fragmentA.
Then fragmentA callback mainactivity to replace itself with fragmentB.
MainActivity throw exception fragmentmanager already executing transaction when replace and commit transaction with fragmentB.
The issue actually comes from fragmentB.
I have a TabHost in fragment B which require getfragmentmanager()
to add tabfragment.
Replace getfragmentmanager()
by getchildfragmentmanager()
in fragmentB solve the issue.
Solution 5 - Android
I had same problem. However, I was using the FragmentStateAdapter
constructor that gets only the FragmentActivity
:
package androidx.viewpager2.adapter;
public FragmentStateAdapter(
@NonNull FragmentActivity fragmentActivity
) {
this(fragmentActivity.getSupportFragmentManager(), fragmentActivity.getLifecycle());
}
Even though it gets the Lifecycle from fragmentActivity
instance, it was not working at all.
Then, diving into the code of the FragmentStateAdapter
, I saw there is another constructor where you can pass the Lifecycle instance.
package androidx.viewpager2.adapter;
public FragmentStateAdapter(
@NonNull FragmentManager fragmentManager,
@NonNull Lifecycle lifecycle
) {
mFragmentManager = fragmentManager;
mLifecycle = lifecycle;
super.setHasStableIds(true);
}
So, I changed my PageAdapter
constructor to receive the FragmentManager
as childFragmentManager
and the LifeCycle from the viewLifecycleOwner
. And pass those parameters to the FragmentStateAdapter
constructor:
class MyPagerAdapter(
fragmentManager: FragmentManager,
lifecycle: Lifecycle
) : FragmentStateAdapter(fragmentManager, lifecycle) {
//...
}
Then, when calling my PagerAdapter constructor, I pass the FragmentManager and
I get the lifeCycle from the viewLifecycleOwner
:
val myPagerAdapter = MyPagerAdapter(
fragmentManager = childFragmentManager,
lifecycle = viewLifecycleOwner.lifecycle
)
[UPDATE] MyPagerAdapter is set from a Fragment.
Solution 6 - Android
ViewPager2
It is quite an old answer from the time of ViewPager
and has helped many to solve the issue who came across this. Just in case you are having ViewPager2
and face similar issue then,
We know this happens when we have ViewPager2
inside a fragment and FragmentStateAdapter
class has a constructor with Fragment
parameter, so you can use that to create your adapter.
Like,
class ScreenSlidePagerAdapter(fragment: Fragment): FragmentStateAdapter(fragment)
Then,
val pagerAdapter = ScreenSlidePagerAdapter(this)
Solution 7 - Android
Got a similar error not connected to question but hops its helps someone when working with firebase.
Remove activity
, requireActivity
or this from .addSnapshotListener()
, keep it blank.
videosListener = videosCollectionRef
.addSnapshotListener() { snapshot, exception ->
if (exception != null) {
Log.e("Exception", "Could not retrieve documents: $exception")
}
if (snapshot != null) {
parseData(snapshot)
}
}
Solution 8 - Android
If anyone is using Robolectric >3.6 and at least through 4.0.2 with a ViewPager you may see this even with correct code.
There is related information in this github issue tracking the problem for Robolectric.
The issue is not resolved as I write this answer, and the only workarounds known are to use @Config(sdk={27}) which appears to work for some but did not work for me, or implement a ViewPagerShadow with a workaround in your test package with the rather-long code referenced on github (I can include it here if that is better but this may not be relevant enough as an answer?) and use @Config(shadows={ShadowViewPager.class})
Solution 9 - Android
I used/extended my adapter with FragmentStatePagerAdapter
instead of FragmentPagerAdapter
this resolved my issue.
Use FragmentStatePagerAdapter
if the fragments are dynamic and are changing frequently during run-time.
Use FragmentPagerAdapter
if the fragments are static and do NOT change frequently during run-time.
enlightened from this article, https://medium.com/inloopx/adventures-with-fragmentstatepageradapter-4f56a643f8e0
Solution 10 - Android
I had the same issue. Navigation from one fragment to another by add method. The problem was that I added fragment transition in the onViewCreadted
method. Then I tried to move the transition in onResume
method. That also does not help. So I added transition half-second after the resume. After that everything was fine.
It was a small crush that affected less than half percent of my users. Even though, it was very annoying.
Solution 11 - Android
Just do not call
FragmentTransaction#commit()
from fragment which is already been in the same FragmentManager
at another transaction process
For ex:
Activity:
override fun onCreate(savedInstanceState: Bundle?) {
val fragment = MyFragment()
setFragment(fragment)
}
fun setFragment(fragment: Fragment){
supportFragmentManager.beginTransaction()
.replace(...,fragment, ...)
.commit()
}
MyFragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
/*
* here error will occurs, because the fragment MyFragment is in current transaction
*/
activity?.setFragment(AnotherFragment())//error
}
Solution:
Do this:
Activity:
override fun onCreate(savedInstanceState: Bundle?) {
setFragment(MyFragment())
...
setFragment(AnotherFragment())
}
fun setFragment(fragment: Fragment){
supportFragmentManager.beginTransaction()
.replace(...,fragment, ...)
.commit()
}