IllegalStateException: Can't change container ID of Fragment

匿名 (未验证) 提交于 2019-12-03 09:06:55

问题:

Android platform: 3.1

I am trying to move a fragment from a container A to a container B. Here follows the code to accomplish this:

private void reattach(int newContainerId, Fragment frag, String tag) {        if (frag == null || !frag.isAdded() || (frag.getId() == newContainerId)) { return; }        final FragmentManager fm = getFragmentManager();       FragmentTransaction ft = fm.beginTransaction();       ft.remove(frag);     //stacco il frammento dal container A       ft.commit();       fm.executePendingTransactions();        ft = fm.beginTransaction();       ft.add(newContainerId, frag, tag); //attacco il frammento sul container D       ft.commit();       fm.executePendingTransactions();       } 

When I run the system, I get the following IllegalStateException:

03-26 00:13:14.829: E/AndroidRuntime(30090): java.lang.RuntimeException: Unable to start activity ComponentInfo{eu.areamobile.apps.sfa/eu.areamobile.apps.sfa.activity.HomeActivity}: java.lang.IllegalStateException: Can't change container ID of fragment FragmentHomeController{408202a8 id=0x7f050010 HomeController}: was 2131034128 now 2131034132 03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1751) 03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1767) 03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:3117) 03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.app.ActivityThread.access$1600(ActivityThread.java:122) 03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1009) 03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.os.Handler.dispatchMessage(Handler.java:99) 03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.os.Looper.loop(Looper.java:132) 03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.app.ActivityThread.main(ActivityThread.java:4028) 03-26 00:13:14.829: E/AndroidRuntime(30090):    at java.lang.reflect.Method.invokeNative(Native Method) 03-26 00:13:14.829: E/AndroidRuntime(30090):    at java.lang.reflect.Method.invoke(Method.java:491) 03-26 00:13:14.829: E/AndroidRuntime(30090):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:844) 03-26 00:13:14.829: E/AndroidRuntime(30090):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:602) 03-26 00:13:14.829: E/AndroidRuntime(30090):    at dalvik.system.NativeStart.main(Native Method) 03-26 00:13:14.829: E/AndroidRuntime(30090): Caused by: java.lang.IllegalStateException: Can't change container ID of fragment FragmentHomeController{408202a8 id=0x7f050010 HomeController}: was 2131034128 now 2131034132 03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.app.BackStackRecord.doAddOp(BackStackRecord.java:338) 03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.app.BackStackRecord.add(BackStackRecord.java:316) 03-26 00:13:14.829: E/AndroidRuntime(30090):    at eu.areamobile.apps.sfa.activity.HomeActivity.reattach(HomeActivity.java:340) 03-26 00:13:14.829: E/AndroidRuntime(30090):    at eu.areamobile.apps.sfa.activity.HomeActivity.customHideShowCreate(HomeActivity.java:253) 03-26 00:13:14.829: E/AndroidRuntime(30090):    at eu.areamobile.apps.sfa.activity.HomeActivity.customHideShowCreate(HomeActivity.java:155) 03-26 00:13:14.829: E/AndroidRuntime(30090):    at eu.areamobile.apps.sfa.activity.HomeActivity.onPostCreate(HomeActivity.java:66) 03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.app.Instrumentation.callActivityOnPostCreate(Instrumentation.java:1111) 03-26 00:13:14.829: E/AndroidRuntime(30090):    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1734) 

After a quick debug, I noticed that on Android 3.1 the FragmentTransaction.remove doesn't set to 0 the mContainerId of the fragment being removed, while on ICS it works correctly.
Any suggestions or workarounds?

回答1:

My solution to this problem is to re-create the fragment while keeping its state:

FragmentTransaction ft = mFragmentManager.beginTransaction(); ft.remove(old); Fragment newInstance = recreateFragment(old); ft.add(R.id.new_container, newInstance); ft.commit(); 

With the following helper function:

