$30 off During Our Annual Pro Sale. View Details »

IO Extended 23 - What's up with Android Back

IO Extended 23 - What's up with Android Back

GDG Montreal

June 22, 2023
Tweet

More Decks by GDG Montreal

Other Decks in Programming

Transcript

  1. What’s up with Android’s
    back?
    @navczydev

    View Slide

  2. override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
    return if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() === 0) {
    // do something on back.
    true
    } else super.onKeyDown(keyCode, event)
    }
    https://1000logos.net/android-logo/

    View Slide

  3. override fun onBackPressed() {
    super.onBackPressed()
    }
    https://android-developers.googleblog.com/2009/12/back-and-other-hard-keys-three-stories.html

    View Slide

  4. Welcome to
    gesture based
    navigation
    https://www.youtube.com/watch?v=Ljtz7T8R_Hk

    View Slide

  5. View Slide

  6. View Slide

  7. https://youtu.be/Elpqr5xpLxQ?list=RDCMUCVHFbqXqoYvEWM1Ddxl0QDg

    View Slide

  8. NativePreInputStage
    @Override
    protected int onProcess(QueuedInputEvent q) {
    if (q.mEvent instanceof KeyEvent) {
    fi
    nal KeyEvent event = (KeyEvent) q.mEvent;
    // If the new back dispatch is enabled, intercept KEYCODE_BACK before it reaches the
    // view tree or IME, and invoke the appropriate {@link OnBackInvokedCallback}.
    if (isBack(event)
    && mContext != null
    && WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) {
    OnBackInvokedCallback topCallback =
    getOnBackInvokedDispatcher().getTopCallback();
    if (event.getAction() == KeyEvent.ACTION_UP) {
    if (topCallback != null) {
    topCallback.onBackInvoked();
    return FINISH_HANDLED;
    }
    }
    // ..
    }
    }
    }
    https://shorturl.at/jCGUV

    View Slide

  9. onBackPressed()
    public void onBackPressed() {
    mOnBackPressedDispatcher.onBackPressed();
    }
    private
    fi
    nal OnBackPressedDispatcher mOnBackPressedDispatcher =
    new OnBackPressedDispatcher(new Runnable() {
    @Override
    public void run() {
    try {
    ComponentActivity.super.onBackPressed();
    } catch (IllegalStateException e) {
    }
    }
    });
    https://developer.android.com/jetpack/androidx/releases/activity#1.6.0-alpha05

    View Slide

  10. onBackPressedDispatcher
    public void onBackPressed() {
    Iterator iterator =
    mOnBackPressedCallbacks.descendingIterator();
    while (iterator.hasNext()) {
    OnBackPressedCallback callback = iterator.next();
    if (callback.isEnabled()) {
    callback.handleOnBackPressed();
    return;
    }
    }
    if (mFallbackOnBackPressed != null) {
    mFallbackOnBackPressed.run();
    }
    }

    View Slide

  11. OnBackPressedCallback - Activity
    onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
    override fun handleOnBackPressed() {
    isEnabled = false
    }
    })
    if (Build.VERSION.SDK_INT >= 33) {
    updateBackInvokedCallbackState()
    onBackPressedCallback.enabledChangedCallback = enabledChangedCallback
    }
    LifeCycleOwner
    OnBackPressed
    Callback

    View Slide

  12. onBackPressedCallback - Dialog
    MaterialAlertDialogBuilder(this)
    .setTitle(getString(R.string.alert_dialog_title))
    .setPositiveButton(
    getString(R.string.ok)
    ) { dialog, _ -> dialog.dismiss() }
    .create()
    .apply {
    onBackPressedDispatcher.addCallback(this) {
    dismiss()
    // remove this callback
    remove()
    }
    }.show()

    View Slide

  13. onBackPressedCallback - Fragment
    val backPressedCallback = object : OnBackPressedCallback(enabled = true) {
    override fun handleOnBackPressed() {
    //... back logic
    }
    }
    requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, backPressedCallback)

    View Slide

  14. onBackPressedCallback - Composable
    https://developer.android.com/reference/kotlin/androidx/activity/compose/package-summary#backhandler
    MaterialTheme {
    // adds the lambda to
    // LocalOnBackPressedDispatcherOwner
    BackHandler(enabled = isBackEnable) {
    onBackPressed()
    }
    }

    View Slide

  15. onBackPressedCallback
    // De
    fi
    ne a callback
    val callback = onBackPressedDispatcher.addCallback(enabled = true) {
    // Do your custom stuff here..
    }

    View Slide

  16. onBackPressedCallback
    // De
    fi
    ne a callback
    val callback = onBackPressedDispatcher.addCallback(enabled = true) {
    // Do your custom stuff here..
    }
    // remove the callback from onBackPressedDispatcher
    callback.remove()

    View Slide

  17. onBackPressedCallback
    // De
    fi
    ne a callback
    val callback = onBackPressedDispatcher.addCallback(enabled = true) {
    // Do your custom stuff here..
    }
    // remove the callback from onBackPressedDispatcher
    callback.remove()
    // update the enable state
    callback.isEnabled = true

    View Slide

  18. onBackPressedCallback
    // De
    fi
    ne a callback
    val callback = onBackPressedDispatcher.addCallback(enabled = true) {
    // Do your custom stuff here..
    }
    // remove the callback from onBackPressedDispatcher
    callback.remove()
    // update the enable state
    callback.isEnabled = true
    // check if any enabled callbacks exists
    onBackPressedDispatcher.hasEnabledCallbacks()

    View Slide

  19. Migration

    View Slide

  20. Option 1,
    if (aheadOfTimeBack) {
    // Add onBackPressed as default back behavior.
    mDefaultBackCallback = this::navigateBack;
    getOnBackInvokedDispatcher().registerSystemOnBackInvokedCallback(mDefaultBackCallback);
    }
    // ..
    android:enableOnBackInvokedCallback=“true"
    // ..
    >

    View Slide

  21. Option 2,
    •Custom back code
    •Using AndroidX

    View Slide

  22. OnBackPressedDispatcher
    •Backward compatible, API >=21
    •Single source of truth

    View Slide

  23. Option 3,
    •Custom back code
    •Not using AndroidX

    View Slide

  24. OnBackInvokedDispatcher
    •API >= 13
    •Need to manage back functionality at multiple places

    View Slide

  25. Platform API
    if (BuildCompat.isAtLeastT()) {
    }

    View Slide

  26. Platform API
    if (BuildCompat.isAtLeastT()) {
    // Step 1.
    val callback =
    OnBackInvokedCallback {
    lifecycleScope.launch {
    mainActivityViewModel.updateFlag(shouldUnregister = true)
    }
    }
    }

    View Slide

  27. Platform API
    if (BuildCompat.isAtLeastT()) {
    // Step 1.
    val callback =
    OnBackInvokedCallback {
    lifecycleScope.launch {
    mainActivityViewModel.updateFlag(shouldUnregister = true)
    }
    }
    // Step 2.
    onBackInvokedDispatcher.registerOnBackInvokedCallback(
    OnBackInvokedDispatcher.PRIORITY_DEFAULT,
    callback
    )
    } DEFAULT or OVERLAY

    View Slide

  28. Platform API
    if (BuildCompat.isAtLeastT()) {
    // Step 1.
    val callback =
    OnBackInvokedCallback {
    lifecycleScope.launch {
    mainActivityViewModel.updateFlag(shouldUnregister = true)
    }
    }
    // Step 2.
    onBackInvokedDispatcher.registerOnBackInvokedCallback(
    OnBackInvokedDispatcher.PRIORITY_DEFAULT,
    callback
    )
    // Step 3.
    lifecycleScope.launch {
    mainActivityViewModel.backCallback.collectLatest { backFlag ->
    if (backFlag) {
    onBackInvokedDispatcher.unregisterOnBackInvokedCallback(callback)
    }
    }
    }
    }
    DEFAULT or OVERLAY

    View Slide

  29. updates…

    View Slide

  30. Enable gesture at activity level
    android:enableOnBackInvokedCallback=“false"
    >
    android:name=".MainActivity"
    android:enableOnBackInvokedCallback="false"
    android:exported=“true"/>
    android:name=".SecondActivity"
    android:enableOnBackInvokedCallback="true"
    android:exported="false" />

    View Slide

  31. Cross activity custom transitions
    overrideActivityTransition(
    OVERRIDE_TRANSITION_OPEN,
    R.anim.slide_in_left,
    R.anim.slide_out_left
    )
    overrideActivityTransition(
    OVERRIDE_TRANSITION_CLOSE,
    R.anim.slide_in_left,
    R.anim.slide_out_left
    )
    https://developer.android.com/jetpack/androidx/releases/activity#1.8.0-alpha01

    View Slide

  32. Back progress
    OnBackPressedCallback(enabled = true) {
    override fun handleOnBackProgressed(backEvent: BackEvent) {
    when (backEvent.swipeEdge) {
    BackEvent.EDGE_LEFT ->
    composeHolder.translationX
    = backEvent.progress * maxXShift
    BackEvent.EDGE_RIGHT ->
    composeHolder.translationX
    = -(backEvent.progress * maxXShift)
    }
    }
    override fun handleOnBackStarted() {
    }
    override fun handleOnBackCancelled() {
    }
    }
    https://developer.android.com/jetpack/androidx/releases/activity#1.8.0-alpha01

    View Slide

  33. AndroidX Transition 🤝 back Gesture
    https://developer.android.com/jetpack/androidx/releases/transition#1.5.0-alpha01
    val callbackWithTransitionsAPI = object : OnBackPressedCallback(enabled = true) {
    var controller: TransitionSeekController? = null
    override fun handleOnBackStarted(backEvent: BackEvent) {
    controller = TransitionManager.controlDelayedTransition(
    binding.root, // rootView that contains "tv1" and “tv2"
    Fade() // Slide, ChangeBound, Explode, etc (Only Transitions work that supports seeking)
    )
    binding.tv1.isVisible = true
    binding.tv2.isVisible = false
    }
    override fun handleOnBackProgressed(backEvent: BackEvent) {
    if (controller?.isReady == true) {
    controller?.currentPlayTimeMillis = (backEvent.progress * controller?.durationMillis!!).toLong()
    }
    }
    override fun handleOnBackPressed() {
    controller?.animateToEnd()
    this.isEnabled = false
    }
    override fun handleOnBackCancelled() {
    // If the user cancels the back gesture, reset the state
    TransitionManager.beginDelayedTransition(binding.root)
    binding.tv1.isVisible = true
    binding.tv2.isVisible = true
    }
    }

    View Slide

  34. Material design support
    •BottomSheet
    •SideSheet
    •SearchView
    1.10.0-alpha03

    View Slide

  35. Predictive back design guide
    https://developer.android.com/design/ui/mobile/guides/patterns/predictive-back

    View Slide

  36. Demo…

    View Slide

  37. Demo…

    View Slide

  38. Sample Code
    https://github.com/navczydev/droidcon-SF-sample

    View Slide

  39. References
    • https://android-developers.googleblog.com/2009/12/back-and-other-hard-keys-three-stories.html
    • https://developer.android.com/guide/navigation/predictive-back-gesture?authuser=1#migrate-existing
    • https://www.youtube.com/watch?v=Elpqr5xpLxQ&t=514s
    • https://developer.android.com/reference/kotlin/androidx/activity/compose/package-summary#backhandler
    • https://cs.android.com/android/platform/superproject/+/refs/heads/master:frameworks/base/core/java/android/view/
    ViewRootImpl.java;drc=7346c436e5a11ce08f6a80dcfeb8ef941ca30176;l=11055?q=NativePreImeInputStage%20
    • https://developer.android.com/design/ui/mobile/guides/patterns/predictive-back
    https://logos.fandom.com/wiki/Android/Versions

    View Slide

  40. Thank You
    @navczydev

    View Slide