片段管理器已经在执行事务。什么时候提交后初始化页导航是安全的?

我有个活动,里面有两个片段。活动在加载对象时开始显示加载程序。然后通过 newInstance 方法将加载的对象作为参数传递给这两个片段,并附加这些片段。

final FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
trans.replace(R.id.container1, Fragment1.newInstance(loadedObject));
trans.replace(R.id.container2, Fragment2.newInstance(loadedObject));
trans.commit();

第二个片段包含 android.support. v4.view. ViewPager 和 tab. onResume,我们按照如下方式初始化它

viewPager.setAdapter(adapter);
viewPager.setOffscreenPageLimit(adapter.getCount()); //the count is always < 4
tabLayout.setupWithViewPager(viewPager);

问题是机器人会抛出

IllegalStateException: FragmentManager 已经在执行 交易

使用这个堆栈跟踪: (为了简单起见,我从包名中去掉了 android.support)

应用程序 v4.app. FragmentManagerImp.ExecSingleAction (FragmentManager.java: 1620) 在 App.BackStackRecord.committee NowAllowingStateloss (BackStackRecord.java: 637) 在 V4.app. FragmentPagerAdapter.finishUpdate (FragmentPagerAdapter.java: 143) (ViewPager.java: 1235) (ViewPager.java: 1083) 在 V4.view. ViewPager.setOffscreen Pagelimit (ViewPager.java: 847)

数据显示是否删除了 setOffscreenPageLimit(...);。是否有其他方法来避免这个问题?

在生命周期中,什么时候片段事务完成了,以便我可以等待安装我的寻呼机?

102683 次浏览

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

Simply use childFragmentManger() for viewpager inside a Fragment

mPagerAdapter = new ScreenSlidePagerAdapter(getChildFragmentManager());
mPager.setAdapter(mPagerAdapter);

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

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

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.

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

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

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)

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
)

MyPagerAdapter is set from a Fragment.

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.

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