Bug P4
Status Update
Comments
ko...@gmail.com <ko...@gmail.com> #2
Project: platform/frameworks/support
Branch: androidx-master-dev
commit 85c1de7395f07e3256a50706d4f619e654624250
Author: Ian Lake <ilake@google.com>
Date: Fri Apr 19 13:54:21 2019
Ensure Fragment OnBackPressedCallbacks take priority
As FragmentController is not yet driven by a
LifecycleObserver ( b/127528777 ), LifecycleObservers on
Fragments are not necessarily nested within
LifecycleObservers registered at the Activity level.
This can lead to cases where a Fragment is started
before the Activity's Lifecycle is started.
In the case of FragmentManager's usage of Lifecycle to
add OnBackPressedCallbacks, that nesting is critical
to ensure the ordering of callbacks. By creating a
host level Lifecycle that is exactly nested outside
the callbacks to FragmentController, we can ensure
the proper nesting in callbacks.
Test: added test passes
Change-Id: I958d2389c90dcd9d157c6c3d30dffb689ed40c62
M fragment/src/androidTest/AndroidManifest.xml
M fragment/src/androidTest/java/androidx/fragment/app/OnBackPressedCallbackTest.kt
M fragment/src/main/java/androidx/fragment/app/FragmentActivity.java
https://android-review.googlesource.com/948209
https://goto.google.com/android-sha1/85c1de7395f07e3256a50706d4f619e654624250
Branch: androidx-master-dev
commit 85c1de7395f07e3256a50706d4f619e654624250
Author: Ian Lake <ilake@google.com>
Date: Fri Apr 19 13:54:21 2019
Ensure Fragment OnBackPressedCallbacks take priority
As FragmentController is not yet driven by a
LifecycleObserver (
Fragments are not necessarily nested within
LifecycleObservers registered at the Activity level.
This can lead to cases where a Fragment is started
before the Activity's Lifecycle is started.
In the case of FragmentManager's usage of Lifecycle to
add OnBackPressedCallbacks, that nesting is critical
to ensure the ordering of callbacks. By creating a
host level Lifecycle that is exactly nested outside
the callbacks to FragmentController, we can ensure
the proper nesting in callbacks.
Test: added test passes
Change-Id: I958d2389c90dcd9d157c6c3d30dffb689ed40c62
M fragment/src/androidTest/AndroidManifest.xml
M fragment/src/androidTest/java/androidx/fragment/app/OnBackPressedCallbackTest.kt
M fragment/src/main/java/androidx/fragment/app/FragmentActivity.java
Description
Let's say you have two fragments, FragmentA and FragmentB. And let's say FragmentA launches FragmentB with the navigation library with some data, manipulates it, and then returns the manipulated result back to FragmentA.
Following the guide fromhttps://developer.android.com/guide/navigation/use-graph/programmatic#returning_a_result , it's recommended to "listen" for navigation results via
And it also says:
There are two bugs going on here with this approach.
First Bug
This approach does not work with app restoration from process death. Or put it another way, doesn't work when testing with "Don't Keep Activities" in the developer options on the phone.
This is because
navController.currentBackStackEntry
is null at the timeFragment.onViewCreated
is called on app restoration. In the normal app flow, it will be non-null.My solution to this problem is to use the
currentBackStackEntryFlow
to essentially wait until there is a NavBackStackEntry and then observe on the LiveData. Here would be an example of the code:However, I do not fully know / understand the ramifications of this code. If the flow emits multiple times, then it will add multiple duplicate observers. So this seems like a pretty big flaw in my solution. But I am not sure if there will be multiple emits to the collect. So Android community / Google, please find flaws in this!
Second Bug
If you want to consume the result once, the guide recommends this:
However there are flaws with this. While it does fix the problem where the result won't be emitted from the observer on device rotation (cause in this case, you only want to consume the result once and have it never emit again on rotation), but it does not allow for subsequent results.
Let's go back to my initial example with FragmentA and FragmentB but with one more additional step. Let's say FragmentA launches FragmentB with some data, manipulates it, and then returns the manipulated result back to FragmentA. FragmentA consumes the result and calls
SavedStateHandle.remove(...)
.Now let's say FragmentA does the same again. It launches FragmentB which returns the result back to FragmentA a second time. Since
remove
was called after consuming the first result, the second result will no longer be consumed becauseremove
also removes theLiveData
from theSavedStateHandle
. This is the bug.The code using the guide's example (and without my first bug fix) would look something like this.
My solution would be to keep the
LiveData
alive but only null out the value of the LiveData to "consume" the result. My solution, which I don't like mind you, would be:The obvious flaw here is that nullable results cannot be used as null here means that we consumed the result.
Combined Solutions
So putting my two solutions together, the code would look something like this:
My Ask
So my ask of you, Google, is to have a better approach to app process death & restoration when returning results as well as having better support for consuming results once while still allowing for subsequent results. Once a result is consumed, it should not be emitted again on device rotation for example.
Improvements
I did make some generic extension functions for anyone wanting to use my gross solutions that works setting & getting fragment results.
You can put these extension functions in some
FragmentUtils
orNavigationUtils
class.Using my example of FragmentA and FragmentB, FragmentA would use this extension function:
And FragmentB would use this extension function: