12

Can i first add a Fragment to a View, then "detach" it, and then "re-attach" it to another View?

In code, i want to:

fragOne one = new fragOne();
getSupportFragmentManager().beginTransaction()
        .add(R.id.left, one, "tag").commit();

getSupportFragmentManager().beginTransaction()
        .detach(one).commit();      // or .remove(), or .addToBackStack(null).remove()

getSupportFragmentManager().executePendingTransactions();

getSupportFragmentManager().beginTransaction()
        .add(R.id.right, one).commit();

But it throws error:

04-05 13:28:03.492: E/AndroidRuntime(7195): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.trybackstack/com.example.trybackstack.MainActivity}: java.lang.IllegalStateException: Can't change container ID of fragment fragOne{40523130 #0 id=0x7f080000 tag}: was 2131230720 now 2131230721

Thanks for help!

midnite
  • 5,157
  • 7
  • 38
  • 52
  • You are facing this exception because you are using two different id's for fragment container(i.e R.id.right and R.id.left) for adding the "Fragment one".Use one single id for container.. – Karan_Rana Apr 05 '13 at 05:45
  • 1
    Indeed. But i want to move that fragment from left to right. – midnite Apr 05 '13 at 05:53
  • In that case you need to commit the previous transaction first and then again begin a new transaction to add the same fragment to different view – Karan_Rana Apr 05 '13 at 05:57
  • I think i did so, as what you said. The last line is the one causing that exception. It is a new transaction. – midnite Apr 05 '13 at 08:54
  • i just tried a new solution to the problem. i think it will work..come on chat – Karan_Rana Apr 05 '13 at 09:00
  • @midnite did u found a solution? – Accollativo Sep 12 '13 at 15:10
  • @Accollativo: Not yet. But i will look into the source code of Android and find the solution! (or find out why it is impossible to do so.) i am currently busying on another project. Meanwhile, you may also look into the source code for the solution, and post it below :-) – midnite Sep 12 '13 at 20:55

6 Answers6

17

I had the same problem but I found that what I really needed was to reparent the fragment's view and not the fragment itself, thus avoiding any fragmentManager transaction.

View vv = fragment.getView();
ViewGroup parent = (ViewGroup)vv.getParent();
parent.removeView(vv);
newparent.addView(vv, layoutParams);
befstrat
  • 449
  • 4
  • 7
  • 1
    This is the best solution because the view hierarchy is not destroyed and recreated – Greg Ennis Dec 06 '14 at 17:58
  • 1
    Yes I totally agree, because the container is a simple FrameLayout in most senarios, and this approach avoids playing with the FragmentManager and Fragment lifecycles. – Hai Zhang Feb 19 '15 at 11:53
  • 2
    This did not work for me since I want to move the fragment from one parent to another on orientation change. So after orientation change the fragment's host view is destroyed. – Nabster Nov 08 '18 at 01:05
  • Dude you rock. Working like a charm. Combined this response with https://medium.com/cs-random-thoughts-on-tech/android-retain-webview-on-orientation-change-best-practices-be29fd7e56e3 and now i have a webview that can be teleported anywhere in the app without losign state... Thanks!! – RegularGuy Jan 20 '22 at 22:51
14

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, manager.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();              
Community
  • 1
  • 1
Yan.Yurkin
  • 970
  • 8
  • 15
  • 3
    This causes the view to be destroyed and recreated, not preserved – Greg Ennis Dec 06 '14 at 17:57
  • In some cases this won't work. I got better results with the sequence in nave's original answer: remove fragments, kill backstack and then add fragments. – Theo Dec 13 '14 at 19:01
9

I guess you would have this figured out by now, but i dont't see any satisfactory answer. So, I'm posting this for others who may refer to this in the future.

If you want to move a fragment from one view to another you do the following:

android.app.FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.remove(fragment1);
fragmentTransaction.commit();
fragmentManager.executePendingTransactions();
fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.containerToMoveTo, fragment1);
fragmentTransaction.commit();

This way you do not have to duplicate the fragment.

nave
  • 428
  • 5
  • 19
  • 1
    It doesn't work... Error: java.lang.RuntimeException: Unable to start activity ComponentInfo: java.lang.IllegalStateException: Can't change container ID of fragment SubMenuFragment{419b9c60 #1 id=0x7f050036}: was 2131034166 now 2131034167 – Accollativo Sep 12 '13 at 13:21
  • 1
    Actually that does work. Tested on Nexus7/2012/4.4 and Galaxy SII/4.1.2. What am i doing wrong? =) – mjollneer Dec 04 '13 at 08:46
  • 1
    Looks like this works only if you have an empty back stack. Here is improved code: http://stackoverflow.com/a/21328919/1855764 – Yan.Yurkin Jan 24 '14 at 09:26
  • 3
    This causes the view to be destroyed and recreated, not preserved – Greg Ennis 41 secs ago edit – Greg Ennis Dec 06 '14 at 17:59
3

Please check the solution,you need to create the new instance of the same fragment and instantiate it with state of the old fragment if you want to save the state of the old fragment.

 FragmentTransaction ft = mFragmentManager.beginTransaction();
    ft.remove(one);
    Fragment newInstance = fetchOldState(one);
    ft.add(R.id.right, newInstance);
    ft.commit();


//TO fetch the old state
    private Fragment fetchOldState(Fragment f)
        {
            try {
                Fragment.SavedState oldState= mFragmentManager.saveFragmentInstanceState(f);

                Fragment newInstance = f.getClass().newInstance();
                newInstance.setInitialSavedState(oldState);

                return newInstance;
            }
            catch (Exception e) // InstantiationException, IllegalAccessException
            {

            }
        }
Karan_Rana
  • 2,813
  • 2
  • 26
  • 35
  • 1
    Thanks for your reply @Karan_Rana! Your method works. But it is actually _duplicating_ the fragment, but not really _moving_ it. I have also found a few people concerning about this. I am not sure. But it seems for a Fragment, once it is added, we can't "re-add" it. Duplicating it seems to be the best method I have found so far. Yet it might lead to a potential problem: references of it may not work anymore. – midnite Apr 06 '13 at 23:34
0

I have run into that problem as well. Sometimes moving fragments works, sometimes you have to create a new instance. It seems that moving fragments does not work, if the fragment keeps being in the "isRemoving" state. Being removed also seems to be prevented by having a fragment in the backstack.

Burtan
  • 41
  • 4
0

Adding to the Yan. Yurkin's answer. Make sure not to have any transitions applied to the transaction as these seem to delay the fragment from being detached.

Community
  • 1
  • 1
bkant
  • 157
  • 1
  • 11