Step #6: Responding to Input
So now we are able to display the dialog. However, we still need to find out if the user clicks the “Retry” button, so we can retry the import operation.
For that, first, add this companion object
to ErrorDialogFragment
:
companion object {
const val KEY_RETRY = "retryRequested"
}
This just sets up a constant. We use a companion object
here so other classes — such as RosterListFragment
— have access to the constant while also keeping it tied to ErrorDialogFragment
. If we were only using this constant inside of ErrorDialogFragment
, we could have just used a private const val
file-level property, as we have before.
Then, modify onRetryRequest()
to be:
private fun onRetryRequest() {
findNavController()
.previousBackStackEntry?.savedStateHandle?.set(KEY_RETRY, args.scenario)
}
This strange construction is how we can get a result out of this dialog and over to RosterListFragment
as the one that displayed the dialog. Here, we:
- Get our
NavController
, for access to the Navigation component APIs - Find the
BackStackEntry
corresponding to whatever it was that displayed this dialog - Get a
SavedStateHandle
for thatBackStackEntry
, whereSavedStateHandle
is a bit like aHashMap
(key-value store of data) - Set the
KEY_RETRY
value in that state to be theErrorScenario
that was passed into us via the navigation arguments
With this in place, when the user clicks the “Retry” button, we update this SavedStateHandle
with the ErrorScenario
that triggered the dialog.
Over on RosterListFragment
, we can now find out about changes in the KEY_RETRY
value and use that to retry the import.
First, though, add this clearImportError()
function to RosterListFragment
:
private fun clearImportError() {
findNavController()
.getBackStackEntry(R.id.rosterListFragment)
.savedStateHandle
.set(ErrorDialogFragment.KEY_RETRY, ErrorScenario.None)
}
Then, add this block of code to the bottom of onViewCreated()
:
findNavController()
.getBackStackEntry(R.id.rosterListFragment)
.savedStateHandle
.getLiveData<ErrorScenario>(ErrorDialogFragment.KEY_RETRY)
.observe(viewLifecycleOwner) { retryScenario ->
when (retryScenario) {
ErrorScenario.Import -> {
clearImportError()
motor.importItems()
}
}
}
Here, we:
- Get our
NavController
, for access to the Navigation component APIs - Find our own
BackStackEntry
, based upon our destination ID (R.id.rosterListFragment
) - Get the
SavedStateHandle
for thatBackStackEntry
- Observe a
LiveData
of objects associated withKEY_RETRY
, so we find out when that value changes - If we get an
Import
error, clear it, then retry the import usingmotor.importItems()
LiveData
is a bit like StateFlow
. It represents a source of states, in this case the states of our KEY_RETRY
value. Our observe()
lambda expression will be invoked when that state changes. LiveData
is part of the Jetpack and works with both Java and Kotlin, whereas StateFlow
is a Kotlin-specific construct. Jetpack Navigation works with both Java and Kotlin, so it uses LiveData
for consistency between the two languages.
LiveData
in the "Thinking About Threads and LiveData" chapter of Elements of Android Jetpack!
So, when the user clicks the “Retry” button, the ErrorDialogFragment
emits an ErrorScenario
, which RosterListFragment
picks up and uses to handle whatever needs to be retried.
clearImportError()
addresses the fact that these results are delivered by LiveData
. LiveData
intrinsically is a cache. If we do not remove this value from our SavedStateHandle
, we can wind up with the error being re-delivered to us… erroneously:
- Get the error and see the dialog
- Click “Retry”, and wind up getting the error again
- Click “Cancel” to dismiss the dialog
- Navigate to another screen (e.g., choose Settings from the overflow menu)
- Navigate back to
RosterListFragment
- When we start observing the
LiveData
again, we get the error re-delivered to us, so we retry once more, unexpectedly
remove()
on the SavedStateHandle
sets our value to None
, so we skip over the Import
error logic and avoid this infinite loop.
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.