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

The Workflow Pattern, Composed (droidconSF 2022)

The Workflow Pattern, Composed (droidconSF 2022)

Avatar for Zach Klippenstein

Zach Klippenstein

June 04, 2022
Tweet

More Decks by Zach Klippenstein

Other Decks in Programming

Transcript

  1. @zachklipp Who’s this guy? • Android for 7+ years •

    Square: POS, mobile infrastructure, design systems • Google: Compose
  2. @zachklipp Overview Detail Work fl ow props Overview Work fl

    ow Detail Work fl ow props renderings
  3. @zachklipp Advantages • Injectability • Reusability • Testability • Modularizability

    FooModule MyWorkflow(val fooService, val barService) MyOtherWorkflow() BarModule OtherWorkflow(val bazService)
  4. @zachklipp Advantages • Injectability • Reusability • Testability • Modularizability

    • View customizability class MyWorkflow(val fooService, val barService) { / / . . . }
  5. @zachklipp 2,837.95USD Tech Company Class A View data class StockRendering(

    val price: String, val currency: String, val name: String ) Rendering ScreenViewFactory
  6. @zachklipp interface ViewFactory<in RenderingT : Any> { fun buildView( initialRendering:

    RenderingT, initialViewEnvironment: ViewEnvironment ) : View }
  7. @zachklipp interface ViewFactory<in RenderingT : Any> { fun buildView( initialRendering:

    RenderingT, initialViewEnvironment: ViewEnvironment, contextForNewView: Context ) : View }
  8. @zachklipp interface ViewFactory<in RenderingT : Any> { fun buildView( initialRendering:

    RenderingT, initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? = null ) : View }
  9. @zachklipp interface ViewFactory<in RenderingT : Any> { fun buildView( initialRendering:

    RenderingT, initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? = null ) : View } interface AndroidScreen<S : AndroidScreen<S > > { public val viewFactory: ViewFactory<S> }
  10. @zachklipp abstract class ComposeViewFactory<RenderingT : Any> : ViewFactory<RenderingT> { f

    i nal override fun buildView( initialRendering: RenderingT, initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? ) : View }
  11. @zachklipp abstract class ComposeViewFactory<RenderingT : Any> : ViewFactory<RenderingT> { f

    i nal override fun buildView( initialRendering: RenderingT, initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? ) : View = ComposeView(contextForNewView) }
  12. @zachklipp abstract class ComposeViewFactory<RenderingT : Any> : ViewFactory<RenderingT> { f

    i nal override fun buildView( initialRendering: RenderingT, initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? ) : View = ComposeView(contextForNewView).also { composeView - > composeView.setContent {} } }
  13. @zachklipp abstract class ComposeViewFactory<RenderingT : Any> : ViewFactory<RenderingT> { f

    i nal override fun buildView( initialRendering: RenderingT, initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? ) : View = ComposeView(contextForNewView).also { composeView - > composeView.setContent {} } @Composable abstract fun Content( rendering: RenderingT, viewEnvironment: ViewEnvironment ) }
  14. @zachklipp abstract class ComposeViewFactory<RenderingT : Any> : ViewFactory<RenderingT> { f

    i nal override fun buildView( initialRendering: RenderingT, initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? ) : View = ComposeView(contextForNewView).also { composeView - > composeView.setContent { Content(initialRendering, initialViewEnvironment) } } @Composable abstract fun Content( rendering: RenderingT, viewEnvironment: ViewEnvironment ) }
  15. @zachklipp abstract class ComposeViewFactory<RenderingT : Any> : ViewFactory<RenderingT> { f

    i nal override fun buildView( initialRendering: RenderingT, initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? ) : View = ComposeView(contextForNewView).also { composeView - > composeView.bindShowRendering( initialRendering, initialViewEnvironment ) { rendering, environment - > composeView.setContent { Content(initialRendering, initialViewEnvironment) } } } @Composable abstract fun Content( rendering: RenderingT, viewEnvironment: ViewEnvironment ) }
  16. @zachklipp abstract class ComposeViewFactory<RenderingT : Any> : ViewFactory<RenderingT> { f

    i nal override fun buildView( initialRendering: RenderingT, initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? ) : View = ComposeView(contextForNewView).also { composeView - > composeView.bindShowRendering( initialRendering, initialViewEnvironment ) { rendering, environment - > composeView.setContent { Content(initialRendering, initialViewEnvironment) } } } @Composable abstract fun Content( rendering: RenderingT, viewEnvironment: ViewEnvironment ) }
  17. @zachklipp abstract class ComposeViewFactory<RenderingT : Any> : ViewFactory<RenderingT> { f

    i nal override fun buildView( initialRendering: RenderingT, initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? ) : View = ComposeView(contextForNewView).also { composeView - > composeView.bindShowRendering( initialRendering, initialViewEnvironment ) { rendering, environment - > composeView.setContent { Content(rendering, environment) } } } @Composable abstract fun Content( rendering: RenderingT, viewEnvironment: ViewEnvironment ) }
  18. @zachklipp abstract class ComposeViewFactory<RenderingT : Any> : ViewFactory<RenderingT> { f

    i nal override fun buildView( initialRendering: RenderingT, initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? ) : View = ComposeView(contextForNewView).also { composeView - > composeView.bindShowRendering( initialRendering, initialViewEnvironment ) { rendering, environment - > composeView.setContent { Content(rendering, environment) } } } @Composable abstract fun Content( rendering: RenderingT, viewEnvironment: ViewEnvironment ) }
  19. @zachklipp interface ComposeScreen : AndroidScreen<ComposeScreen> { override val viewFactory: ViewFactory<ComposeScreen>

    get() = object : ComposeViewFactory<ComposeScreen>() {} @Composable fun Content(viewEnvironment: ViewEnvironment) }
  20. @zachklipp interface ComposeScreen : AndroidScreen<ComposeScreen> { override val viewFactory: ViewFactory<ComposeScreen>

    get() = object : ComposeViewFactory<ComposeScreen>() { @Composable override fun Content( rendering: ComposeScreen, viewEnvironment: ViewEnvironment ) {} } @Composable fun Content(viewEnvironment: ViewEnvironment) }
  21. @zachklipp interface ComposeScreen : AndroidScreen<ComposeScreen> { override val viewFactory: ViewFactory<ComposeScreen>

    get() = object : ComposeViewFactory<ComposeScreen>() { @Composable override fun Content( rendering: ComposeScreen, viewEnvironment: ViewEnvironment ) { rendering.Content(viewEnvironment) } } @Composable fun Content(viewEnvironment: ViewEnvironment) }
  22. @zachklipp data class NameRendering( val name: String ) : ComposeScreen

    { @Composable override fun Content(viewEnvironment: ViewEnvironment) { } }
  23. @zachklipp data class NameRendering( val name: String ) : ComposeScreen

    { @Composable override fun Content(viewEnvironment: ViewEnvironment) { Text(name) } }
  24. @zachklipp data class PersonRendering( val name: String, val details: Any

    ) : ComposeScreen { @Composable override fun Content(viewEnvironment: ViewEnvironment) { Text(name) } }
  25. @zachklipp data class PersonRendering( val name: String, val details: Any

    ) : ComposeScreen { @Composable override fun Content(viewEnvironment: ViewEnvironment) { Column { Text(name) ???(details) } } }
  26. @zachklipp data class PersonRendering( val name: String, val details: Any

    ) : ComposeScreen { @Composable override fun Content(viewEnvironment: ViewEnvironment) { Column { Text(name) WorkflowRendering( details, viewEnvironment, Modif i er.weight(1f) ) } } }
  27. @zachklipp data class PersonRendering( val name: String, val details: Any

    ) : ComposeScreen { @Composable override fun Content(viewEnvironment: ViewEnvironment) { Column { Text(name) WorkflowRendering( details, viewEnvironment, Modif i er.weight(1f) ) } } }
  28. @zachklipp @Composable fun WorkflowRendering( rendering: Any, viewEnvironment: ViewEnvironment, modif i

    er: Modif i er = Modif i er ) { val viewFactory: ComposeViewFactory<Any> = ??? }
  29. @zachklipp @Composable fun WorkflowRendering( rendering: Any, viewEnvironment: ViewEnvironment, modif i

    er: Modif i er = Modif i er ) { val viewFactory: ComposeViewFactory<Any> = remember { viewEnvironment[ViewRegistry] .getFactoryForRendering(rendering) } }
  30. @zachklipp @Composable fun WorkflowRendering( rendering: Any, viewEnvironment: ViewEnvironment, modif i

    er: Modif i er = Modif i er ) { val viewFactory: ComposeViewFactory<Any> = remember { viewEnvironment[ViewRegistry] .getFactoryForRendering(rendering) .asComposeViewFactory() } }
  31. @zachklipp @Composable fun WorkflowRendering( rendering: Any, viewEnvironment: ViewEnvironment, modif i

    er: Modif i er = Modif i er ) { val viewFactory: ComposeViewFactory<Any> = remember { viewEnvironment[ViewRegistry] .getFactoryForRendering(rendering) .asComposeViewFactory() } viewFactory.Content(rendering, viewEnvironment) }
  32. @zachklipp @Composable fun WorkflowRendering( rendering: Any, viewEnvironment: ViewEnvironment, modif i

    er: Modif i er = Modif i er ) { val viewFactory: ComposeViewFactory<Any> = remember { viewEnvironment[ViewRegistry] .getFactoryForRendering(rendering) .asComposeViewFactory() } Box(modif i er, propagateMinConstraints = true) { viewFactory.Content(rendering, viewEnvironment) } }
  33. @zachklipp @Composable fun WorkflowRendering( rendering: Any, viewEnvironment: ViewEnvironment, modif i

    er: Modif i er = Modif i er ) { val viewFactory: ComposeViewFactory<Any> = remember { viewEnvironment[ViewRegistry] .getFactoryForRendering(rendering) .asComposeViewFactory() } Box(modif i er, propagateMinConstraints = true) { viewFactory.Content(rendering, viewEnvironment) } }
  34. @zachklipp @Composable fun WorkflowRendering( rendering: Any, viewEnvironment: ViewEnvironment, modif i

    er: Modif i er = Modif i er ) { val viewFactory: ComposeViewFactory<Any> = remember { viewEnvironment[ViewRegistry] .getFactoryForRendering(rendering) .asComposeViewFactory() } val lifecycleOwner = rememberChildLifecycleOwner() CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) { Box(modif i er, propagateMinConstraints = true) { viewFactory.Content(rendering, viewEnvironment) } } }
  35. @zachklipp @Composable fun WorkflowRendering( rendering: Any, viewEnvironment: ViewEnvironment, modif i

    er: Modif i er = Modif i er ) { val viewFactory: ComposeViewFactory<Any> = remember { viewEnvironment[ViewRegistry] .getFactoryForRendering(rendering) .asComposeViewFactory() } val lifecycleOwner = rememberChildLifecycleOwner() CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) { Box(modif i er, propagateMinConstraints = true) { viewFactory.Content(rendering, viewEnvironment) } } }
  36. @zachklipp @Composable fun WorkflowRendering( rendering: Any, viewEnvironment: ViewEnvironment, modif i

    er: Modif i er = Modif i er ) { val renderingCompatibilityKey = rendering : : class val viewFactory: ComposeViewFactory<Any> = remember { viewEnvironment[ViewRegistry] .getFactoryForRendering(rendering) .asComposeViewFactory() } val lifecycleOwner = rememberChildLifecycleOwner() CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) { Box(modif i er, propagateMinConstraints = true) { viewFactory.Content(rendering, viewEnvironment) } } }
  37. @zachklipp @Composable fun WorkflowRendering( rendering: Any, viewEnvironment: ViewEnvironment, modif i

    er: Modif i er = Modif i er ) { val renderingCompatibilityKey = rendering : : class val viewFactory: ComposeViewFactory<Any> = remember(renderingCompatibilityKey) { viewEnvironment[ViewRegistry] .getFactoryForRendering(rendering) .asComposeViewFactory() } val lifecycleOwner = rememberChildLifecycleOwner() CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) { Box(modif i er, propagateMinConstraints = true) { viewFactory.Content(rendering, viewEnvironment) } } }
  38. @zachklipp @Composable fun WorkflowRendering( rendering: Any, viewEnvironment: ViewEnvironment, modif i

    er: Modif i er = Modif i er ) { val renderingCompatibilityKey = rendering : : class key(renderingCompatibilityKey) { val viewFactory: ComposeViewFactory<Any> = remember { viewEnvironment[ViewRegistry] .getFactoryForRendering(rendering) .asComposeViewFactory() } val lifecycleOwner = rememberChildLifecycleOwner() CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) { Box(modif i er, propagateMinConstraints = true) { viewFactory.Content(rendering, viewEnvironment) } } } }
  39. @zachklipp @Composable fun WorkflowRendering( rendering: Any, viewEnvironment: ViewEnvironment, modif i

    er: Modif i er = Modif i er ) { val renderingCompatibilityKey = rendering : : class key(renderingCompatibilityKey) { val viewFactory: ComposeViewFactory<Any> = remember { viewEnvironment[ViewRegistry] .getFactoryForRendering(rendering) .asComposeViewFactory() } val lifecycleOwner = rememberChildLifecycleOwner() CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) { Box(modif i er, propagateMinConstraints = true) { viewFactory.Content(rendering, viewEnvironment) } } } }
  40. @zachklipp /** * Renders [rendering] into the composition using this

    [ViewEnvironment]'s [ViewRegistry] to * generate the view. * * This function fulf i lls a similar role as [WorkflowViewStub], but is much more convenient to use * from Composable functions. Note, however, that just like [WorkflowViewStub], it doesn't matter * whether the factory registered for the rendering is using classic Android views or Compose. * * # # Example * * ` ` ` * data class FramedRendering<R : Any>( * val borderColor: Color, * val child: R * ) : ComposeRendering { * * @Composable override fun Content(viewEnvironment: ViewEnvironment) { * Surface(border = Border(borderColor, 8.dp)) { * WorkflowRendering(child, viewEnvironment) * } * } * } * ` ` ` * * @param rendering The workflow rendering to display. May be of any type for which a [ViewFactory] * has been registered in [viewEnvironment]'s [ViewRegistry]. * @param modif i er A [Modif i er] that will be applied to composable used to show [rendering]. * * @throws I l l egalArgumentException if no factory can be found for [rendering]'s type. * / @WorkflowUiExperimentalApi @Composable public fun WorkflowRendering( rendering: Any, viewEnvironment: ViewEnvironment, modif i er: Modif i er = Modif i er ) { / / This will fetch a new view factory any time the new rendering is incompatible with the previous / / one, as determined by Compatible. This corresponds to WorkflowViewStub's canShowRendering / / check. val renderingCompatibilityKey = Compatible.keyFor(rendering) / / By surrounding the below code with this key function, any time the new rendering is not / / compatible with the previous rendering we'll tear down the previous subtree of the composition, / / including its lifecycle, which destroys the lifecycle and any remembered state. If the view / / factory created an Android view, this will also remove the old one from the view hierarchy / / before replacing it with the new one. key(renderingCompatibilityKey) { val viewFactory = remember { / / The view registry may return a new factory instance for a rendering every time we ask it, for / / example if an AndroidViewRendering doesn't share its factory between rendering instances. We / / intentionally don't ask it for a new instance every time to match the behavior of / / WorkflowViewStub and other containers, which only ask for a new factory when the rendering is / / incompatible. viewEnvironment[ViewRegistry] / / Can't use ViewRegistry.buildView here since we need the factory to convert it to a / / compose one. .getFactoryForRendering(rendering) .asComposeViewFactory() } / / Just like WorkflowViewStub, we need to manage a Lifecycle for the child view. We just provide / / a local here – ViewFactoryAndroidView will handle setting the appropriate view tree owners / / on the child view when necessary. Because this function is surrounded by a key() call, when / / the rendering is incompatible, the lifecycle for the old view will be destroyed. val lifecycleOwner = rememberChildLifecycleOwner() CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) { / / We need to propagate min constraints because one of the likely uses for the modif i er passed / / into this function is to directly control the layout of the child view – which means / / minimum constraints are likely to be signif i cant. Box(modif i er, propagateMinConstraints = true) { viewFactory.Content(rendering, viewEnvironment) } } } }
  41. @zachklipp @Composable fun WorkflowRendering( rendering: Any, viewEnvironment: ViewEnvironment, modif i

    er: Modif i er = Modif i er ) { val viewFactory: ComposeViewFactory<Any> = remember { viewEnvironment[ViewRegistry] .getFactoryForRendering(rendering) .asComposeViewFactory() } val lifecycleOwner = rememberChildLifecycleOwner() CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) { Box(modif i er, propagateMinConstraints = true) { viewFactory.Content(rendering, viewEnvironment) } } }
  42. @zachklipp val color = composeViewFactory<Color> { rendering, _ - >

    Text(rendering.toString()) } val red = composeViewFactory<Unit> { _, viewEnvironment - > Row { Text("Red: ") color.Content(Color.Red, viewEnvironment) } }
  43. @zachklipp val color = composeViewFactory<Color> { rendering, _ - >

    Text(rendering.toString()) } val red = composeViewFactory<Unit> { _, viewEnvironment - > Row { Text("Red: ") key(Color.Red : : class) { val lifecycleOwner = rememberChildLifecycleOwner() CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) { Box(propagateMinConstraints = true) { color.Content(Color.Red, viewEnvironment) } } } } }
  44. @zachklipp fun <R : Any> ViewFactory<R>.asComposeViewFactory() : ComposeViewFactory<R> = (this

    as? ComposeViewFactory) ?: object : ComposeViewFactory<R>() {}
  45. @zachklipp fun <R : Any> ViewFactory<R>.asComposeViewFactory() : ComposeViewFactory<R> = (this

    as? ComposeViewFactory) ?: object : ComposeViewFactory<R>() { private val originalFactory = this@asComposeViewFactory @Composable override fun Content( rendering: R, viewEnvironment: ViewEnvironment ) {} }
  46. @zachklipp fun <R : Any> ViewFactory<R>.asComposeViewFactory() : ComposeViewFactory<R> = (this

    as? ComposeViewFactory) ?: object : ComposeViewFactory<R>() { private val originalFactory = this@asComposeViewFactory @Composable override fun Content( rendering: R, viewEnvironment: ViewEnvironment ) { AndroidView() } }
  47. @zachklipp fun <R : Any> ViewFactory<R>.asComposeViewFactory() : ComposeViewFactory<R> = (this

    as? ComposeViewFactory) ?: object : ComposeViewFactory<R>() { private val originalFactory = this@asComposeViewFactory @Composable override fun Content( rendering: R, viewEnvironment: ViewEnvironment ) { AndroidView( factory = { context - > }, update = { view - > } ) } }
  48. @zachklipp fun <R : Any> ViewFactory<R>.asComposeViewFactory() : ComposeViewFactory<R> = (this

    as? ComposeViewFactory) ?: object : ComposeViewFactory<R>() { private val originalFactory = this@asComposeViewFactory @Composable override fun Content( rendering: R, viewEnvironment: ViewEnvironment ) { AndroidView( factory = { context - > originalFactory.buildView(rendering, viewEnvironment, context) }, update = { view - > } ) } }
  49. @zachklipp fun <R : Any> ViewFactory<R>.asComposeViewFactory() i nal override fun

    buildView( initialRendering: RenderingT, initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? ) : View = ComposeView(contextForNewView).also { composeView - > composeView.bindShowRendering( initialRendering, initialViewEnvironment ) { rendering, environment - > composeView.setContent { Content(rendering, environment) } } }
  50. @zachklipp fun <R : Any> ViewFactory<R>.asComposeViewFactory() : ComposeViewFactory<R> = (this

    as? ComposeViewFactory) ?: object : ComposeViewFactory<R>() { private val originalFactory = this@asComposeViewFactory @Composable override fun Content( rendering: R, viewEnvironment: ViewEnvironment ) { AndroidView( factory = { context - > originalFactory.buildView(rendering, viewEnvironment, context) }, update = { view - > } ) } }
  51. @zachklipp fun <R : Any> ViewFactory<R>.asComposeViewFactory() : ComposeViewFactory<R> = (this

    as? ComposeViewFactory) ?: object : ComposeViewFactory<R>() { private val originalFactory = this@asComposeViewFactory @Composable override fun Content( rendering: R, viewEnvironment: ViewEnvironment ) { AndroidView( factory = { context - > originalFactory.buildView(rendering, viewEnvironment, context) }, update = { view - > view.showRendering(rendering, viewEnvironment) } ) } }
  52. @zachklipp fun <R : Any> ViewFactory<R>.asComposeViewFactory() : ComposeViewFactory<R> = (this

    as? ComposeViewFactory) ?: object : ComposeViewFactory<R>() { private val originalFactory = this@asComposeViewFactory @Composable override fun Content( rendering: R, viewEnvironment: ViewEnvironment ) { AndroidView( factory = { context - > originalFactory.buildView(rendering, viewEnvironment, context) }, update = { view - > view.showRendering(rendering, viewEnvironment) } ) } }
  53. @zachklipp Compose / View intro • Complicated implementation, simple API

    • Back in early 2020, very buggy – much be tt er now
  54. @zachklipp class ComposeViewFactory<RenderingT : Any>( override val type: KClass<RenderingT>, private

    val content: @Composable() (RenderingT, ViewEnvironment) - > Unit ) : ViewFactory<RenderingT> { override fun buildView( initialRendering: RenderingT, initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? ) : View { val composeContainer = FrameLayout(contextForNewView) val renderState = mutableStateOf<Pair<RenderingT, ViewEnvironment>?>( / / This will be updated immediately by bindShowRendering below. value = null, areEquivalent = StructurallyEqual ) FrameManager.ensureStarted() composeContainer.bindShowRendering( initialRendering, initialViewEnvironment ) { rendering, environment - > renderState.value = Pair(rendering, environment) } composeContainer.setOrContinueContent(initialViewEnvironment) { val (rendering, environment) = renderState.value ! ! showRenderingWrappedWithRoot(rendering, environment) } return composeContainer } @Composable internal fun showRenderingWrappedWithRoot( rendering: RenderingT, viewEnvironment: ViewEnvironment ) { wrapWithRootIfNecessary(viewEnvironment) { content(rendering, viewEnvironment) } } }
  55. @zachklipp abstract class ComposeViewFactory<RenderingT : Any> : ViewFactory<RenderingT> { @Composable

    abstract fun Content( rendering: RenderingT, viewEnvironment: ViewEnvironment ) f i nal override fun buildView( initialRendering: RenderingT, initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? ) : View = ComposeView(contextForNewView).also { composeView - > composeView.bindShowRendering( initialRendering, initialViewEnvironment ) { rendering, environment - > composeView.setContent { Content(rendering, environment) } } } }
  56. @zachklipp ViewTree*Owners… 😐 • More fl exible than previous hooks

    • Gotcha: root views (e.g. modals) • Only a pa tt ern
  57. @zachklipp Implementing view state restoration is hard • Multiple mechanisms

    • View onSaveInstanceState/onRestoreInstanceState • AndroidX SavedStateRegistry • Compose SaveableStateRegistry • IDs • Lifecycle-sensitive
  58. @zachklipp Writing a navigation library is hard • But there

    are bene fi ts if you’re willing to invest.
  59. @zachklipp Writing a navigation library for Views is hard •

    But there are bene fi ts if you’re willing to invest. • It’s a lot easier in Compose! • Bidirectional interop is one of Compose’s best features.
  60. @zachklipp Current status Adoption • Integration with internal app sca

    ff olding. • New internal design system is primarily Compose-based. • All new UIs are built with Compose. • ~120 renderings already using it. • Lots of samples on public GitHub.
  61. @zachklipp Current status Work fl ow pe rf ormance •

    Immutable tree of immutable renderings • Eager rendering • Molecule?
  62. @zachklipp Thanks: • Ray Ryan + Square Foundation team: Code

    reviews, talk feedback, the entire Work fl ow project • Leland Richardson: Troubleshooting early interop bugs
  63. @zachklipp …and thank you! Questions? Zach Klippenstein (he/him) @zachklipp [email protected]

    kotlinlang.slack.com #squarelibraries #compose square.github.io/work fl ow bit.ly/work fl ow-compose-blog developer.android.com/jetpack/compose