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

The Workflow Pattern, Composed (droidconSF 2022)

The Workflow Pattern, Composed (droidconSF 2022)

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