Table of Contents
Open Table of Contents
The Concept
What is it?
- When user is in middle of the back gesture, it shows a preview of the action that’s about to happen.
- Examples: peeking the previous navigation destination, peeking device home screen, animating a bottomsheet away.
What it implies?
- Framework has to know what would happen on back action before it happens.
- Back gesture is not a single click event. It’s a swipe with a start and an end. It can be canceled. The progression of the gesture drives the peek animation.
- Back is only invoked once the whole gesture is completed.
Hows it implemented?
- Navigation components (FragmentManager, NavController etc.) intercept back gestures to handle the animation.
- If there is no interception, the OS handles the animation to show previous Activity (or home screen, etc.)
- To be able to know in advance if there will be any interceptions: back handlers need to be stored as objects in a collection.
- Only one interceptor is invoked.
What it changes?
onBackPressed()is no longer invoked, since it does not fit this model (it would always be called, which can interfere with other interceptors).- If you use standard components you get the gesture previews mostly for free.
- If you had code in
onBackPressed(), you now need to register it as a callback. But if you’ve been paying attention: this act means that other interceptors will not be invoked and you will not have the default preview animations!
The mental model change: callbacks are to be used temporarily!
Previously the only option was to override onBackPressed() and thus listen for the duration of the activity. Imagine users EditText is in a wrong state and thus we want to block the back press. You likely had some code in onBackPressed that checked with EditText if back can proceed or not. With callbacks this approach is an antipattern. Now you should reactively enable a callback as soon as EditText is in a wrong state, and disable it as soon as it exits that state.
Only by adapting to this new mindset you allow the framework to do the predictive animations. Blindly copying logic from onBackPressed() to callbacks is a mistake.
Tech notes:
Enabling it:
- Add to manifest (either app or activity level):
android:enableOnBackInvokedCallback="true"
Listener APIs:
- AndroidX:
onBackPressedDispatcher.addCallback - Platform, since API 33:
onBackInvokedDispatcher.registerOnBackInvokedCallback
After enabling predictive back gestures:
- Activity
onBackPressed()is no longer invoked. - Gesture and button navigation modes stops generating
KeyEvent.KEYCODE_BACK. (Note that pressing back on emulator controls panel still does send this event)
Programmatically invoking onBackPressed:
- Invoke
onBackPressedDispatcher.onBackPressed()(androidx) - You can also call
activity.onBackPressed(), it just forwards the call toonBackPressedDispatcher. But you are invoking a deprecated function so better dont. - This will invoke the androidx callback. But it will not invoke the Platform callback.
- There’s no way of programmatically invoking the Platform callback. Therefore either don’t use it or don’t invoke onBackPressed() on your own.
Logging back presses
As mentioned above, adding a callback means you handle it now - you lose animations, and only single callback is invoked. Thus transparently doing something trivial like logging/analytics on back press is now impossible.
Workaround can be focusing on navigation changes instead. Adding OnBackStackChangedListener to FragmentManager, or observing the backstack size in compose navigation.
The platform callback on API 36 got PRIORITY_SYSTEM_NAVIGATION_OBSERVER for a similar purpose. This means you can add a low priority listener that does not interfere with predictive animations.
But all the caveats still apply:
- If any other callback is active, this won’t be invoked.
- Including FragmentManagers default callback for handling gesture animations etc.
- In other words it can only be invoked when backpress reaches Activity.
- If onBackPressed() is invoked programmatically this won’t be called.
So it can be unpredictable as it depends very much on app state.