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:

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:

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.

You can learn more about 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:

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.