I have some simple code, where I am replacing a fragment with another fragment. The problem I have seen is that the new fragment never has any of its create /attach etc lifecycles called.
The code is as follows:
String SERVER="SERVER";
android.support.v4.app.FragmentManager fm = getChildFragmentManager();
FragmentTransaction ft = fm.beginTransaction().setCustomAnimations(R.anim.pop_enter, R.anim.pop_exit );
Fragment s = fm.findFragmentByTag(SERVER);
if(s == null ) s = new ServerFragment();
ft.replace(R.id.fragment_container, s, SERVER);
ft.show(s);
ft.commit();
boolean result = fm.executePendingTransactions();
//Validate if added
Fragment frag = fm.findFragmentByTag(SERVER);
frag.isAdded(); //Returns FALSE!
I would expect the isAdded method to return True and critically I would also expect the usual Fragment lifecycle methods to be invoked. This however is not the case.
Any thoughts would be greatly appreciated?
Regards
not positive here, but it seems like since you're creating a new Fragment each time and not explicitly destroying the old ServerFragments, it's likely that findFragmentByTag is finding the first Fragment created, which would not return true for isAdded() since it's been removed from the view hierarchy.
If that's correct, then you should see the isAdded() call return true the very first time that method is called, and not again after that
I believe there is a function to execute pending transactions that has the word immediate in it. It makes the transaction happen immediately rather than when the program gets to it.
Related
I am refactoring an Android App to use fragments. I have a fragment which I add to the layout with transaction.replace method but whose onCreateView method is not called. Code looks as follows:
FragmentManager fragmentManager = m_Activity.getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
m_StartNewGameFragment = new StartNewGameToggleButtonsFragment();
fragmentTransaction.replace(R.id.bottom_pane, m_StartNewGameFragment);
fragmentTransaction.commit();
String winnerText = null;
if (isComputerWinner)
winnerText = m_Activity.getResources().getText(R.string.computer_winner).toString();
else
winnerText = m_Activity.getResources().getText(R.string.player_winner).toString();
m_StartNewGameFragment.updateStats(computerWins, playerWins, winnerText);
The method updateStats of fragment is as follows:
public void updateStats(int computer_wins, int player_wins, String winnerText) {
System.out.println("Update stats " + m_ComputerWins);
m_ComputerWins.setText(Integer.toString(computer_wins));
m_PlayerWins.setText(Integer.toString(player_wins));
m_WinnerTextView.setText(winnerText);
}
When updateStats is called m_ComputerWins is null and the program crashes. m_ComputerWins is initialized inside the onCreateView method of the fragment which seems not to be called.
Can anyone please help ?
Take global variables in your fragment class for computer_wins, player_wins, winnerText and init them inside updateStats method.
then inside onViewCreated() method, set values like
m_ComputerWins.setText(Integer.toString(computer_wins));
m_PlayerWins.setText(Integer.toString(player_wins));
m_WinnerTextView.setText(winnerText);
You never know when the fragments is created (which calls onCreate()), because fragment's life cycle differs from your activity's. In your code, you are trying to update fragment from activity. But in the way you have written your code, it is almost guaranteed that your fragment is not created yet.
Search for how to pass parameters to fragment or update fragment to find out the correct way of updating fragment from activity. You can find several related question, e.g. this one.
FragmentTransaction only runs on the next event loop. If you want to immediately run a fragment transaction, use fragmentTransaction.commitNowAllowingStateLoss();.
Please note that this won't actually work correctly after process death, which is why you should use the fragment.setArguments(Bundle method to pass initial argument values to a fragment.
I have been using jfeinstein's SlidingMenu. I am currently trying to find if a certain fragment is visible to the user. I first tried:
if(mainfrag.isVisible()){
Log.d("Frag","Main is visible");
}else{
Log.d("Frag","Main is NOT visible");
}
Which always printed that the fragment was NOT visible. I then tried:
android.support.v4.app.FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
Log.d("Frag","CurFrag: "+fragmentManager.findFragmentById(R.id.content_frame).toString());
MainFragment mf = new MainFragment();
if(fragmentManager.findFragmentById(R.id.content_frame) == mf){
Log.d("Frag","This is Main");
}else{
Log.d("Frag","This is NOT Main :(");
}
This prints
So I know that the findFragmentById will tell me the current fragment but I don't know how I can logically compare it so I can do things only if it is visible.
I have never dived into the details of SlidingMenu and couldn't tell you what's wrong in the first problem.
But in your second problem, you are comparing two different objects.
MainFragment mf = new MainFragment();
fragmentManager.findFragmentById(R.id.content_frame) == mf
Here you create a new MainFragment, and try to compare it with a old instance. It can never be true. When comparing Objects, the address are compared. It will only return true if they are the same objects.
If you just want to check the class of object, use the following code:
Fragment f = fragmentManager.findFragmentById(R.id.content_frame);
if(f instanceof MainFragment)
// code here.
Get the fragment by tag or by I'd
Get the fragment's view
Get window visibility on the view will provide visibility
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.
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.
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 + "");
}