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!
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);
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();
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.
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
{
}
}
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.
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.
Related
I think the issue is probably simple, but being pretty new to Java and to Android dev, I'm not really sure. What I'm trying to do is close a fragment that's been toggled on using a ToggleButton, but I can't find a way to then hide or close it. Any help would be appreciated.
Below is the code for the OnCheckChanged() listener.
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
MyFragment frag = new MyFragment();
FragmentManager manager = getFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.add(R.id.main_activity, frag, "Fragment1");
if(isChecked)
{
transaction.show(frag);
}
else
{
transaction.hide(frag);
//transaction.remove(frag);
}
transaction.commit();
}
Every time you hit onCheckedChanged, you are adding a NEW fragment and then either hiding or showing it. All other fragments you have previously created are still attached laid one on top of the other.
You should get the current fragment that is attached to the layout if there is one. If there isn't one already, create it and add it to the container.
For example, this should point you in the right direction.
private void HideFragment(){
// Get the fragment that is attached to the layout
Fragment frag=getFragmentManager().findFragmentById(R.id.centerFrame);
if (frag==null){
// The fragment does not exist yet so create one and add it
frag=new MyFragment();
getFragmentManager()
.beginTransaction()
.replace(R.id.centerFrame,frag)
.commit();
}
//Hide the fragment
getFragmentManager()
.beginTransaction()
.hide(frag)
.commit();
}
Update:
It is a little ambiguous what you are actually trying to achieve. You keep saying "close" the fragment but your code suggests you just want to toggle its visibility on and off.
If you actually want to completely remove the fragment then you should use the Remove method of the FragmentTransaction.
I am using navigation drawer and it is simple to use. I am not providing the complete code but providing you detail which could be easy for you to understand my problem. I am using fragments these are about 8 in numbers and I am replacing them with one an other. But here comes a problem
I am replacing them on click event of the navigation drawer. but there are two main problems
After replacement , I can see the previous fragment in the background. does replace method just call the new fragment over it ? if yes then what should I do to old fragment not be visible in the background of my new fragment.
When I click navigation drawer Item , it loads the specific fragment successfully. but keeping in that fragment when I click to that specific item again it loads this fragment again and again. For example if drawer item num 3 opens fragment MyBook , then by clicking item num three 2 or many times would open fragment that much time.
So please some one answer me how to cure my app for such kind of actions which I described above.
I tried like this. Its working fine me
FragmentManager frgmanager = getFragmentManager();
frgmanager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
FragmentTransaction frgTransaction = frgmanager.beginTransaction();
if(subitem.equalsIgnoreCase("subitem")){
Frag1 frg1 =new Frag1(mCtx);
frgTransaction.replace(R.id.inflate_layout, frg1);
}else if(subitem1.equalsIgnoreCase("subitem1")){
Frag2 frg2 =new Frag2(mCtx);
frgTransaction.replace(R.id.inflate_layout, frg2);
}else{
Frag2 frg3 =new Frag3(mCtx);
frgTransaction.replace(R.id.inflate_layout, frg3);
}
frgTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
frgTransaction.commit();
you can use addtobackstack in fragmentstranstion object.like
FragmentManager manager = getFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.bodyfragment, new AnotherFragment());
transaction.addtoBackStack(null).commit();
Use replace-method of FragmentTransaction instead of add (http://developer.android.com/guide/components/fragments.html#Transactions)
FragmentManager manager = getFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.bodyfragment, new AnotherFragment());
transaction.commit();
To avoid re-instantiating the fragment, keep track of the current open fragment and only do a fragment transaction, if we next-to-be-opened fragment is a different one than the current.
This may achieved like the following:
class MyActivity ... {
private String currentFragment;
private void openNewFragment(Fragment fragment) {
String newFragment = fragment.getClass().getSimpleName();
if (newFragment.equals(currentFragment)){
// new fragment already shown
return;
}
// Fragment transaction etc here:
}
}
Note that this only compares fragments based in their class name. Sometimes this might not be unique, e.g. if there is a DetailFragment class which displays information about an entity. Which entities details to show may depend on intent arguments.
The above code however will then prevent opening DetailFragment for Entity=1 if currently details for Entity=2 are shown. For these scenarios the information about the fragment kept needs to be extended (e.g. storing a Reference or WeakReference to the fragment instance itself).
I'm trying to learn how to use Fragments in android.
I'm trying to remove old fragment when new fragment is calling in android.
You need to find reference of existing Fragment and remove that fragment using below code. You need add/commit fragment using one tag ex. "TAG_FRAGMENT".
Fragment fragment = getSupportFragmentManager().findFragmentByTag(TAG_FRAGMENT);
if(fragment != null)
getSupportFragmentManager().beginTransaction().remove(fragment).commit();
That is it.
I had the same issue. I came up with a simple solution. Use fragment .replace instead of fragment .add. Replacing fragment doing the same thing as adding fragment and then removing it manually.
getFragmentManager().beginTransaction().replace(fragment).commit();
instead of
getFragmentManager().beginTransaction().add(fragment).commit();
I had the same issue to remove old fragments. I ended up clearing the layout that contained the fragments.
LinearLayout layout = (LinearLayout) a.findViewById(R.id.layoutDeviceList);
layout.removeAllViewsInLayout();
FragmentTransaction ft = getFragmentManager().beginTransaction();
...
I do not know if this creates leaks, but it works for me.
Probably you instance old fragment it is keeping a reference. See this interesting article Memory leaks in Android — identify, treat and avoid
If you use addToBackStack, this keeps a reference to instance fragment avoiding to Garbage Collector erase the instance. The instance remains in fragments list in fragment manager. You can see the list by
ArrayList<Fragment> fragmentList = fragmentManager.getFragments();
The next code is not the best solution (because don´t remove the old fragment instance in order to avoid memory leaks) but removes the old fragment from fragmentManger fragment list
int index = fragmentManager.getFragments().indexOf(oldFragment);
fragmentManager.getFragments().set(index, null);
You cannot remove the entry in the arrayList because apparenly FragmentManager works with index ArrayList to get fragment.
I usually use this code for working with fragmentManager
public void replaceFragment(Fragment fragment, Bundle bundle) {
if (bundle != null)
fragment.setArguments(bundle);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
Fragment oldFragment = fragmentManager.findFragmentByTag(fragment.getClass().getName());
//if oldFragment already exits in fragmentManager use it
if (oldFragment != null) {
fragment = oldFragment;
}
fragmentTransaction.replace(R.id.frame_content_main, fragment, fragment.getClass().getName());
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
fragmentTransaction.commit();
}
I had issue with fragment onBackPress. I didn't want to return to old fragments.
This solution worked for me to remove old fragments from fragment manager :
FragmentManager fm = getSupportFragmentManager();
fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
You can also use an if block like this :
FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() > 0) {
fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
In Kotlin
val fragList = f.supportFragmentManager.fragments
for (fragment in fragList) {
f.supportFragmentManager.beginTransaction().remove(fragment).commit()
}
I was facing the same error with few views so I set the visibility of reductant views as GONE. This method solved my issue as charm.
<View>.setVisibility(View.GONE);
I have two fragments, I need to keep them both but show and hide on button clicks.
I added the first fragment using:
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
mDishFragment = new DishFragment();
transaction.add(R.id.dish_fragment, mDishFragment, "DishFragment");
transaction.commit();
First fragment (DishFragment) has a button on clicking of which the code checks if "OrderSummaryFragment" exists(using findFragmentbyTag), if it does, it should show() it else add() a new one. here is the code:
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
if (getFragmentManager().findFragmentByTag("OrderSummaryFragment") == null) {
System.out.println("OrderSummaryFragment not found");
transaction.add(R.id.dish_fragment, mOrderSummaryFragment,"OrderSummaryFragment");
System.out.println("Orderfragment added");
transaction.addToBackStack(null);
transaction.commit();
}else{
System.out.println("OrderSummaryFragment found");
transaction.hide(mDishFragment);
transaction.show(mOrderSummaryFragment);
transaction.commit();
}
For the first time since "OrderSummaryFragment" doesn't exist, the code adds one and it is displayed. There is a back button on the "OrderSummaryFragment" pressing of which show() up the first fragment "DishFragment".
The second time, since we have already added the "Ordersummaryfragment" previously, the findFragmentByTag should return OrderSummaryFragment but it returns null instead.
Note: I am not using replace() cause I want to reuse both of these fragments.
Hope someone can help me out.
Your fragment should search for your fragments using the getSupportFragmentManager() instead of the getFragmentManager Method() in your if structure.
BTW, What is that R.id.dish_fragment object? A fragment? A container? It should be a container like a LinearLayout.
I use this method on my container Activity to show a BFrag
public void showBFrag()
{
// Start a new FragmentTransaction
FragmentTransaction fragmentTransaction = mFragmentMgr.beginTransaction();
if(mBFrag.isAdded())
{
Log.d(LOG_TAG, "Show() BFrag");
fragmentTransaction.show(mBFrag);
}
else
{
Log.d(LOG_TAG, "Replacing AFrag -> BFrag");
fragmentTransaction.replace(R.id.operation_fragments_frame, mBFrag);
}
// Keep the transaction in the back stack so it will be reversed when backbutton is pressed
fragmentTransaction.addToBackStack(null);
// Commit transaction
fragmentTransaction.commit();
}
I call it from my container Activity; for the first time:
gets into the else statement and mBFrag replace mAFrag.
Then I press the back button:
and the operation is reversed (mAFrag is shown but.. does mBFrag is removed?).
Then I go forward again by calling showBFrag() from the same Activity:
and it gets AGAIN into the else statement. (so I can deduce that mBFrag is NOT ADDED)
but I got a Fragment already added IllegalStateException... (so why it didn't get into the if statement instead?)
So:
Why is the isAdded() method not returning TRUE if I'm getting a Fragment already added IllegalStateException??
Does popBackStack operation completely remove previously added fragments?
What behaviour am I misunderstanding?
EDIT:
Here is the complete info of the exception.
06-07 12:08:32.730: ERROR/AndroidRuntime(8576): java.lang.IllegalStateException: Fragment already added: BFrag{40b28158 id=0x7f0c0085}
06-07 12:08:32.730: ERROR/AndroidRuntime(8576): at android.app.BackStackRecord.doAddOp(BackStackRecord.java:322)
06-07 12:08:32.730: ERROR/AndroidRuntime(8576): at android.app.BackStackRecord.replace(BackStackRecord.java:360)
06-07 12:08:32.730: ERROR/AndroidRuntime(8576): at android.app.BackStackRecord.replace(BackStackRecord.java:352)
06-07 12:08:32.730: ERROR/AndroidRuntime(8576): at myPackageName.containerActivity.showBFrag() // This line: "fragmentTransaction.replace(R.id.operation_fragments_frame, mBFrag);"
In the end my workaround was to execute remove() of the previous fragment and add() the new one. Although that's what replace() method was meant to do.
But I am still guessing why replace() method didn't work properly in this case. It is really weird and I want to discard that it is because I am misunderstanding something or doing something wrong.
If the state of the activity has already been saved its no longer safe to call commit. You must call commitAllowingStateLoss() instead. Hope this helps!
Edit: ok I've taken a closer look at your issue, problem is you are trying to add a fragment that has already been added. Even if you use replace or remove calls you can't do this. Only work around I have found is to create a new instance of a fragment and add it every time. Once you remove or replace a fragment it is best to drop all of your references to it so the GC can take care of it.
Probably not related to this issue directly, but I've also noticed that setting a transition to the FragmentTransaction will cause an IllegalStateException, while not setting a transition will not.
Here's the bug for this issue: http://code.google.com/p/android/issues/detail?id=25598
I used this:
if (getFragmentManager().findFragmentByTag(newFragment.getClass().getName()) != null) {
transaction.remove(newFragment);
}
and added fragment with
MyFragment frag = new MyFragment();
transaction.add(R.id.container, frag, MyFragment.class.getName())
MyFragment.class.getName() stands for tag
Check whether the frament is already added or not using the method fragment.isAdded() Do replace or add the fragment accordingly
try this after fragmentTransection.replace()
fragmentTransection.addToBackStack(null);
fragmentTransection.commitAllowingStateLoss();
Removing setOffscreenPageLimit from Viewpager solved my issue.
Thanks.
I tried calling FragmentTransaction.remove() from onTabUnselected() and it worked around this bug.
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
ft.add(R.id.fragment_container, fragment, null);
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
ft.remove(fragment);
}
This code is working fine for me. try this
((MiActivity)getActivity()).addAccount = new AddAccount();
((MiActivity)getActivity()).addAccount.setArguments(params);
fragmentManager = getActivity().getSupportFragmentManager();
fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container((MiActivity)getActivity()).addAccount);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
Solved it by looping through my fragments and checking if isAdded() is true, then removing that fragment. More details here.
use list keep fragment instance, and judge this:
if (!mFragmentTags.contains(fragTag + "")) {
transaction.add(R.id.tab_main_container, mCurrentFragment, fragTag + "");
}