Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Lessons learned decoupling Architecture Compone...

Lessons learned decoupling Architecture Components from platform specific code

Follow along as the Android team walks you through the process of taking some of their architectural building blocks, such as SavedState and System Back, through the process of decoupling them from platform specific implementations and how they approached the task of writing good common APIs in preparation for future integrations with other platforms while maintaining the quality achieved in android.

Avatar for Marcello Galhardo

Marcello Galhardo

May 23, 2025
Tweet

More Decks by Marcello Galhardo

Other Decks in Programming

Transcript

  1. Lessons learned decoupling Architecture Components from platform specific code Jeremy

    Woods, Marcello Galhardo linkedIn@jeremybwoods 🦋 @marcellogalhardo.dev
  2. • 6 people across 3 countries. • 12 different libraries

    ◦ Activity, DataStore, Fragment, Hilt, Lifecycle, Navigation, Paging, Room, SavedState, ViewModel, SQLite, XProcessing Architecture Components Team • 6 people across 3 countries.
  3. • 6 people across 3 countries. • 14 different libraries

    ◦ Lifecycle, SavedState, ViewModel. Navigation, Activity, Fragment, Room, DataStore, Paging, Dagger Hilt, SQLite, XProcessing. ◦ Navigation3, NavigationEvent Architecture Components Team • 6 people across 3 countries. • 1 different libraries ◦ Activity, DataStore, Fragment, Hilt, Lifecycle, Navigation, Paging, Room, SavedState, ViewModel, SQLite, XProcessing
  4. • Android is Kotlin-first (since 2019). • Code sharing across

    native platforms. Why KMP? • Android is Kotlin-first (since 2019).
  5. • Android is Kotlin-first (since 2019). • Code sharing across

    native platforms. • Help building the ecosystem. Why KMP? • Android is Kotlin-first (since 2019). • Code sharing across native platforms.
  6. • Used to save and restore state in a process

    death. • First alpha released in December 2018, written in Java. • KMP-compatible release in May 2025 (1.3.0). AndroidX SavedState
  7. public class SavedStateRegistryController { public val savedStateRegistry: SavedStateRegistry public fun

    performRestore(savedState: Bundle?) public fun performSave(outBundle: Bundle) // {...} }
  8. public class SavedStateRegistryController { public val savedStateRegistry: SavedStateRegistry public fun

    performRestore(savedState: Bundle?) public fun performSave(outBundle: Bundle) // {...} }
  9. public class SavedStateRegistryController { public val savedStateRegistry: SavedStateRegistry public fun

    performRestore(savedState: Bundle?) public fun performSave(outBundle: Bundle) // {...} }
  10. public class SavedStateRegistry { public fun consumeRestoredStateForKey(key: String): Bundle? public

    fun registerSavedStateProvider(key: String, provider: SavedStateProvider) public fun interface SavedStateProvider { public fun saveState(): Bundle } // {...} }
  11. public class SavedStateRegistry { public fun consumeRestoredStateForKey(key: String): Bundle? public

    fun registerSavedStateProvider(key: String, provider: SavedStateProvider) public fun interface SavedStateProvider { public fun saveState(): Bundle } // {...} }
  12. public class SavedStateRegistry { public fun consumeRestoredStateForKey(key: String): Bundle? public

    fun registerSavedStateProvider(key: String, provider: SavedStateProvider) public fun interface SavedStateProvider { public fun saveState(): Bundle } // {...} }
  13. class ComponentActivity : Activity() { val controller = SavedStateRegistryController.create(owner =

    this) override fun onCreate(savedInstanceState: Bundle?) { controller.performRestore(savedInstanceState) // {...} } override fun onSaveInstanceState(outState: Bundle) { // {...} controller.performSave(outState) } }
  14. class ComponentActivity : Activity() { val controller = SavedStateRegistryController.create(owner =

    this) override fun onCreate(savedInstanceState: Bundle?) { controller.performRestore(savedInstanceState) // {...} } override fun onSaveInstanceState(outState: Bundle) { // {...} controller.performSave(outState) } }
  15. class ComponentActivity : Activity() { val controller = SavedStateRegistryController.create(owner =

    this) override fun onCreate(savedInstanceState: Bundle?) { controller.performRestore(savedInstanceState) // {...} } override fun onSaveInstanceState(outState: Bundle) { // {...} controller.performSave(outState) } }
  16. // Or a more high-level API. class MyViewModel( private val

    handle: SavedStateHandle, ) : ViewModel() { val name = handle.getMutableStateFlow(“key”) { “John Doe” } }
  17. class Bundle { constructor(capacity: Int) constructor(b: Bundle?) constructor(persistableBundle: PersistableBundle) fun

    putAll(bundle: Bundle) fun size(): Int fun isEmpty(): Boolean fun clear() public override fun clone(): Any fun putBoolean(key: String, value: Boolean) fun putByte(key: String, value: Byte) fun putChar(key: String, value: Char) fun putShort(key: String, value: Short) fun putInt(key: String, value: Int) fun putLong(key: String, value: Long) fun putFloat(key: String, value: Float) fun putDouble(key: String, value: Double) fun putString(key: String, value: String?) fun putCharSequence(key: String, value: CharSequence?) fun putParcelable(key: String, value: Parcelable?) fun putParcelableArray(key: String, value: Array<Parcelable?>?) fun putParcelableArrayList(key: String, value: ArrayList<out Parcelable>?) fun putSparseParcelableArray(key: String, value: android.util.SparseArray<out Parcelable>?) fun putSerializable(key: String, value: Serializable?) fun putIntegerArrayList(key: String, value: ArrayList<Int>?) fun putStringArrayList(key: String, value: ArrayList<String>?) fun putCharSequenceArrayList(key: String, value: ArrayList<CharSequence>?) fun putBundle(key: String, value: Bundle?) fun putBooleanArray(key: String, value: BooleanArray?) fun putByteArray(key: String, value: ByteArray?) fun putShortArray(key: String, value: ShortArray?) fun putCharArray(key: String, value: CharArray?) fun putIntArray(key: String, value: IntArray?) fun putLongArray(key: String, value: LongArray?) fun putFloatArray(key: String, value: FloatArray?) fun putDoubleArray(key: String, value: DoubleArray?) fun putStringArray(key: String, value: Array<String?>?) fun putCharSequenceArray(key: String, value: Array<CharSequence?>?) fun putParcelableArray(key: String, value: Array<Parcelable?>?) fun putSize(key: String, value: Size?) fun putSizeF(key: String, value: SizeF?) fun getBoolean(key: String): Boolean fun getBoolean(key: String, defaultValue: Boolean): Boolean fun getByte(key: String): Byte fun getByte(key: String, defaultValue: Byte): Byte fun getChar(key: String): Char fun getChar(key: String, defaultValue: Char): Char fun getShort(key: String): Short fun getShort(key: String, defaultValue: Short): Short fun getInt(key: String): Int fun getInt(key: String, defaultValue: Int): Int fun getLong(key: String): Long fun getLong(key: String, defaultValue: Long): Long fun getFloat(key: String): Float fun getFloat(key: String, defaultValue: Float): Float fun getDouble(key: String): Double fun getDouble(key: String, defaultValue: Double): Double fun getString(key: String): String? fun getCharSequence(key: String): CharSequence? fun <T : Parcelable?> getParcelable(key: String): T? fun <T : Parcelable?> getParcelableArray(key: String): Array<T?>? fun <T : Parcelable?> getParcelableArrayList(key: String): ArrayList<T>? fun <T : Parcelable?> getSparseParcelableArray(key: String): android.util.SparseArray<T>? fun getSerializable(key: String): Serializable? fun getIntegerArrayList(key: String): ArrayList<Int>? fun getStringArrayList(key: String): ArrayList<String>? fun getCharSequenceArrayList(key: String): ArrayList<CharSequence>? fun getBundle(key: String): Bundle? fun getBooleanArray(key: String): BooleanArray? fun getByteArray(key: String): ByteArray? fun getShortArray(key: String): ShortArray? fun getCharArray(key: String): CharArray? fun getIntArray(key: String): IntArray? fun getLongArray(key: String): LongArray? fun getFloatArray(key: String): FloatArray? fun getDoubleArray(key: String): DoubleArray? fun getStringArray(key: String): Array<String?>? fun getCharSequenceArray(key: String): Array<CharSequence?>? fun getSize(key: String): Size? fun getSizeF(key: String): SizeF? fun containsKey(key: String): Boolean fun remove(key: String) fun keySet(): Set<String> }
  18. // commonMain public expect class SavedState // androidMain public actual

    typealias SavedState = android.os.Bundle // nonAndroidMain public actual class SavedState(map: MutableMap<String, Any?>)
  19. // commonMain public expect class SavedState // androidMain public actual

    typealias SavedState = android.os.Bundle // nonAndroidMain public actual class SavedState(map: MutableMap<String, Any?>)
  20. // commonMain public expect class SavedState // androidMain public actual

    typealias SavedState = android.os.Bundle // nonAndroidMain public actual class SavedState(map: MutableMap<String, Any?>)
  21. public expect inline fun savedState(block: SavedStateWriter.() -> Unit): SavedState @JvmInline

    public expect value class SavedStateReader(source: SavedState) // {...} public inline fun <T> SavedState.read(block: SavedStateReader.() -> T): T // {...} @JvmInline public expect value class SavedStateWriter(source: SavedState) // {...} public inline fun <T> SavedState.write(block: SavedStateWriter.() -> T): T // {...}
  22. public expect inline fun savedState(block: SavedStateWriter.() -> Unit): SavedState @JvmInline

    public expect value class SavedStateReader(source: SavedState) // {...} public inline fun <T> SavedState.read(block: SavedStateReader.() -> T): T // {...} @JvmInline public expect value class SavedStateWriter(source: SavedState) // {...} public inline fun <T> SavedState.write(block: SavedStateWriter.() -> T): T // {...}
  23. public expect inline fun savedState(block: SavedStateWriter.() -> Unit): SavedState @JvmInline

    public expect value class SavedStateReader(source: SavedState) // {...} public inline fun <T> SavedState.read(block: SavedStateReader.() -> T): T // {...} @JvmInline public expect value class SavedStateWriter(source: SavedState) // {...} public inline fun <T> SavedState.write(block: SavedStateWriter.() -> T): T // {...}
  24. public expect inline fun savedState(block: SavedStateWriter.() -> Unit): SavedState @JvmInline

    public expect value class SavedStateReader(source: SavedState) // {...} public inline fun <T> SavedState.read(block: SavedStateReader.() -> T): T // {...} @JvmInline public expect value class SavedStateWriter(source: SavedState) // {...} public inline fun <T> SavedState.write(block: SavedStateWriter.() -> T): T // {...}
  25. public expect inline fun savedState(block: SavedStateWriter.() -> Unit): SavedState @JvmInline

    public expect value class SavedStateReader(source: SavedState) // {...} public inline fun <T> SavedState.read(block: SavedStateReader.() -> T): T // {...} @JvmInline public expect value class SavedStateWriter(source: SavedState) // {...} public inline fun <T> SavedState.write(block: SavedStateWriter.() -> T): T // {...}
  26. data class Person(val name: String, val age: Int) val person

    = Person(“John Doe”, 21) val state = savedState { this: SavedStateWriter -> putString("name", person.name) putInt("age", person.age) } val name = state.read { this: SavedStateReader -> getString("name") }
  27. data class Person(val name: String, val age: Int) val person

    = Person(“John Doe”, 21) val state = savedState { this: SavedStateWriter -> putString("name", person.name) putInt("age", person.age) } val name = state.read { this: SavedStateReader -> getString("name") }
  28. data class Person(val name: String, val age: Int) val person

    = Person(“John Doe”, 21) val state = savedState { this: SavedStateWriter -> putString("name", person.name) putInt("age", person.age) } val name = state.read { this: SavedStateReader -> getString("name") }
  29. @Serializable data class Person(val name: String, val age: Int) fun

    main() { val person = Person("John Doe", 21) val encoded: SavedState = encodeToSavedState(person) val decoded: Person = decodeFromSavedState(encoded) }
  30. @Serializable data class Person(val name: String, val age: Int) fun

    main() { val person = Person("John Doe", 21) val encoded: SavedState = encodeToSavedState(person) val decoded: Person = decodeFromSavedState(encoded) }
  31. @Serializable data class Person(val name: String, val age: Int) fun

    main() { val person = Person("John Doe", 21) val encoded: SavedState = encodeToSavedState(person) val decoded: Person = decodeFromSavedState(encoded) }
  32. @Serializable data class Person(val name: String, val age: Int) fun

    main() { val person = Person("John Doe", 21) val encoded: SavedState = encodeToSavedState(person) val decoded: Person = decodeFromSavedState(encoded) }
  33. @Serializable data class Person(val name: String, val age: Int) fun

    main() { val configuration = SavedStateConfiguration { serializersModule = SerializersModule {…} encodeDefaults = true classDiscriminatorMode = ClassDiscriminatorMode.POLYMORPHIC } val person = Person("John Doe", 21) val encoded: SavedState = encodeToSavedState(configuration, person) val decoded: Person = decodeFromSavedState(configuration encoded) }
  34. @Serializable data class Person(val name: String, val age: Int) class

    MyViewModel(handle: SavedStateHandle) : ViewModel() { val person by handle.saved { Person("John Doe", 21) } }
  35. @Serializable data class Person(val name: String, val age: Int) class

    MyComponent : SavedStateRegistryOwner { val person by saved { Person("John Doe", 21) } }
  36. @Serializable data class Person(val name: String, val age: Int) class

    MyActivity : ComponentActivity() { val person by saved { Person("John Doe", 21) } }
  37. @Serializable data class Person(val name: String, val age: Int) class

    MyFragment : Fragment() { val person by saved { Person("John Doe", 21) } }
  38. @Serializable data class Person(val name: String, val age: Int) val

    navController = rememberNavController() NavHost(navController, startDestination = Profile) { composable<Profile> { entry: NavBackStackEntry -> val selectedUser by remember { entry.saved { Person("John Doe", 21) } } Profile(navController) } }
  39. @Serializable data class Person(val name: String, val age: Int) val

    backStack = rememberNavBackStack() NavDisplay(backStack, entryProvider = entryProvider { entry<Profile> { val owner = LocalSavedStateRegistryOwner.current.savedStateRegistry val selectedUser by remember { owner.saved { Person("John Doe", 21) } } Profile(navController) } )
  40. @Serializable data class Person(val name: String, val age: Int) @Composable

    fun MyComposable() { val saveable = rememberSaveable(serializer = serializer<Person>()) { Person("John Doe", 21) } }
  41. @Serializable data class Person(val name: String, val age: Int) @Composable

    fun MyComposable() { val saveable = rememberSaveable(serializer = serializer<Person>()) { Person("John Doe", 21) } }
  42. @Serializable data class Person(val name: String, val age: Int) val

    backStack = rememberNavBackStack() NavDisplay(backStack, entryProvider = entryProvider { entry<Profile> { val owner = LocalSavedStateRegistryOwner.current val registry = owner.savedStateRegistry val person by remember { registry.saved { Person("John Doe", 21) } } Profile(person) } )
  43. @Serializable data class Person(val name: String, val age: Int) val

    backStack = rememberNavBackStack() NavDisplay(backStack, entryProvider = entryProvider { entry<Profile> { val person by rememberSaveable(serializer = serializer<Person>()) { Person("John Doe", 21) } Profile(person) } )
  44. • System Back ◦ Predictive Back • Keyboard Events What

    is Navigation Event? • System Back ◦ Predictive Back
  45. • System Back ◦ Predictive Back • Keyboard Events •

    Forward Navigation What is Navigation Event? • System Back ◦ Predictive Back • Keyboard Events
  46. OnBackPressedDispatcher • Added in the AndroidX Activity 1.0.0 release •

    Available from ComponentActivity • Added in the AndroidX Activity 1.0.0 release
  47. OnBackPressedDispatcher • Added in the AndroidX Activity 1.0.0 release •

    Available from ComponentActivity • Register OnBackPressedCallback to listen for events • Added in the AndroidX Activity 1.0.0 release • Available from ComponentActivity
  48. OnBackPressedDispatcher • Added in the AndroidX Activity 1.0.0 release •

    Available from ComponentActivity • Register OnBackPressedCallback to listen for events • Predictive/BackHandler in Compose • Added in the AndroidX Activity 1.0.0 release • Available from ComponentActivity • Register OnBackPressedCallback to listen for events
  49. class MyComponent(activity: ComponentActivity) { val dispatcher = activity.onBackPressedDispatcher init {

    dispatcher.addCallback(object : OnBackPressedCallback(true) { override fun handleOnBackStarted(backEvent: BackEventCompat) {...} override fun handleOnBackProgressed(backEvent: BackEventCompat) {...} override fun handleOnBackPressed() {...} override fun handleOnBackCancelled() {...} }) } } OnBackPressedDispatcher
  50. class MyComponent(activity: ComponentActivity) { val dispatcher = activity.onBackPressedDispatcher init {

    dispatcher.addCallback(object : OnBackPressedCallback(true) { override fun handleOnBackStarted(backEvent: BackEventCompat) {...} override fun handleOnBackProgressed(backEvent: BackEventCompat) {...} override fun handleOnBackPressed() {...} override fun handleOnBackCancelled() {...} }) } } OnBackPressedDispatcher
  51. class MyComponent(activity: ComponentActivity) { val dispatcher = activity.onBackPressedDispatcher init {

    dispatcher.addCallback(object : OnBackPressedCallback(true) { override fun handleOnBackStarted(backEvent: BackEventCompat) {...} override fun handleOnBackProgressed(backEvent: BackEventCompat) {...} override fun handleOnBackPressed() {...} override fun handleOnBackCancelled() {...} }) } } OnBackPressedDispatcher
  52. class MyComponent(activity: ComponentActivity) { val dispatcher = activity.onBackPressedDispatcher init {

    dispatcher.addCallback(object : OnBackPressedCallback(true) { override fun handleOnBackStarted(backEvent: BackEventCompat) {...} override fun handleOnBackProgressed(backEvent: BackEventCompat) {...} override fun handleOnBackPressed() {...} override fun handleOnBackCancelled() {...} }) } } OnBackPressedDispatcher
  53. class MyComponent(activity: ComponentActivity) { val dispatcher = activity.onBackPressedDispatcher init {

    dispatcher.addCallback(object : OnBackPressedCallback(true) { override fun handleOnBackStarted(backEvent: BackEventCompat) {...} override fun handleOnBackProgressed(backEvent: BackEventCompat) {...} override fun handleOnBackPressed() {...} override fun handleOnBackCancelled() {...} }) } } OnBackPressedDispatcher
  54. class MyComponent(activity: ComponentActivity) { val dispatcher = activity.onBackPressedDispatcher init {

    dispatcher.addCallback(object : OnBackPressedCallback(true) { override fun handleOnBackStarted(backEvent: BackEventCompat) {...} override fun handleOnBackProgressed(backEvent: BackEventCompat) {...} override fun handleOnBackPressed() {...} override fun handleOnBackCancelled() {...} }) } } OnBackPressedDispatcher
  55. class MyComponent(activity: ComponentActivity) { val dispatcher = activity.onBackPressedDispatcher init {

    dispatcher.addCallback(object : OnBackPressedCallback(true) { override fun handleOnBackStarted(backEvent: BackEventCompat) {...} override fun handleOnBackProgressed(backEvent: BackEventCompat) {...} override fun handleOnBackPressed() {...} override fun handleOnBackCancelled() {...} }) } } OnBackPressedDispatcher
  56. class MyComponent(activity: ComponentActivity) { val dispatcher = activity.onBackPressedDispatcher init {

    dispatcher.addCallback { /* handle system back here */ } } } OnBackPressedDispatcher
  57. class MyComponent(activity: ComponentActivity) { val dispatcher = activity.onBackPressedDispatcher init {

    dispatcher.addCallback { /* handle system back here */ } } } OnBackPressedDispatcher
  58. @Composable fun myComposable() { PredictiveBackHandler(true /* enabled condition */) {

    backEvent -> /* handleOnBackStarted */ try { backEvent.collect { /* handleOnBackProgressed */} /* handleOnBackPressed */ } finally { /* handleOnBackCancelled */ } } } PredictiveBackHandler
  59. @Composable fun myComposable() { PredictiveBackHandler(true /* enabled condition */) {

    backEvent -> /* handleOnBackStarted */ try { backEvent.collect { /* handleOnBackProgressed */} /* handleOnBackPressed */ } finally { /* handleOnBackCancelled */ } } } PredictiveBackHandler
  60. @Composable fun myComposable() { PredictiveBackHandler(true /* enabled condition */) {

    backEvent -> /* handleOnBackStarted */ try { backEvent.collect { /* handleOnBackProgressed */} /* handleOnBackPressed */ } finally { /* handleOnBackCancelled */ } } } PredictiveBackHandler
  61. @Composable fun myComposable() { PredictiveBackHandler(true /* enabled condition */) {

    backEvent -> /* handleOnBackStarted */ try { backEvent.collect { /* handleOnBackProgressed */} /* handleOnBackPressed */ } finally { /* handleOnBackCancelled */ } } } PredictiveBackHandler
  62. @Composable fun myComposable() { PredictiveBackHandler(true /* enabled condition */) {

    backEvent -> /* handleOnBackStarted */ try { backEvent.collect { /* handleOnBackProgressed */} /* handleOnBackPressed */ } finally { /* handleOnBackCancelled */ } } } PredictiveBackHandler
  63. @Composable fun myComposable() { PredictiveBackHandler(true /* enabled condition */) {

    backEvent -> /* handleOnBackStarted */ try { backEvent.collect { /* handleOnBackProgressed */} /* handleOnBackPressed */ } finally { /* handleOnBackCancelled */ } } } PredictiveBackHandler
  64. @Composable fun myComposable() { PredictiveBackHandler(true /* enabled condition */) {

    backEvent -> /* handleOnBackStarted */ try { backEvent.collect { /* handleOnBackProgressed */} /* handleOnBackPressed */ } finally { /* handleOnBackCancelled */ } } } PredictiveBackHandler
  65. @Composable fun myComposable() { PredictiveBackHandler(true /* enabled condition */) {

    backEvent -> /* handleOnBackStarted */ try { backEvent.collect { /* handleOnBackProgressed */} /* handleOnBackPressed */ } finally { /* handleOnBackCancelled */ } } } PredictiveBackHandler
  66. @Composable fun myComposable() { PredictiveBackHandler(true /* enabled condition */) {

    backEvent -> /* handleOnBackStarted */ try { backEvent.collect { /* handleOnBackProgressed */} /* handleOnBackPressed */ } finally { /* handleOnBackCancelled */ } } } PredictiveBackHandler
  67. Back Up Forward Home Android Phone ✅ ✅ ❓ ✅

    Android Tablet ✅ ✅ ❓ ✅ Android XR ✅ ✅ ❓ ✅ Web (Browser) ✅ ✅ ✅ ❓ iOS ✅ ❓ ❓ ✅ Desktop (Multiplatform) ✅ ❓ ❓ ❓ System Events on other Platforms
  68. public expect class NavigationInputHandler { private val dispatcher: NavigationEventDispatcher internal

    fun updateActiveState() } public expect class NavigationEvent { public constructor( touchX: Float, touchY: Float, progress: Float, swipeEdge: @SwipeEdge Int, frameTimeMillis: Long = 0 ) } Expect Classes
  69. public expect class NavigationInputHandler { private val dispatcher: NavigationEventDispatcher internal

    fun updateActiveState() } public expect class NavigationEvent { public constructor( touchX: Float, touchY: Float, progress: Float, swipeEdge: @SwipeEdge Int, frameTimeMillis: Long = 0 ) } Expect Classes
  70. public expect class NavigationInputHandler { private val dispatcher: NavigationEventDispatcher internal

    fun updateActiveState() } public expect class NavigationEvent { public constructor( touchX: Float, touchY: Float, progress: Float, swipeEdge: @SwipeEdge Int, frameTimeMillis: Long = 0 ) } Expect Classes
  71. public expect class NavigationInputHandler { private val dispatcher: NavigationEventDispatcher internal

    fun updateActiveState() } public expect class NavigationEvent { public constructor( touchX: Float, touchY: Float, progress: Float, swipeEdge: @SwipeEdge Int, frameTimeMillis: Long = 0 ) } Expect Classes
  72. public expect class NavigationInputHandler { private val dispatcher: NavigationEventDispatcher internal

    fun updateActiveState() } public expect class NavigationEvent { public constructor( touchX: Float, touchY: Float, progress: Float, swipeEdge: @SwipeEdge Int, frameTimeMillis: Long = 0 ) } Expect Classes
  73. public expect class NavigationInputHandler { private val dispatcher: NavigationEventDispatcher internal

    fun updateActiveState() } public expect class NavigationEvent { public constructor( touchX: Float, touchY: Float, progress: Float, swipeEdge: @SwipeEdge Int, frameTimeMillis: Long = 0 ) } Expect Classes
  74. public expect class NavigationInputHandler { private val dispatcher: NavigationEventDispatcher internal

    fun updateActiveState() } public expect class NavigationEvent { public constructor( touchX: Float, touchY: Float, progress: Float, swipeEdge: @SwipeEdge Int, frameTimeMillis: Long = 0 ) } Expect Classes
  75. public expect class NavigationInputHandler { private val dispatcher: NavigationEventDispatcher internal

    fun updateActiveState() } public expect class NavigationEvent { public constructor( touchX: Float, touchY: Float, progress: Float, swipeEdge: @SwipeEdge Int, frameTimeMillis: Long = 0 ) } Expect Classes
  76. public expect class NavigationInputHandler { private val dispatcher: NavigationEventDispatcher internal

    fun updateActiveState() } public expect class NavigationEvent { public constructor( touchX: Float, touchY: Float, progress: Float, swipeEdge: @SwipeEdge Int, frameTimeMillis: Long = 0 ) } Expect Classes
  77. • Re-write OnBackPressedDispatcher on top of NavigationEvent • Allow integration

    with the new Navigation3 library Verifying the new approach • Re-write OnBackPressedDispatcher on top of NavigationEvent
  78. public expect class NavigationEvent { public constructor( touchX: Float, touchY:

    Float, progress: Float, swipeEdge: @SwipeEdge Int, frameTimeMillis: Long = 0 ) } Expect NavigationEvent
  79. public actual class NavigationEvent actual constructor(...) { public constructor(backEvent: BackEvent)

    : this( touchX = backEvent.touchX, touchY = backEvent.touchY, progress = backEvent.progress, swipeEdge = backEvent.swipeEdge, frameTimeMillis = backEvent.frameTimeMillis, ) } Actual NavigationEvent
  80. public actual class NavigationEvent actual constructor(...) { public constructor(backEvent: BackEvent)

    : this( touchX = backEvent.touchX, touchY = backEvent.touchY, progress = backEvent.progress, swipeEdge = backEvent.swipeEdge, frameTimeMillis = backEvent.frameTimeMillis, ) } Actual NavigationEvent
  81. public actual class NavigationInputHandler(val dispatcher:...) { private var invoker: OnBackInvokedDispatcher?

    = null private var onBackInvokedCallback: OnBackInvokedCallback? = null private var registered = false public fun setOnBackInvokedDispatcher(invoker: OnBackInvokedDispatcher) { this.invoker = invoker updateActiveState() } init { ... } internal actual fun updateActiveState() { ... } } Actual NavigationInputHandler
  82. public actual class NavigationInputHandler(val dispatcher:...) { private var invoker: OnBackInvokedDispatcher?

    = null private var onBackInvokedCallback: OnBackInvokedCallback? = null private var registered = false public fun setOnBackInvokedDispatcher(invoker: OnBackInvokedDispatcher) { this.invoker = invoker updateActiveState() } init { ... } internal actual fun updateActiveState() { ... } } Actual NavigationInputHandler
  83. public actual class NavigationInputHandler(val dispatcher:...) { private var invoker: OnBackInvokedDispatcher?

    = null private var onBackInvokedCallback: OnBackInvokedCallback? = null private var registered = false public fun setOnBackInvokedDispatcher(invoker: OnBackInvokedDispatcher) { this.invoker = invoker updateActiveState() } init { ... } internal actual fun updateActiveState() { ... } } Actual NavigationInputHandler
  84. public actual class NavigationInputHandler(val dispatcher:...) { private var invoker: OnBackInvokedDispatcher?

    = null private var onBackInvokedCallback: OnBackInvokedCallback? = null private var registered = false public fun setOnBackInvokedDispatcher(invoker: OnBackInvokedDispatcher) { this.invoker = invoker updateActiveState() } init { ... } internal actual fun updateActiveState() { ... } } Actual NavigationInputHandler
  85. public actual class NavigationInputHandler(val dispatcher:...) { private var invoker: OnBackInvokedDispatcher?

    = null private var onBackInvokedCallback: OnBackInvokedCallback? = null private var registered = false public fun setOnBackInvokedDispatcher(invoker: OnBackInvokedDispatcher) { this.invoker = invoker updateActiveState() } init { ... } internal actual fun updateActiveState() { ... } } Actual NavigationInputHandler
  86. public actual class NavigationInputHandler(val dispatcher:...) { private var invoker: OnBackInvokedDispatcher?

    = null private var onBackInvokedCallback: OnBackInvokedCallback? = null private var registered = false public fun setOnBackInvokedDispatcher(invoker: OnBackInvokedDispatcher) { this.invoker = invoker updateActiveState() } init { ... } internal actual fun updateActiveState() { ... } } Actual NavigationInputHandler
  87. public actual class NavigationInputHandler(val dispatcher:...) { private var invoker: OnBackInvokedDispatcher?

    = null private var onBackInvokedCallback: OnBackInvokedCallback? = null private var registered = false public fun setOnBackInvokedDispatcher(invoker: OnBackInvokedDispatcher) { this.invoker = invoker updateActiveState() } init { ... } internal actual fun updateActiveState() { ... } } Actual NavigationInputHandler
  88. public actual class NavigationInputHandler(val dispatcher:...) { private var invoker: OnBackInvokedDispatcher?

    = null private var onBackInvokedCallback: OnBackInvokedCallback? = null private var registered = false public fun setOnBackInvokedDispatcher(invoker: OnBackInvokedDispatcher) { this.invoker = invoker updateActiveState() } init { ... } internal actual fun updateActiveState() { ... } } Actual NavigationInputHandler
  89. internal actual fun updateActiveState() { val shouldBeRegistered = dispatcher.hasEnabledCallbacks()\ if

    (shouldBeRegistered && !registered) { registerOnBackInvokedCallback(...) } else if (!shouldBeRegistered && registered) { unregisterOnBackInvokedCallback(...) } } Actual NavigationInputHandler
  90. internal actual fun updateActiveState() { val shouldBeRegistered = dispatcher.hasEnabledCallbacks() if

    (shouldBeRegistered && !registered) { registerOnBackInvokedCallback(...) } else if (!shouldBeRegistered && registered) { unregisterOnBackInvokedCallback(...) } } Actual NavigationInputHandler
  91. internal actual fun updateActiveState() { val shouldBeRegistered = dispatcher.hasEnabledCallbacks() if

    (shouldBeRegistered && !registered) { registerOnBackInvokedCallback(...) } else if (!shouldBeRegistered && registered) { unregisterOnBackInvokedCallback(...) } } Actual NavigationInputHandler
  92. public actual class NavigationInputHandler(val dispatcher:...) { private var invoker: OnBackInvokedDispatcher?

    = null private var onBackInvokedCallback: OnBackInvokedCallback? = null private var registered = false public fun setOnBackInvokedDispatcher(invoker: OnBackInvokedDispatcher) { this.invoker = invoker updateActiveState() } init { ... } internal actual fun updateActiveState() { ... } } Actual NavigationInputHandler
  93. public class NavigationInputHandler(val dispatcher:...) { private var onBackInvokedCallback: OnBackInvokedCallback? =

    null init { onBackInvokedCallback: OnBackInvokedCallback = OnBackAnimationCallback { override fun onBackStarted(backEvent: BackEvent) { dispatcher.dispatchOnStarted(NavigationEvent(backEvent)) } override fun onBackProgressed(backEvent: BackEvent) { dispatcher.dispatchOnProgressed(NavigationEvent(backEvent)) } override fun onBackInvoked() { dispatcher.dispatchOnCompleted() } override fun onBackCancelled() { dispatcher.dispatchOnCancelled() } } } } Actual NavigationInputHandler
  94. public class NavigationInputHandler(val dispatcher:...) { private var onBackInvokedCallback: OnBackInvokedCallback? =

    null init { onBackInvokedCallback: OnBackInvokedCallback = OnBackAnimationCallback { override fun onBackStarted(backEvent: BackEvent) { dispatcher.dispatchOnStarted(NavigationEvent(backEvent)) } override fun onBackProgressed(backEvent: BackEvent) { dispatcher.dispatchOnProgressed(NavigationEvent(backEvent)) } override fun onBackInvoked() { dispatcher.dispatchOnCompleted() } override fun onBackCancelled() { dispatcher.dispatchOnCancelled() } } } } Actual NavigationInputHandler
  95. public class NavigationInputHandler(val dispatcher:...) { private var onBackInvokedCallback: OnBackInvokedCallback? =

    null init { onBackInvokedCallback: OnBackInvokedCallback = OnBackAnimationCallback { override fun onBackStarted(backEvent: BackEvent) { dispatcher.dispatchOnStarted(NavigationEvent(backEvent)) } override fun onBackProgressed(backEvent: BackEvent) { dispatcher.dispatchOnProgressed(NavigationEvent(backEvent)) } override fun onBackInvoked() { dispatcher.dispatchOnCompleted() } override fun onBackCancelled() { dispatcher.dispatchOnCancelled() } } } } Actual NavigationInputHandler
  96. abstract class OnBackPressedCallback(enabled: Boolean) { internal val eventCallback = object

    : NavigationEventCallback(isEnabled = enabled) { override fun onEventStarted(event: NavigationEvent) { handleOnBackStarted(BackEventCompat(event)) } override fun onEventProgressed(event: NavigationEvent) { handleOnBackProgressed(BackEventCompat(event)) } ... } } OnBackPressedCallback
  97. abstract class OnBackPressedCallback(enabled: Boolean) { internal val eventCallback = object

    : NavigationEventCallback(isEnabled = enabled) { override fun onEventStarted(event: NavigationEvent) { handleOnBackStarted(BackEventCompat(event)) } override fun onEventProgressed(event: NavigationEvent) { handleOnBackProgressed(BackEventCompat(event)) } ... } } OnBackPressedCallback
  98. class OnBackPressedDispatcher(...) { fun addCallback(onBackPressedCallback: OnBackPressedCallback) { eventDispatcher.addCallback(onBackPressedCallback.eventCallback) } private

    fun onBackStarted(backEvent: BackEventCompat) { eventDispatcher.dispatchOnStarted(backEvent.toNavigationEvent()) } private fun onBackProgressed(backEvent: BackEventCompat) { eventDispatcher.dispatchOnProgressed(backEvent.toNavigationEvent()) } ... } OnBackPressedDispatcher
  99. class OnBackPressedDispatcher(...) { fun addCallback(onBackPressedCallback: OnBackPressedCallback) { eventDispatcher.addCallback(onBackPressedCallback.eventCallback) } private

    fun onBackStarted(backEvent: BackEventCompat) { eventDispatcher.dispatchOnStarted(backEvent.toNavigationEvent()) } private fun onBackProgressed(backEvent: BackEventCompat) { eventDispatcher.dispatchOnProgressed(backEvent.toNavigationEvent()) } ... } OnBackPressedDispatcher
  100. class OnBackPressedDispatcher(...) { fun addCallback(onBackPressedCallback: OnBackPressedCallback) { eventDispatcher.addCallback(onBackPressedCallback.eventCallback) } private

    fun onBackStarted(backEvent: BackEventCompat) { eventDispatcher.dispatchOnStarted(backEvent.toNavigationEvent()) } private fun onBackProgressed(backEvent: BackEventCompat) { eventDispatcher.dispatchOnProgressed(backEvent.toNavigationEvent()) } ... } OnBackPressedDispatcher
  101. class OnBackPressedDispatcher(...) { fun addCallback(onBackPressedCallback: OnBackPressedCallback) { eventDispatcher.addCallback(onBackPressedCallback.eventCallback) } private

    fun onBackStarted(backEvent: BackEventCompat) { eventDispatcher.dispatchOnStarted(backEvent.toNavigationEvent()) } private fun onBackProgressed(backEvent: BackEventCompat) { eventDispatcher.dispatchOnProgressed(backEvent.toNavigationEvent()) } ... } OnBackPressedDispatcher
  102. • System Back ◦ Predictive Back • Keyboard Events What’s

    next for Navigation Event? • System Back ◦ Predictive Back
  103. • System Back ◦ Predictive Back • Keyboard Events •

    Forward Navigation What’s next for Navigation Event? • System Back ◦ Predictive Back • Keyboard Events
  104. ✅ Keeps existing behavior and code that’s already tested. ✅

    Can move to KMP step-by-step, reducing risk. ✅ Customers don't need to change their code. ❌ Harder to clean up old or platform-specific design. ❌ May involve complex refactoring and workarounds. Migrate a Library
  105. ✅ Keeps existing behavior and code that’s already tested. ✅

    Can move to KMP step-by-step, reducing risk. ✅ Customers don't need to change their code. ❌ Harder to clean up old or platform-specific design. ❌ May involve complex refactoring and workarounds. Migrate a Library ✅ Clean design from the start, no legacy "platform-specific" code. ✅ Easier to apply modern best practices. ❌ Slower to build feature parity if the existing library is large. ❌ May miss edge cases already handled in the existing code. ❌ Customers will need to migrate. Rewrite a Library
  106. ✅ Keeps existing behavior and code that’s already tested. ✅

    Can move to KMP step-by-step, reducing risk. ✅ Customers don't need to change their code. ❌ Harder to clean up old or platform-specific design. ❌ May involve complex refactoring and workarounds. Migrate a Library ✅ Clean design from the start, no legacy "platform-specific" code. ✅ Easier to apply modern best practices. ❌ Slower to build feature parity if the existing library is large. ❌ May miss edge cases already handled in the existing code. ❌ Customers will need to migrate. Rewrite a Library
  107. • SavedState 1.3 (KMP + Serialization) • NavigationEvent Alpha01 •

    Navigation3 Alpha01 New Releases! goo.gle/nav3
  108. if (action == Action.Back) { val event = NavigationEvent(currentX, currentY,

    calculateProgress(startX, currentX, windowSize.width.toFloat()), Edge.Left) backChannel.onProgressed(event) } override fun dispatchEvent(e: NavigationEventWrapper, callback: NavigationEventCallback) { when (e.action) { Action.Back -> { when (e.type) { EventType.Started -> { backNavigation?.let { it.callback.onBackCancelled() } backNavigation = Navigation(callback.deepCopy()).also { it.callback.onBackStarted(e.externalEvent!!) } } EventType.Progressed -> backNavigation?.callback?.onBackProgressed(e.externalEvent!!) EventType.Cancelled -> { backNavigation?.callback?.onBackCancelled() backNavigation = null } EventType.Completed -> { backNavigation?.callback?.onBackCompleted() backNavigation = null } } } } }