Home » Android » android – Show confirmation on back/up in Fragment with Navigation Architecture Component

android – Show confirmation on back/up in Fragment with Navigation Architecture Component

Posted by: admin June 15, 2020 Leave a comment

Questions:

I am using the Navigation Architecture Component for Android.

For one of my fragments I wish to intercept “back” and “up” navigation, so that I can show a confirmation dialog before discarding any unsaved changes by the user. (Same behavior as the default Calendar app when you press back/up after editing event details)

My current approach (untested) is as follows:

For “up” navigation, I override onOptionsItemSelected on the fragment:

override fun onOptionsItemSelected(item: MenuItem?): Boolean {
    if(item?.itemId == android.R.id.home) {
        if(unsavedChangesExist()) {
            // TODO: show confirmation dialog
            return true
        }
    }
    return super.onOptionsItemSelected(item)
}

For “back” navigation, I created a custom interface and callback system between the fragment and its activity:

interface BackHandler {
    fun onBackPressed(): Boolean
}

class MainActivity : AppCompatActivity() {
    ...

    val backHandlers: MutableSet<BackHandler> = mutableSetOf()

    override fun onBackPressed() {
        for(handler in backHandlers) {
            if(handler.onBackPressed()) {
                return
            }
        }
        super.onBackPressed()
    }

    ...
}

class MyFragment: Fragment(), BackHandler {
    ...

    override fun onAttach(context: Context) {
        super.onAttach(context)
        if (context is MainActivity) {
            context.backHandlers.add(this)
        }
    }

    override fun onDetach() {
        (activity as? MainActivity)?.backHandlers?.remove(this)
        super.onDetach()
    }

    override fun onBackPressed(): Boolean {
        if(unsavedChangedExist()) {
            // TODO: show confirmation dialog
            return true
        }
    }

    ...
}

This is all pretty gross and boilerplatey for such a simple thing. Is there a better way?

How to&Answers:

As of androidx.appcompat:appcompat:1.1.0-beta01, in order to intercept the back button with navigation component you need to add a callback to the OnBackPressedDispatcher. This callback has to extend OnBackPressedCallback and override handleOnBackPressed. OnBackPressedDispatcher follows a chain of responsibility pattern to handle the callbacks. In other words, if you set your callback as enabled, only your callback will be executed. Otherwise, OnBackPressedDispatcher will ignore it and proceed to the next callback, and so on until it finds an enabled one (this might be useful when you have more than one callback, for instance). More info on this here.

So, in order to show your dialog, you would have to do something similar to this:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
  super.onViewCreated(view, savedInstanceState)

  val callback = object : OnBackPressedCallback(true /** true means that the callback is enabled */) {
    override fun handleOnBackPressed() {
        // Show your dialog and handle navigation
    }
  }

  // note that you could enable/disable the callback here as well by setting callback.isEnabled = true/false

  requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback)
}

As for the up button, it seems like (at least for now) there aren’t many possibilities. The only option I could find up until now that uses the navigation component is to add a listener for the navigation itself, which would handle both buttons at the same time:

navController.addOnDestinationChangedListener { navController, destination ->
  if (destination.id == R.id.destination) {
    // do your thing
  }
}

Regardless, this has the caveat of allowing the activity or fragment where you add the listener to know about destinations it might not be supposed to.

Answer:

With the navigation architecture components, you can do something like this:

  1. Tell your activity to dispatch all up clicks on the home button(back arrow) to anyone listening for it. This goes in your activity.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
     if (item.itemId == android.R.id.home) {
         onBackPressedDispatcher.onBackPressed()
         return true
     }
     return super.onOptionsItemSelected(item)
}
  1. Then in your fragments, consume the events like so
override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        requireActivity().onBackPressedDispatcher.addCallback(this) {
           if (*condition for showing dialog here*) {
               // Show dialog
           } else {
               // pop fragment by calling function below. Analogous to when the user presses the system UP button when the associated navigation host has focus.
               findNavController().navigateUp()
           }
        }
    }

Answer:

for up navigation simply override onOptionsItemSelected()

override fun onOptionsItemSelected(item: MenuItem): Boolean =
    when (item.itemId) {
        android.R.id.home -> {
            showDialog() // show your dialog here
            true
        }
        else -> super.onOptionsItemSelected(item)
}

Answer:

If you’re using it with AppBarConfiguration, with the latest release there is now an AppBarConfiguration.OnNavigateUpListener. Refer to the below link for more information

https://developer.android.com/reference/androidx/navigation/ui/AppBarConfiguration.OnNavigateUpListener