private Fragment recreateFragment(Fragment f)     {         try {             Fragment.SavedState savedState = mFragmentManager.saveFragmentInstanceState(f);              Fragment newInstance = f.getClass().newInstance();             newInstance.setInitialSavedState(savedState);              return newInstance;         }         catch (Exception e) // InstantiationException, IllegalAccessException         {             throw new RuntimeException("Cannot reinstantiate fragment " + f.getClass().getName(), e);         }     } 

It works for me, at least with the latest support library (r11), although I didn't test much yet.

The extra cost is to instantiate the fragment twice.



回答2:

after trying all the answers from similar questions, looks like i've found a way to do the trick.

First issue - you really have to commit and execute remove transaction before trying to add fragment to another container. Thanks for that goes to nave's answer

But this doesn't work every time. The second issue is a back stack. It somehow blocks the transaction.

So the complete code, that works for me looks like:

manager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); manager.beginTransaction().remove(detailFragment).commit(); manager.executePendingTransactions(); manager.beginTransaction()     .replace(R.id.content, masterFragment, masterTag)     .add(R.id.detail, detailFragment, activeTag)     .commit();               


回答3:

I had a similar problem and calling manager.executePendingTransactions() before adding the fragment for the second time did the trick for me. Thanks!



回答4:

Based on Fragment Developer Guide which it states:

If you do not call addToBackStack() when you perform a transaction that removes a fragment, then that fragment is destroyed when the transaction is committed and the user cannot navigate back to it. Whereas, if you do call addToBackStack() when removing a fragment, then the fragment is stopped and will be resumed if the user navigates back

So I assume that your frag object is destroyed after the calls to

ft.remove(frag);     //stacco il frammento dal container A ft.commit(); 

But you say that it works in ICS and this is strange. Why don't you try the replace method and see what's happens?

Hope this helps...



回答5:

Try to use replace() method..If not don't use commit twice.



回答6:

I'll need to prototype something later if this doesn't help but one thing I would try is not doing this as separate transactions because even though you call executePendingTransactions() it shouldn't be a blocking call, so while it's doing its thing the rest of your code will begin to execute, which includes trying to add the Fragment to a new container while it might still exist in its current container.

Since you're doing all of this from a single method, you could try doing the remove and add in a single FragmentTransaction, so that you know it will execute in the order that you want it to, and so that you know when the Fragment has been removed from one container and can add it to a new one and then commit. Else, if you really want to do it as two separate transactions, the base Fragment class has an isRemoving() method that will tell you if the Fragment is being removed from it's container and you can wait until that becomes false so that you know it's been removed. That said, you're starting to introduce synchronization issues if now one transaction depends on another before it can execute, and I don't see any reason not to just to the remove and add as a single transaction.

Thanks, David



回答7:

Is there any valid reason aside from poor programming principals, that you'd want to keep a reference to an existing fragment across an activity or two anyway? Rather than saving the state of the fragment and simply recreating it?

I was creating fragments using:

    public static ContactsFragment mContactsFragment;      public static ContactsFragment getInstance() {     if (mContactsFragment == null) {         mContactsFragment = new ContactsFragment();     }     return mContactsFragment; } 

I've done this since I learned Android but can't see why I'd want to refer to my fragment statically despite it being in everyone's examples. Originally I thought it was to refer to a Context statically, but you can always set this when you create a new fragment and saving a context anyway often leads to odd bugs later anyway.

Why wouldn't you:

    public static ContactsFragment newInstance(Context c) {     ContactsFragment mContactsFragment = new ContactsFragment();     mContext = c;     return mContactsFragment; } 

and simply save any work you've done on the fragment and recreate it when you need it again? Perhaps I can imagine if you were creating 100+ (news app?) you might not want to have to create them each time, rather store them in memory? Anyone?



回答8:

So, i had same issue, and solved the problem on a different way (at least a bit of). My App has 4 fragments and i need to manage when my device is on Landscape Mode.

let me explain my app and the solution:

My App i have 4 fragments in my app: 1 - Recipes (which contain a list of recipes) 2 - Ingredients (a list of ingredients from select recipe) 3 - Steps (list of steps) 4 - Player (a fragment that’s called to play videos)

On portrait mode i had no issue, all works fine!

Landscape My activity have 2 containers for my fragments, one on left side wich contains my list of recipes, and another on right side. This container on right side, will dinamically change .

I'm having this issue, when, on portrait mode, my fragment is showing, and when i rotate my device, i want the same fragment that was showing to go to detail container. And thats where all my problems started.

My Solution

  1. Save the last fragment showed in a variable;
  2. Save the state fragment onSavedInstance;
  3. In onCreate retrieve the fragment and the last fragment showed.

So, to be more especific i created this gist to help https://gist.github.com/jillesRagonha/4c184165b92fdf026ab3cd033b64b1bf

hope this help anyone :D



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!