Correct way to close DialogFragment when using Navigation component? - android

I am using the Navigation component to show a DialogFragment (<dialog...>...</dialog> in the navigation.xml) and want to know what's the recommended way to close the Dialog. I tried myself and got the following results:
1) dismiss()in DialogFragment: seems to work fine
2) findNavController().navigateUp(): seems to work fine
3) findNavController().navigate(MyDialogFragmentDirections.actionMyDialogFragmentToMyNormalFragment()): works, but loads a fresh version of the target destination, so depending on the use case this might not be what one wants to have.
Note: My use case is that MyNormalFragmentuses MyDialogFragmentto get some input, so after MyDialogFragmentis shown, I need to get back to the already existing instance of MyNormalFragment.
So for me, only 1) or 2) is correct. Now I am wondering, is there any difference between 1) and 2) ?

Both 1) and 2) end up doing the same thing the end, but 2) is always a safer option.
When you call dismiss(), the DialogFragment is dismissed and the DialogFragment is stopped (it receives a callback to onStop()). This triggers the listener in DialogFragmentNavigator, which then updates the NavController's state by calling popBackStack().
dismiss() however, is an asynchronous operation (as seen in the DialogFragment source code - you'll note it does not use commitNow(), etc). Therefore if you were to check what destination you are on from the NavController.getCurrentDestination(), you'd see that you're still on the dialog destination, despite having triggered the dismissal.
navigateUp(), on the other hand, goes directly to the NavController. Since you have another destination on your back stack (the one under the DialogFragment), the NavController source code shows that navigateUp() just calls popBackStack() - the same operation that dismiss() was eventually triggering.
However, when it is the NavController that is driving the operation, NavController updates its state synchronously. This means that immediately after you call navigateUp(), it will have updated its getCurrentDestination() and internal state in addition to calling through to DialogFragmentNavigator's popBackStack(), which is what calls through to dismiss() (removing the observer mentioned above to prevent double dismissals).
Therefore calling navigateUp() is always the safer choice since it ensures that the NavController is synchronously updated to the correct state, rather than rely on FragmentManager's asynchronous timing (which may mean that additional click events are received during that time period due to multi-touch, etc.).
Calling navigate() with an action that has an app:destination on it will navigate to a new instance of the destination, which would not be appropriate for returning back to your previous instance.

Related

I have a trouble in Fragment life cycle and need a resolution to come out of it

I have done ample research on this, and there is not one clear solution on the problem.
In the life-cycle, particularly in the Fragment life-cycle, following Exception comes any moment after onPause().
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
My logic says, that to continue with the current fragment, after it reaches this state, I have to restart the activity and again point back to the intended fragment using Intent.
I want to be clear on what is happening and what should be real solution to deal with it.
I need to know the pros and cons of this mechanism; its importance in Fragment or Activity life-cycle.
Also, if I am changing the Windows Feature in onCreate to not to go to sleep, unless if the user has manually pressed the home button, will still the activity will go to this state?
This exception happens when you're trying to add/remove/replace/interact in any other way with a Fragment inside the Activity when it's paused.
Which means Activity will not be able to restore it's state (restore the state of a Fragment which has been changed) if it will be destroyed right away.
Best solution here, is to check that Activity is NOT paused during the interaction with a Fragment.
Another option is to use commitAllowingStateLoss() to interact with Fragment transaction, with a risk of losing it's state.
See:
https://developer.android.com/reference/android/app/FragmentTransaction.html#commitAllowingStateLoss()
In a perfect world you should analyze each crash carefully and add checks to verify that you interact with fragments only when Activity is up and running.
A better explanation is presented in a new Android developer reference and guide documents for using JetPack Life Cycle Listener.
https://developer.android.com/topic/libraries/architecture/lifecycle#kotlin
The library makes the components Activity Life Cycle aware. That means you do not require an abstract baseActivity class which overrides every life cycle callback, and record that state in a boolean variable. LifeCycle listener will do it for you.
All you have to do is stop introducing a new fragment or stop any Loader that updates the UI when its response returns. The right time to do this is before onStop or onSavedInstance state is called, and your components will be made aware of it.
It clearly states that after the onSavedInstancState or onStop is called the UI becomes immutable till the onStart of the Activity is called again. Sometimes you have to call restart the same activity using NEW TASK and CLEAR TASK flags using intent, when this state occurs and there is no chance that otherwise onStart is going to be called.
Happy Coding :-)

How to know if FragmentManager's back stack popped anything?

