I have a DialogFragment that is shown on the screen from a background thread. When I rotate the screen, my app crashes. Below is the code:
public void showDialog(DialogFragment dialog) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.addToBackStack(null);
dialog.show(ft, "dialog");
}
This gives me the following error:
10-24 13:20:51.490: E/AndroidRuntime(3038): java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
After looking for answers on SO, and this article, I tried doing this:
public void showDialog(DialogFragment dialog) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.addToBackStack(null);
ft.add(dialog, "dialog");
ft.commitAllowingStateLoss();
}
But with this, I get the following error on ft.commitAllowingStateLoss()
10-24 13:26:58.890: E/AndroidRuntime(3765): java.lang.IllegalStateException: Activity has been destroyed
Any idea what am I missing?
You have a method as showDialog(DialogFragment dialog) but your problem is in your dialogFragment's initialization I guess. If you put dialogFragments initialization data to its arguments, your problem will be solved. Write a newInstance method and use it on creation of your dialog. See sample here.
Edit: Reason of your exception is; your activity and also dialogFragment is destroyed and re-created somehow(maybe by config_changes not set at manifest or "settings -> developer options -> don't keep activities" is selected. This can happen, it is normal). And initialization paramaters of your dialog has been lost(became null). You have to put your initialization parameters to dialogFragment's arguments and so it can read them after re-cration on create.
Try this:
FragmentManager fm = getActivity().getSupportFragmentManager();
dialog.setTargetFragment(this, 0);
dialog.show(fm, 0);
Are you creating the fragment using a constructor with parameters?? if so that could be the case because you are not supposed to create fragments that way
Related
So i'm having one dialog that i'm trying to show from an Activity
this activity has a navigation graph (JETPACK)
for now i can show this popup only once if i'm in my Activity
then if i close the activity and come back again to it, it's won't show the Dialog because the state of the activity is already saved. and i'm using commitAllowingStateLoss to commit this dialog fragment if i use commit it's crashing. i searched everywhere in stackoverflow couldn't find a single solution for this problem.
code of how im showing the fragment:
public void show(Context context) {
FragmentManager fragmentManager = ((AppCompatActivity) context).getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
Fragment prev = fragmentManager.findFragmentByTag(TAG_DIALOG);
if (prev != null) {
fragmentTransaction.remove(prev);
}
fragmentTransaction.add(this, TAG_DIALOG);
fragmentTransaction.commitAllowingStateLoss();
}
Please show how you open the fragment.
Do you save anything when the activity closes? I can only imagine problems with saving the actual fragment instance, but it's all guess work without seeing any code.
I have used the following codes for showing and canceling dialogfragment :
public static void showDialogFragment(FragmentManager fm,String type){
FragmentTransaction ft = fm.beginTransaction();
MyDialogFragment prev = (MyDialogFragment)fm.findFragmentByTag(type);
if (prev != null) {
prev.dismissAllowingStateLoss();
ft.remove(prev);
}
ft.addToBackStack(null);
MyDialogFragment newFragment = MyDialogFragment.newInstance();
try{
newFragment.show(ft,type);
}catch(IllegalStateException e){
return;
}
}
public static void cancelDialogFragment(FragmentManager fm,String tag){
FragmentTransaction ft = fm.beginTransaction();
MyDialogFragment prev = (MyDialogFragment )fm.findFragmentByTag(tag);
if (prev != null) {
prev.dismiss();
ft.remove(prev);
}
ft.addToBackStack(null);
ft.commit();
}
when I open the activity I show a dialogFragment and after receiving the data from internet I cancel it and show the recieved data, But if I press back button again it shows the dialogFragment and I have to press back button again to dismiss it and one more time to finish the activity. I know I can override onBackPressed but I want to know why this happens? why dose it again show the dialogfragment?
What is wrong with my code?
What you do is in showDialogFragment() you add this fragment to FragmentManager to backstack. Then in cancelDialogFragment() method you remove it from backstack with ft.remove(prev);
So now, your backstack is as it was before showing DialogFragment.
But what you do next is, that you add this DialogFragment again to backstack. It is not shown, but it is on the top of backstack. That means, if you press backButton, the top item in backstack, your DialogFragment, will be shown. On the next BackPress, your DialogFragment will be dismissed.
So dont add the fragment to backstack in your cancelDialogFragment() method.
Remove this line:
ft.addToBackStack(null);
Replace your entire cancelDialogFragment with this:
public static void cancelDialogFragment(FragmentManager fm,String tag){
fm.popBackStack();
}
Finally I have found the reason and the correct answer. the problem is with:
ft.addToBackStack(null);
From document:
Add this transaction to the back stack. This means that the
transaction will be remembered after it is committed, and will reverse
its operation when later popped off the stack.
Parameters name An optional name for this back stack state, or null.
that menas:
hey android I have removed dialogfragment from backstack (so there is nothing on the top of the backStack and the answer of #Vojtaaa9 is wrong because as I added the comment when you run MyDialogFragment prev = (MyDialogFragment )fm.findFragmentByTag(tag); after calling cancel you will get null, this means the backStack dose not have any dialogfragment) but remmber my action, remember that there was a dialogfragment but now it has removed. When user presses the back button the transaction reverses, it means that now there is nothing on the top of the backStack but then android pushes a dialogFragment to the backStack to do the transaction in a reverse order.
I have a FragmentDialog sppearing in front of a GoogleMap that I want to temporarily hide so that the user can select something on the background, and then bring it back in front again. Regular hide()/show() won't work since it's not "embedded" fragment, but a FragmentDialog cannot be hidden. I tried to use dismiss() and then show() using the same instance, since that should recreate the dialog, but it doesn't seem to work for some reason. Here's my code:
if (onHoldDialog != null) {
onHoldDialog.setPointText(mapMarker.getTitle());
FragmentTransaction ft = getFragmentManager().beginTransaction();
Fragment prev = getFragmentManager().findFragmentByTag("dialog");
if (prev != null) {
ft.remove(prev);
}
ft.addToBackStack(null);
onHoldDialog.show(ft, "dialog");
}
Is my method wrong? Are FragmentDialog instances only "one-use"? Can I copy it to another instance?
The problem I had was I overrode onDismiss in my DialogFragment subclass, but I didn't call super.onDismiss. Once I called the super's method, the problem was fixed.
In general, remember to call the super method of an overridden method in fragments and activities.
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 + "");
}
We add a general/normal Fragment programatically by doing something like:
fragmentTransaction.add(containerViewId, fragmentToAdd, fragmentTag);
and we replace a Fragment by another by doing something like:
fragmentTransaction.replace(containerViewId, newFragment, tagOfNewFragment);
But we add a DialogFragment by
dialogFramentInstance.show(fragmentManager, fragmentTag);
The question is that how should I replace this DialogFragment which has been added by the show() method?
dialogFramentInstance.show(fragmentManager, fragmentTag);
Just adds the dialog fragment to the fragment manger using an add transaction (with no container).
In order to replace fragments you'll need a container and since you don't have one your only option is to dismiss() the first one and show() the new one.
private void closeYourDialogFragment() {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
Fragment fragmentToRemove = getSupportFragmentManager().findFragmentByTag("your_dialog_fragment");
if (fragmentToRemove != null) {
ft.remove(fragmentToRemove);
}
ft.addToBackStack(null);
ft.commit(); // or ft.commitAllowingStateLoss()
}
private void replaceYourDialogFragment() {
closeYourDialogFragment();
YourDialogFragment yourDialogFragment = new YourDialogFragment();
yourDialogFragment.show(getSupportFragmentManager(), "your_dialog_fragment");
}
Maybe you can do like this:
public void showFragment(Fragment fragment) {
if (fragment instanceof DialogFragment) {
FragmentTransaction ft = mContext.getFragmentManager().beginTransaction();
Fragment prev = mContext.getFragmentManager().findFragmentByTag("dialog");
if (prev != null) {
Log.d(TAG, "showFragment: remove prev...." + prev.getClass().getSimpleName());
ft.remove(prev);
}
mContext.getFragmentManager().executePendingTransactions();
if (!fragment.isAdded()){
ft.addToBackStack(null);
((DialogFragment) fragment).show(ft, "dialog");
} else {
Log.w(TAG, "showFragment: fragment has been added!" );
}
}
}
So this took me a lot of digging to figure out.
Dialog fragment show method only adds fragments hence if you want to replace them you have to manually remove the previous dialog fragment.
One thing to keep in mind, it is important to use the same fragmentManager used to open the initial dialog fragment. For example if you opened the first dialog fragment via an Activity (supportFragmentManager) and now using the dialog fragment fragment manager (childFragmentManager) since they do not have the same stack you wont be able to access the original dialog fragment and remove it.