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

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

Related

Correct way to close DialogFragment when using Navigation component?

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.

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.

Fragments: IllegalStateException Can not perform this action after onSaveInstanceState

I am sending an HTTP request to some server(in a background thread) inside onCreateView() method within my fragment class.
When the request received i check some stuff and according to that i add a new fragment(through fragment transaction in the UI thread).
I use the support library
But, for example, if the user press the android home button, while the request hasn't received yet, the fragment is going to pause or stop state and then the request received and of course the exception is thrown(just after trying to commit).
I searched the web and found some very good articles and answers which are relevant to this issue and all the things related to the 'state loss' after onSaveInstanceState() has been called, etc...
For example i read this excellent article of Alex Lockwood: http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html
and also those stackoverflow questions/answers: getting exception "IllegalStateException: Can not perform this action after onSaveInstanceState"
and: "Can not perform this action after onSaveInstanceState" - why am I getting this exception from my activity's onResume method? and more.
But i couldn't came to a conclusion of what to do in my case.
The only thing that came to mind is to use commitAllowingStateLoss() instead of commit() but this fills hacky and not correct as some of the answers that i read conclude about it.
Also there was a suggestion to commit the transaction in onCreate() because it safe, but of course it can't work in my situation, all other suggestions doesn't work for me as well.
Maybe i can use some boolean flag to check if i can't make a transaction and then wait for the fragment to resume and if the flag is true then do the transaction.
This fills to much work and also presents the problem in which i need to know if the transaction can be done(don't even know how to check it).

FragmentManager popBackStack does not work - Bug?

I am using this code to clear up my backstack up to my main view:
while(mFragmentManager.getBackStackEntryCount() > 1) {
mFragmentManager.popBackStack(null, 0);
}
I am pretty sure this code was working before, but now the backstack count does not change and no Fragment is being removed, which causes an out of memory exception because the while loop keeps running.
Does anyone know, if there is something wrong with it, or if there is a bug in the latest revision of the SDK-tools. I have no clue what's causing the problem.
You are probably looking for popBackStackImmediate(). This immediately executes a back stack pop.
you can also use .executePendingTransactions(); after popBackStack();
I certainly would not assume that popBackStack() has an immediate effect, which your loop appears to do.
What documentation says for
popBackStack():
Pop all back stack states up to the one with the given identifier.
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 after mFragmentManager.popBackStack(null, 0); your backstack is not empty until your event is completely processed.
Use popBackStackImmediate() for empty it immediately in the current event itself.

Same Activity called twice... Issue with Multiple AsyncTasks?

I have three simultaneous instances of an AsyncTask for download three files. When two particular ones finish, at the end of onPostExecute() I check a flag set by each, and if both are true, I call startActivity() for the next Activity.
I am currently seeing the activity called twice, or something that resembles this type of behavior. Since the screen does that 'swipe left' kind of transition to the next activity, it sometimes does it twice (and when I hit back, it goes back to the same activity). It's obvious two versions of the activity that SHOULD only get called once are being put on the Activity stack.
Could this be from both onPostExecute()s executing simultaneously and both checking the flags each other set at the exact same time? This seems extremely unlikely since two processes would have to be running line-by-line in parallel...
*****EDIT*** A lot removed from this question since I was way off in what I thought was wrong. Nonetheless I found the answer here quite useful, so I have edited the question to reflect the useful parts.
The only way I can find that this is
possible is if both AsyncTasks'
onPostExecute() executed SO
simultaneously that they were
virtually running the same lines at
the same time, since I set the
'itemXdownloaded' flag to true right
before I check for both and call
startActivity().
Since they are both called on the main application thread, that's not possible, unless you're doing something really strange.
I would introduce some Log calls to ensure that you are not misreading the symptoms.
Beyond that, it is difficult to see any problems from your pseudocode, unless there's a possibility of other downloadID values beyond the three shown. For example, if there is a DL4, and DL4 completed after DL1 and DL2, DL4 would trigger your activity.

Categories

Resources