I want to know if anything got popped of when I called getFragmentManager().popBackStack(). How can I find this out?
I'm using android.support.v4.app.FragmentManager.
What I tried
Way 1: return value
In the documentation for android.support.v4.app.FragmentManager.popBackStack(), it says:
Pop the top state off the back stack. Returns true if there was one to pop, else false.
So according to that, it should work like this:
boolean popped = getFragmentManager().popBackStack();
But this doesn't work because the return value of popBackStack() is void.
Is this either a bug in their code or in their documentation?
Way 2: back stack count
I tried to calculate from getFragmentManager().getBackStackEntryCount() if anything was popped:
int countBefore = getFragmentManager().getBackStackEntryCount();
getFragmentManager().popBackStack();
int countAfter = getFragmentManager().getBackStackEntryCount();
boolean popped = countAfter != countBefore;
But this also doesn't work because of how popBackStack() works:
This function is asynchronous -- it enqueues the request to pop, but
the action will not be performed until the application returns to its
event loop.
So this means that both calls to getBackStackEntryCount() give me the same number.
Questions
How can I find out if anything was popped?
What I described in way 1: Is either a bug in Android's code or in Android's documentation?
If it's a bug: How can I tell them, that there's something mixed up? (I'm asking this because once I already tried to fix a mistake in their Android documentation but until today they never merged my pull request)
Question: How can I find out if anything was popped?
There are two methods for popping back stack:
void popBackStack()
Pop the top state off the back stack. This function is asynchronous -- it enqueues the request to pop, but the action will not be performed until the application returns to its event loop.
boolean popBackStackImmediate()
Like popBackStack(), but performs the operation immediately inside of the call. This is like calling executePendingTransactions() afterwards without forcing the start of postponed Transactions.
The latter one seems like the one you were looking for.
Alternatively you could addOnBackStackChangedListener in onStart and unregister it in onStop if you need to observe changes at all times.
Question: What I described in way 1: Is either a bug in Android's code or in Android's documentation?
Quick glance in the docs reveals both methods with appropriate description. Support library looks intact as well. This was probably an oversight on your part.
EDIT: Ok, I glanced too quickly. It does say "Pop the top state off the back stack. Returns true if there was one to pop, else false. This function is asynchronous [...]". This is of course wrong.
Question: If it's a bug: How can I tell them, that there's something mixed up?
GitHub repo is only a mirror. The Android dev team does not go there and merge pull requests. If you wish to contribute, use AOSP gerrit. More info here: https://source.android.com/source/contributing
Alternatively you can file a bug report here: https://issuetracker.google.com/issues

difference between popBackStackImmediate vs popBackStack

Could some one please tell me the difference between popBackStackImmediate and popBackStack ? I really don't get it and additionally what is the "flag" 0(zero) means in 2nd of popBackStack?
Thank you very much for your helps guys...
popBackStackImmediate() will perform the popping commands immediately in the call. The results of which can be verified immediately after the call. It is somewhat slower since all the popping actions are performed within the call.
popBackStack() will perform the popping commands within the next event loop cycle (i.e. next draw phase). So it's asynchronous to the rest of the code. That means the FragmentTransaction will not be removed from the backstack after this is executed. In most cases you don't need the FragmentTransaction immediately popped, so it waits until everything else is finished before it actually happens. All this happens so fast that the user wouldn't recognize it.
The flag at the end is unrelated. It can currently only be set to POP_BACK_STACK_INCLUSIVE. The FragmentManager allows you to set an ID on the backstack. If you set the flag, then it will pop the FragmentTransaction that match the ID specified until there is one that does not match the ID or the bottom is reached. If the flag is not set, then all FragmentTransactions that don't match the ID are popped until one is reached that does match the ID or the bottom is reached.
popBackStack() will pop the back stack, but it won't perform the pop until slightly later - it posts a message to do it so you don't have to wait for a heavyweight operation to occur.
popBackStackImmediate() does it right now, before the function returns. Its slower and can cause perf issues. Use the non-immediate version when possible.
0 as the second parameter means to use the default behavior (remove the top element in the backstack). You can also pass it a series of boolean ORed flags. The only flag currently supported is POP_BACK_STACK_INCLUSIVE, which changes it to take out multiple fragments.

DialogFragments and Dialogs Android

Everywhere I can read: Use FragmentDialog everywhere because it can be independent on Activity lifecylce. But, generally, dialogs very often should be displayed asynchronically. However, it is discouraged to commit to FragmentManager from asynchrounous callback. How to deal with it?
Use DialogFragment by all means because as you mentioned it helps in keeping your activity loosely coupled with the dialogue. Yeah there are cases where you might need to show the dialogue asynchronously. In such case, what you need to do is wrap your dialogue invocation code with method calls on the host component which tells you whether it is safe to show the dialogue at this instance or not.
If you are calling show within an activity, wrap the call to show with -
if(!isDestroyed() && !isFinishing())
and If you are calling show within a fragment , wrap the call to show with -
if (isResumed() && !isRemoving())
This more or less would solve the issue of landing in an inconsistent UI state

How does Android Back Navigation works?

I have a problem understanding the difference between backButton issued as the Hardware button, and backButton issued as a super.onBackPressed() from the Activity (i.e. from menu android.R.id.home).
In the case of Hardware backButton the data in my Activity are initiated immediately and in the exact state which Activity hold before leaving.
In the case of on super.onBackPressed() it seems like the former Activity gets finished, since the data needs to be initiated from the scratch.
Tried studying the lifecycle but found nothing interesting for this usecase.
How can I mimic behavior of the Hardware Button, which would allow me to retain state of the Activity?
Can prepare some demo code, but I hope I provided good enough high level description.

Categories

Resources