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

12

113

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?

Thom answered 2/8, 2016 at 13:48 Comment(1)
apparently, the android ViewPager will promote a NullPointerException inside onCreateView of the fragment inside the pager to a IllegalStateException : FragmentManager is already executing transactions ... just in case someone drops by and needs to knowDebor
A
264

Simply use childFragmentManger() for viewpager inside a Fragment

mPagerAdapter = new ScreenSlidePagerAdapter(getChildFragmentManager());
mPager.setAdapter(mPagerAdapter);
Adrenocorticotropic answered 27/11, 2016 at 13:13 Comment(4)
Dude! You saved my day!! :D Thanks a ton! I was using Nested fragments and your "getChildFragmentManager" trick worked like charm! ^_^Margartmargate
This worked in the case where going 'back' (via back button) to the activity/fragment combo was crashing as well. Good call.Picked
Thanks!! My app crashed when i used Apply Changes and Restart Activity as run button.Hark
This is right solution if you are using nested fragments and reusing the same fragment manager.Selfhypnosis
E
89

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);
    }
});
Edition answered 31/1, 2017 at 9:10 Comment(6)
Simple and effective. I have used postDelayed though. In my use case it was even better, since a lot of stuff is going on on initialisation phaseMirk
This worked, but I wonder how does this fix the issue internally?Vallie
@Vallie It will post a task to the main thread's queue rather than trying to open the fragment forcefully. So the queue will process fragment commits one by one.Exemplify
Thanks for the effective solution, Handler() is deprecated, instead you can use Handler(Looper.getMainLooper())Orthotropous
You are correct, I updated the answer.Edition
very correct and easy, I actually used post Delayed just to be double sureUnassailable
R
28

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()

Railing answered 2/8, 2016 at 13:56 Comment(4)
FragmentManager.executePendingTransactions() will also cause the exception, as inside its code there is a call to execPendingActions() --> ensureExecReady(true) --> if (mExecutingActions) { throw new IllegalStateException("FragmentManager is already executing transactions"); }Humic
This not work for me. The two methods show the crash: java.lang.IllegalStateException: FragmentManager is already executing transactionsBuild
commitNow along with addToBackStack usage also failed :(Thyestes
Anyone using this should know that it's a blocking method, running it in the UI thread will make your app freeze while transitioning. M.Paunov's and Hitesh Sahu's answers are reliable for a production ready app.Ellerd
S
13

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.

Sprig answered 30/3, 2019 at 8:20 Comment(0)
F
11

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.

Famished answered 21/2, 2021 at 23:53 Comment(4)
Had exact same issue (around same time). Would have never figured reading other answers. Thank you :)Bever
viewLifecycleOwner not found in Ativity. Any ideaGauleiter
@PankajKumar When calling viewLifecycleOwner basically you are calling the getViewLifecycleOwner() method from the androidx.fragment.app.Fragment. In this case (I am not sure - but worth to try) You could try to call supportFragmentManager | getSupportFragmentManager() to get the FragmentManager ans just call lifecycle | getLifecycle() from the activity.Famished
public FragmentStateAdapter(@NonNull Fragment fragment) I use this constructor solved my problemCromorne
H
5

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)
Hanzelin answered 7/2, 2021 at 8:30 Comment(0)
S
3

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)
                }
            }
Seko answered 1/2, 2020 at 2:18 Comment(2)
Thanks this was helpful, but now I can open the fragment only once if I got back to it, it crashes.Publus
@AbhishekAN I would need to see more of your code to help you out more. Hope you solve your problem mate.Seko
M
1

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})

Mouseear answered 26/11, 2018 at 23:34 Comment(0)
M
1

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

Mcgough answered 25/9, 2019 at 5:3 Comment(0)
H
0

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.

Hettiehetty answered 20/8, 2021 at 14:7 Comment(0)
C
0

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

Clubbable answered 7/9, 2021 at 10:50 Comment(0)
U
0

In my case, using commitNow() or executePendingTransactions() was the issue.

These methods both throws IllegalStateException when supportFragmentManager is already executing transactions on invocation.

Unless you are required to execute transactions immediately, just use commit() and let the system decide when to execute the transaction.

Uranous answered 25/4 at 6:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.