Proposal for Fragment Results API
See original GitHub issueProblem
We are currently using our own solution for handling fragment to fragment communication, since there was no official way to do that, that fits our architecture last time we checked (#2219 ). While this approach works, it comes with couple of caveats.
Screen results are not lifecycle aware
We dispatch the screen results after the fragment transactions are finished. Since the callback is not lifecycle aware, it’s possible for the app to access views when they are not inflated or in backgroud which can cause crashes to the app.
Bugs
We currently have a open bug report for screen results, where sometimes the screen results are being casted to a different type and cause the app to crash. While it’s easy to just check the type before casting, it shouldn’t have happened in the first place.
When passing back the screen result, we try to make sure the target fragment is present. Sometimes that causes the app to crash when the fragment has not been attached to the activity or if the app has gone into background in the process.
Extending a new type when creating Fragment
Since Fragment didn’t had concept of screen results, we created a new type ExpectsResult
to implement onScreenResult
callback which receives the screen results. Accidentally missing extending this type can cause the app to crash.
Goal
The goal is to avoid managing our own solution for manging communication between fragments, and to resolve the above mentioned issues.
Approach
Android has created a Fragment results API which is available from version 1.3.0-alpha04
, which allows us to pass data between fragments. It solves some of the concerns we mentioned above, the results listener is lifecycle aware and only passes the results when the fragment is in started state.
The results listener API is baked into FragmentManager
, which allows us to directly use it in fragments without having to extend new types.
Listening to screen results
Fragment results API provides us with a lifecycle aware observer that let’s us listen to results based on the request key
setFragmentResultListener(DatePickerResult) { result ->
if (result is Succeeded) {
val selectedDate = result.result as SelectedDate
val event = ManualDateSelected(selectedDate = selectedDate.date, currentDate = LocalDate.now(userClock))
hotEvents.onNext(event)
}
}
For listening to multiple fragment results, we can simply add another listener with a different request key
setFragmentResultListener(DatePickerResult) { result ->
if (result is Succeeded) {
val selectedDate = result.result as SelectedDate
val event = ManualDateSelected(selectedDate = selectedDate.date, currentDate = LocalDate.now(userClock))
hotEvents.onNext(event)
}
}
setFragmentResultListener(OverdueAppointmentRemoved) {
router.pop()
}
Sending the result
We can continue using the existing Router API for sending back the results, internal Router will send the result using FragmentManager
which acts as a central store for all the fragment results.
router.popWithResult(Succeeded(SelectedDate(setDate)))
Migration
The migration process for new fragment results API is pretty straightforward.
- In
onViewCreated
of the fragment, we need to add a listener for the fragment results for the request key. If you have multiple request keys, you need to add multiple listeners. (We can use method references for result handling)
- Once you have added all the listeners for the request keys in the existing
onScreenResult
, we can stop extendingExpectsResult
and removeonScreenResult
Consequences
Multiple listeners
Traditionally when we are handling screen results, we just have one single call back and that would return all the results. But with this approach we have to add multiple listeners to handle different requests.
Issue Analytics
- State:
- Created 2 years ago
- Comments:12 (12 by maintainers)
Top GitHub Comments
Yep, it will pass the request type and result and we can continue using the existing approach. So, only the internal implementation is changed.
The listeners are lifecycle aware, so internally it will observe lifecycle and do necessary operations. So we will only get results when the state is started
I have also updated the code to be a bit more concise and readable.