FragmentManager is already executing transactions. When is it safe to initialise pager after commit?

AndroidAndroid Fragments

Android 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 FragmentStatePagerAdapterif 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()    
     }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionNick CardosoView Question on Stackoverflow
Solution 1 - AndroidHitesh SahuView Answer on Stackoverflow
Solution 2 - AndroidM.PaunovView Answer on Stackoverflow
Solution 3 - AndroidmarmorView Answer on Stackoverflow
Solution 4 - AndroidAlexandre LevieuxView Answer on Stackoverflow
Solution 5 - AndroidJesse LimaView Answer on Stackoverflow
Solution 6 - AndroidgprathourView Answer on Stackoverflow
Solution 7 - AndroidthesamoanppprogrammerView Answer on Stackoverflow
Solution 8 - AndroidMike HardyView Answer on Stackoverflow
Solution 9 - AndroidKarthik HView Answer on Stackoverflow
Solution 10 - AndroidKirk_heheView Answer on Stackoverflow
Solution 11 - AndroidNickUnuchekView Answer on Stackoverflow