Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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/

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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(); } }

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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()

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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()

Slide 19

Slide 19 text

Migration

Slide 20

Slide 20 text

Option 1, if (aheadOfTimeBack) { // Add onBackPressed as default back behavior. mDefaultBackCallback = this::navigateBack; getOnBackInvokedDispatcher().registerSystemOnBackInvokedCallback(mDefaultBackCallback); }

Slide 21

Slide 21 text

Option 2, •Custom back code •Using AndroidX

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

Option 3, •Custom back code •Not using AndroidX

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

updates…

Slide 30

Slide 30 text

Enable gesture at activity level

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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 } }

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

Demo…

Slide 37

Slide 37 text

Demo…

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

Thank You @navczydev