I am using DialogFragment. I have three dialogs that appear one after another.
Countries List --> States List --> Regions List
I have tow button on each of these dialogs, Cancel and OK. I put current dialog onto stack before I show next dialog. Now a user may press Cancel button on say, States List dialog. I don't want him to be taken back Countries List dialog. I just was to exit the dialog. Similarly, I don't want stacked dialogs popup when a user presses OK button. I want to end the dialog gracefully and update the database.
I searched on SO and google docs. I found this should be achieved with this:
FragmentManager ft = getFragmentManager();
ft.popBackStack("mydialog", FragmentManager.POP_BACK_STACK_INCLUSIVE);
But it doesn't work for me. I tried to place it inside builder.setNegativeButton and also inside builder.setPositiveButton. Nothing seems to work.
Here is how I put them on the stack:
RegionsDialogFragment dialogFragment = new RegionsDialogFragment ();
FragmentTransaction ft = getFragmentManager().beginTransaction();
Fragment prev = getFragmentManager().findFragmentByTag("mydialog");
if (prev != null) {
ft.remove(prev);
}
ft.addToBackStack(null);
dialogFragment.show(ft, "mydialog");
Thank you
EDIT
It seems I wasn't clear. I am able to put dialogs unto the stack. That is not a problem. Problem is how can I empty(not popup) back stack when I no longer need to show stacked dialogs? I don't want stacked dialogs to popup on their own again. I want them to die on the stack and don't come back. I want to empty the stack.
Hope it helps.
You don't need add to back stack the dialogs, if you want close a Dialog, you should use: dimiss() method.
Note:
You are setting null in addToBackStack() method
Remove this line from your code
ft.addToBackStack(null);
Related
The DialogFragment's combination with BackStack confuses me and maybe someone could help me out.
From what I found out, there are 2 common ways of displaying the DialogFragment. Either through show() method or by normal adding the fragment through transaction (from the checkup that's essentialy what show() does internaly).
The problem I have is with understanding of where addToBackstack() method comes into this whole process, especialy when you add transaction to backstack prior to calling on show() method, like in this sample:
// DialogFragment.show() will take care of adding the fragment
// in a transaction. We also want to remove any currently showing
// dialog, so make our own transaction and take care of that here.
FragmentTransaction ft = getFragmentManager().beginTransaction();
Fragment prev = getFragmentManager().findFragmentByTag("dialog");
if (prev != null) {
ft.remove(prev);
}
ft.addToBackStack(null);
// Create and show the dialog.
DialogFragment newFragment = MyDialogFragment.newInstance(mStackLevel);
newFragment.show(ft, "dialog");
In the sample above, before displaying the new DialogFragment, we check for other DialogFragment that could be displayed, we remove it and add this procedure to the backstack (I assume that this is for the purpose of displaying the previous DialogFragment, when the new DialogFragment is removed). Afterwards we display the new DialogFragment through show() method. But I fail to see any difference between this approach, and just calling show() method. I just checked on a Test project with displaying multiple DialogFragments one on top of eachother in a succesion, and the internal implementation of DialogFragment handles everything automaticaly, meaning that when I touch back button, the previous DialogFragment is displayed nevertheless.
So, why the addToBackstack is being used in context of DialogFragments?
Alright, after writing more code using the DialogFragment solution including more tests I came to the reason (most likely the key reason) of why to use the addToBackStack way, right after removing the previous dialog.
The reason for it (silly me that I missed that) is that it will make sure only one dialog is visible at one point in time. The show() method does exactly what it says, it "shows" a new dialog fragment, but does absolutely nothing with any previous visible dialog, so in essence all the dialogs using show() method will be stacked on top of the previous dialog. My error was that I didn't realize that until I made dialogs different in size. If all dialogs are of the same size, then the most top one will be hiding all the other dialogs.
So to summarize, show() method does not hide/remove any dialog that is already present on the screen. If we want to do that, we need to do the transaction manually, which of course must include the removing of the previous dialog as well as adding this transaction to the back stack so that when user presses the back button, the previous dialog will reemerge.
I want to pop up a timepicker and a datepicker, more than once in an Activity.
It's not clear to me whether I should make one instance of each, and show them as needed, or if I should make new instances each time I need to pop a dialog up.
And I'm very confused by findFragmentByTag/findFragmentById. It seems that the fragment is only found if it's currently displayed?
But if it's not found and I make a new Fragment and show it with the same tag, I get an IllegalStateException. How can I recover a fragment in onCreate?
Currently, I'm doing this:
FragmentManager fm = getSupportFragmentManager();
if (savedInstanceState == null) {
timePicker = new RadialTimePickerDialog();
datePicker = new CalendarDatePickerDialog();
fm.beginTransaction().add(timePicker, FRAGMENT_TIME_PICKER)
.add(datePicker, FRAGMENT_DATE_PICKER).commit();
} else {
timePicker = (RadialTimePickerDialog)
fm.findFragmentByTag(MedicationCollapsePanel.FRAGMENT_TIME_PICKER);
datePicker = (CalendarDatePickerDialog)
fm.findFragmentByTag(MedicationCollapsePanel.FRAGMENT_DATE_PICKER);
}
As I mentioned, this fails to actually find the fragments, and almost as bad, it displays them immediately when the Activity starts, which I don't want.
Thanks.
I assume these classes are the ones by the same name from gitHub that pop up at the top of a google search.
They extend dialogFragments.
DialogFragments take care of removing themselves with the fragment manager when they are dismissed. You can see the source code as well as get this decription, from the link below:
Dismiss():
Dismiss the fragment and its dialog. If the fragment was added to the
back stack, all back stack state up to and including this entry will
be popped. Otherwise, a new transaction will be committed to remove
the fragment.
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/app/DialogFragment.java#DialogFragment.onDismiss%28android.content.DialogInterface%29
Hence, they are not reusable after being dismissed since they will unattach themselves automatically by calling the underlying context and getting the fragment manager themselves. Rather create a new instance, and show them, or you can try overriding their default behavior. You can try either changing what they do after being dismissed, or prevent them from being dismissed in the first place (e.g. hiding them).
I still have a problem due to a DialogFragment used on my main activity.
I am currently using this code to remove it:
FragmentTransaction transaction = getFragmentManager().beginTransaction();
Fragment frag = getFragmentManager().findFragmentByTag("LockDialog");
if(frag != null) {
transaction.remove(frag);
transaction.commit();
}
The problem is that I am still getting crashes due to the fact that the dialog has duplicates (meaning the dialog was not properly removed sometimes).
So my question is: is it a right way to remove a DialogFragment or must it be only used only for Fragments?
Do I have to use the dismiss() method all the time?:
Fragment lockFragment = getFragmentManager().findFragmentByTag("LockDialog");
//If the dialog already exist, we dismiss it
if(lockFragment != null && lockFragment instanceof LockDialogFragment) {
LockDialogFragment lockDialog = (LockDialogFragment) lockFragment;
lockDialog.dismiss();
}
This is currently my biggest bug on one of my apps so I want to be sure before choosing one or the other.
Thanks!
EDIT: I just realized my current problem is maybe due to the fact that commits can be delayed, I will add executePendingTransactions to see if it gets any better.
But still it brings another question, is it necessary to call transaction.remove() if the dialog has been dismissed? Is using dismiss() more straightforward and safe than using the transactions?
DialogFragment.dismiss() is the correct way. From the documentation:
Dismiss the fragment and its dialog. If the fragment was added to the
back stack, all back stack state up to and including this entry will
be popped. Otherwise, a new transaction will be committed to remove
the fragment.
For showing Dialog Fragment dialogFragment.show(transition,FocusDialogFragment.TAG);
For dismissing dialog fragment by dialogFragment.dismiss();
Scenario what i'm trying to achieve:
Loading activity with two frame containers (for list of items and for details).
At the app launch time add listFragment in listFrame and some initial infoFragment in detailsFrame containers.
Navigating through list items without adding each detail transaction to back stack (want to keep only infoFragment in stack).
As soon as user hit back button (navigate back) he falls back to intial infoFragment what was added in launch time.
If sequential back navigation fallows then apps exit.
My code:
protected override void OnCreate(Bundle savedInstanceState)
{
...
var listFrag = new ListFragment();
var infoFrag = new InfoFragment();
var trans = FragmentManager.BeginTransaction();
trans.Add(Resource.Id.listFrame, listFrag);
trans.Add(Resource.Id.detailsFrame, infoFrag);
trans.Commit();
...
}
public void OnItemSelected(int id)
{
var detailsFrag = DetailFragment.NewInstance(id);
var trans = FragmentManager.BeginTransaction();
trans.Replace(Resource.Id.detailsFrame, detailsFrag);
if (FragmentManager.BackStackEntryCount == 0)
{
trans.AddToBackStack(null);
}
trans.Commit();
}
My problem:
After back button has been hit, infoFrag is overlapped with previous detailFrag! Why?
You can do this:
if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
getSupportFragmentManager().popBackStack(getSupportFragmentManager().getBackStackEntryAt(0).getId(), getSupportFragmentManager().POP_BACK_STACK_INCLUSIVE);
} else {
super.onBackPressed();}
In your activity, so you to keep first fragment.
You shouldn't have, in your first fragment, the addToBackStack. But, in the rest, yes.
Very nice explanation by Budius. I read his advice and implemented similar navigation, which I would like to share with others.
Instead of replacing fragments like this:
Transaction.remove(detail1).add(detail2)
Transaction.remove(detail2).add(detail3)
Transaction.remove(detail3).add(detail4)
I added a fragment container layout in the activity layout file. It can be either LinearLayout, RelativeLayot or FrameLayout etc.. So in the activity on create I had this:
transaction.replace(R.id.HomeInputFragment, mainHomeFragment).commit();
mainHomeFragment is the fragment I want to get back to when pressing the back button, like infoFrag. Then, before EVERY NEXT transaction I put:
fragmentManager.popBackStackImmediate();
transaction.replace(R.id.HomeInputFragment, frag2).addToBackStack(null).commit();
or
fragmentManager.popBackStackImmediate();
transaction.replace(R.id.HomeInputFragment, frag3).addToBackStack(null).commit();
That way you don't have to keep track of which fragment is currenty showing.
The problem is that the transaction that you're backing from have two steps:
remove infoFrag
add detailsFrag (that is the first1 detail container that was added)
(we know that because the documentation This is essentially the same as calling remove(Fragment) for all currently added fragments that were added with the same containerViewId and then add(int, Fragment, String) with the same arguments given here. )
So whenever the system is reverting that one transaction is reverting exactly those 2 steps, and it say nothing about the last detailFrag that was added to it, so it doesn't do anything with it.
There're two possible work arounds I can think on your case:
Keep a reference on your activity to the last detailsFrag used and use the BackStackChange listener to whenever the value change from 1 to 0 (you'll have to keep track of previous values) you also remove that one remaining fragment
on every click listener you'll have to popBackStackImmediatly() (to remove the previous transaction) and addToBackStack() on all transactions. On this workaround you can also use some setCustomAnimation magic to make sure it all looks nice on the screen (e.g. use a alpha animation from 0 to 0 duration 1 to avoid previous fragment appearing and disappearing again.
ps. I agree that the fragment manager/transaction should be a bit more clever to the way it handles back stack on .replace() actions, but that's the way it does it.
edit:
what is happening is like this (I'm adding numbers to the details to make it more clear).
Remember that .replace() = .remove().add()
Transaction.remove(info).add(detail1).addToBackStack(null) // 1st time
Transaction.remove(detail1).add(detail2) // 2nd time
Transaction.remove(detail2).add(detail3) // 3rd time
Transaction.remove(detail3).add(detail4) // 4th time
so now we have detail4 on the layout:
< Press back button >
System pops the back stack and find the following back entry to be reversed
remove(info).add(detail1);
so the system makes that transaction backward.
tries to remove detail1 (is not there, so it ignores)
re-add(info) // OVERLAP !!!
so the problem is that the system doesn't realise that there's a detail4 and that the transaction was .replace() that it was supposed to replace whatever is in there.
You could just override onBackPressed and commit a transaction to the initial fragment.
I'm guessing but:
You've added the transaction to replace infoFrag with 1st detailsFrag into the backstack.
But then you replace 1st detailsFrag with 2nd detailsFrag.
At this point when you click back, the fragment manager cannot cleanly replace 1st detailsFrag with infoFrag as 1st detailsFrag has already been removed and replaced.
Whether the overlapping behaviour is expected or not I don't know.
I would suggest debugging the Android core code to see what it is doing.
I'm not sure whether you can achieve without say overriding Activity::onBackPressed() and doing the pops yourself having added all transactions to the backstack.
Hi I am not sure I am doing the right thing. I have several fragments in one activity (not shown at the same time). When I add the fragment do I have to check if a previous instance exists? I am using the compatibility package and my fragment CameraFragment is a separate class (in its own file):
private void addNewFragment(Fragment fragment, String tag) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.frag1, fragment, tag);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.addToBackStack(null);
ft.commit();
}
and then :
public void startPicTaking() {
addNewFragment(CameraFragment.newInstance(), TAG_PIC_TAKING);
}
So each time a user clicks a button to take a picture I use this methods BUT shall I verify if the fragment already exists and remove it first or does the static method newInstance make sure the fragment is not duplicated?
I have read the doc several times but I don't understand why the line:
ft.addToBackStack(null);
what is it for? I know you can pop the back stack and it keeps the transaction but how can it be used and for what? Is it necessary or if I don't use it I can skip it?
thanks
I have several fragments in one activity (not shown at the same time). When I add the fragment do I have to check if a previous instance exists?
No, it will just create a new instance of that Fragment when it adds the next instance of it. It will not affect the previous instance of it.
So each time a user clicks a button to take a picture I use this methods BUT shall I verify if the fragment already exists and remove it first or does the static method newInstance make sure the fragment is not duplicated?
You could do that if you wanted to, to ensure that no Fragment appears twice in the stack. (So when you hit back, you don't get the same activity again.) Depending on exactly what appears in your back stack, you may not want to remove stuff lower down. (Consider that a user expects previous fragments to appear when he hits the back button.)
I have read the doc several times but I don't understand why the line: ft.addToBackStack(null); what is it for?
When Fragment objects are added to the back stack, then each time the user hits back, they will go to the previous item on the stack. If you do not add an item to the back stack, the user will not encounter it when they hit the back button.