When I open an dialog and navigate the screen from A Fragment to B Fragment,
The dialog is still shown above B Fragment.
I do dismiss & null to the dialog fragment property. But still, sometimes, it exists.
So, What I do is, calling this in B Fragment. (In this case, Bottom Sheet)
(requireActivity().supportFragmentManager).findFragmentByTag(MyBottomSheet.TAG)?.let {
if(it is MyBottomSheet) {
it.dismiss()
}
}
This is better, but it doesn't work sometimes too. So, I thought this is because screen animation. So, I gave 200L before calling dismiss(). Then, It seems to work better.
However, I don't think this is the true and complete solution.
How can I restrict a dialog to a fragment so that I don't need to consider those dismiss thing here and there.
When I create DialogFragment or BottomSheet. I set attachToParent = true like this.
_binding = DataBindingUtil.inflate(inflater, R.layout.modal_bottom_sheet_my, container, true)
And also when I navigate between fragments, I launch it singleTop.
But none of them worked. How can I solve this questioin? Is there something like single instance?
Related
Hi folks I need to throw up a series of DialogFragments one after another using the navigation component. I have encountered some pretty unusual system behavior which looks like a race condition. I show the dialogs with a global action after an item is clicked, and use the fragment result api to determine if another one should be shown.
I am using a custom layout so there's no positive / negative listeners etc., and my own continue / cancel buttons send a result back to the host fragment.
ItemsFragment.kt:
navController.navigate(RegisterItemsDirections.openModsDialog(item.id, 0))
fragment.setFragmentResultListener(ItemsFragment.MODIFIERS_REQUEST) { key, bundle ->
//kill the current dialog
navController.navigateUp()
//some logic to determine if we need another dialog...
if(needAnotherDialog){
//navigate to the next one
navController.navigate(RegisterItemsDirections.openModsDialog(item.id, lastModGroupSelectionIndex + 1))
}
}
ModsDialogFragment.kt, when the user clicks "continue"
setFragmentResult(MODIFIERS_REQUEST, bundleOf(MODIFIERS_RESULT to selectedMods))
So the issue only appears on 3rd or more dialogs on my devices, I can see that the 1st and 2nd dialogs are completely destroyed and detached. When it displays the 3rd one, the first one attaches itself again, and appears beneath the 3rd one which I can't explain.
I've tried:
Popping the back stack in the global action's nav xml
Navigating up or dismissing the dialog fragment in the dialog itself (before calling setFragmentResult), which is the most logical place to put it
Popping the backstack instead of navigating up, basically the same thing in this case
When I don't dismiss / nav up any of the dialogs and allow them all to just stack, there's no issue. When I delay the navigation by 500ms there is no issue. So navving up and then immediately navigating to another instance of the dialog are fighting with eachother producing very strange results. What could be the solution here that doesn't involve a random delay?
Navigation version is 2.3.3 and I'm having a lot of trouble trying to update it due to AGP upgrades being incompatible with a jar I need so I'm not sure if this has been fixed.
I figured out the issue, it's down to dumb copy pasting.
I took the donut tracker sample code and made it stack dialogs in the same way and there was no issue.
The difference between the two was I am subclassing DialogFragment and for some unknown reason doing this:
override fun show(manager: FragmentManager, tag: String?) {
val transaction = manager.beginTransaction()
val prevFragment = manager.findFragmentByTag(tag)
if (prevFragment != null) {
transaction.remove(prevFragment)
}
transaction.addToBackStack(null)
show(transaction, tag)
}
This code predates the Nav component library I believe, and it completely f***s with the fragment manager used by the navigation component. So the moral of the story is to not do bizarre things in super classes, or better yet not super class at all.
I'm currently adding a new fragment (using single activity pattern) to my supportFragmentManager via add() method from another fragment. But my new fragment uses only half of the screen, and I still can interact with the fragment in background. Is there any way to block user interaction with background fragment? Just like DialogFragment do. Also, I cant use dialog fragments cause of this.
Well, right after posting a question i looked up in DialogFragment code and found in onStart() method this lines:
View decorView = mDialog.getWindow().getDecorView();
ViewTreeLifecycleOwner.set(decorView, this);
ViewTreeViewModelStoreOwner.set(decorView, this);
ViewTreeSavedStateRegistryOwner.set(decorView, this);
To make it work in regular fragment i just edited first line to this
val view = myRootView as View //View decorView = mDialog.getWindow().getDecorView();
And that's it
You can use dialog fragment for half of screen (Did you use material dialog fragment).
https://medium.com/#kosta.palash/using-bottomsheetdialogfragment-with-material-design-guideline-f9814c39b9fc
And you can use Eventbus(https://github.com/greenrobot/EventBus) or SingleEvent (https://abhiappmobiledeveloper.medium.com/android-singleliveevent-of-livedata-for-ui-event-35d0c58512da#:~:text=LiveData%20notifies%20Observer%20objects%20when,every%20time%20there's%20a%20change.) for notify the background fragment
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();
I've looked at several of the questions pertaining to handling an orientation change in a DialogFragment, and the common theme seems to be that the askers aren't getting any answers, at none that I understand...
So, I'd like to take a different approach... Is there a way I can programmatically dismiss the dialog... Looking at my LogCat, I can see that if the dialogfragment is active, and the screen orientation is changed, then my Activity restarts.
Along the way, the DialogFragment is also restarted, but this happens after onCreate and before onResume (as close as I can tell). It's fairly simple for me to detect this in my dialog, and what I'd like to do is abort the dialog view creation in this case. What I've tried so far is like this:
if (normal_conditions_detected)
{
View v = inflater.inflate (R.layout.dialog_layout, container, false);
final Dialog d = getDialog();
d.setTitle (R.string.sensor_config);
... more stuff
return v
}
else
{
getDialog().cancel();
return null;
}
This does avoid the null pointer nonsense I was getting, but now I get what I can only describe as an empty dialog, even with the cancel() command in there. Is there a way I can get my dialogFragment code to refuse to create the view?
When you rotate your device, activity executes
onSaveInstanceState
method and save the current state which means it saves your fragment state as well, after recreation it executes
onCreate(Bundle savedInstanceState)
method again, savedInstanceState holds your old data, in here you can make something like
if (savedInstanceState == null){
YourDialogFragment f = new YourDialogFragment()
f.show // etc
}
Only once your fragment will be created, instead of putting a control inside fragment, you can control it by the activity