Slide 1

Slide 1 text

Zach Klippenstein (he/him) The Workflow Pattern, Composed droidconSF 2022 @zachklipp

Slide 2

Slide 2 text

@zachklipp Who’s this guy? • Android for 7+ years • Square: POS, mobile infrastructure, design systems • Google: Compose

Slide 3

Slide 3 text

@zachklipp Workflow Workflow + Compose Lessons learned Current status

Slide 4

Slide 4 text

@zachklipp Workflow Workflow + Compose Lessons learned Current status

Slide 5

Slide 5 text

@zachklipp The What-flow pattern?

Slide 6

Slide 6 text

@zachklipp The Workflow pattern

Slide 7

Slide 7 text

@zachklipp The Workflow pattern 2018 2022

Slide 8

Slide 8 text

@zachklipp The Workflow pattern

Slide 9

Slide 9 text

@zachklipp The Workflow pattern 1/2

Slide 10

Slide 10 text

@zachklipp Work fl ow StateT RenderingT events PropsT

Slide 11

Slide 11 text

@zachklipp Overview Detail Work fl ow props Overview Work fl ow Detail Work fl ow props renderings

Slide 12

Slide 12 text

@zachklipp Advantages • Injectability class MyWorkflow(val fooService, val barService) { / / . . . }

Slide 13

Slide 13 text

@zachklipp Advantages • Injectability • Reusability class MyWorkflow(val fooService, val barService) { / / . . . }

Slide 14

Slide 14 text

@zachklipp Advantages • Injectability • Reusability • Testability class MyWorkflow(val fooService, val barService) { / / . . . }

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

@zachklipp Advantages • Injectability • Reusability • Testability • Modularizability • View customizability class MyWorkflow(val fooService, val barService) { / / . . . }

Slide 17

Slide 17 text

@zachklipp The Workflow pattern 2/2

Slide 18

Slide 18 text

@zachklipp 2,837.95USD Tech Company Class A View data class StockRendering( val price: String, val currency: String, val name: String ) Rendering ScreenViewFactory

Slide 19

Slide 19 text

@zachklipp Overview Detail ViewFactory rendering Overview ViewFactory Detail ViewFactory rendering

Slide 20

Slide 20 text

@zachklipp interface ViewFactory

Slide 21

Slide 21 text

@zachklipp interface ViewFactory { fun buildView() : View }

Slide 22

Slide 22 text

@zachklipp interface ViewFactory { fun buildView( initialRendering: RenderingT ) : View }

Slide 23

Slide 23 text

@zachklipp interface ViewFactory { fun buildView( initialRendering: RenderingT, initialViewEnvironment: ViewEnvironment ) : View }

Slide 24

Slide 24 text

@zachklipp interface ViewFactory { fun buildView( initialRendering: RenderingT, initialViewEnvironment: ViewEnvironment, contextForNewView: Context ) : View }

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

@zachklipp interface ViewFactory { fun buildView( initialRendering: RenderingT, initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? = null ) : View } interface AndroidScreen > { public val viewFactory: ViewFactory }

Slide 27

Slide 27 text

@zachklipp Workflow Workflow + Compose Lessons learned Current status

Slide 28

Slide 28 text

@zachklipp Workflow Workflow + Compose Lessons learned Current status

Slide 29

Slide 29 text

@zachklipp ComposeViewFactory

Slide 30

Slide 30 text

@zachklipp abstract class ComposeViewFactory : ViewFactory

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

@zachklipp abstract class ComposeViewFactory : ViewFactory { 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 ) }

Slide 35

Slide 35 text

@zachklipp abstract class ComposeViewFactory : ViewFactory { 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 ) }

Slide 36

Slide 36 text

@zachklipp abstract class ComposeViewFactory : ViewFactory { 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 ) }

Slide 37

Slide 37 text

@zachklipp abstract class ComposeViewFactory : ViewFactory { 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 ) }

Slide 38

Slide 38 text

@zachklipp abstract class ComposeViewFactory : ViewFactory { 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 ) }

Slide 39

Slide 39 text

@zachklipp abstract class ComposeViewFactory : ViewFactory { 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 ) }

Slide 40

Slide 40 text

@zachklipp interface ViewFactory interface AndroidScreen > { public val viewFactory: ViewFactory }

Slide 41

Slide 41 text

@zachklipp interface ComposeScreen : AndroidScreen { override val viewFactory: ViewFactory }

Slide 42

Slide 42 text

@zachklipp interface ComposeScreen : AndroidScreen { override val viewFactory: ViewFactory @Composable fun Content(viewEnvironment: ViewEnvironment) }

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

@zachklipp • ComposeViewFactory • ComposeScreen

Slide 47

Slide 47 text

@zachklipp data class NameRendering( val name: String )

Slide 48

Slide 48 text

@zachklipp data class NameRendering( val name: String ) : ComposeScreen

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

@zachklipp Showing another rendering Overview Detail ViewFactory rendering Overview ViewFactory Detail ViewFactory rendering

Slide 52

Slide 52 text

@zachklipp data class PersonRendering( val name: String, val details: Any ) : ComposeScreen

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

@zachklipp @Composable fun WorkflowRendering( rendering: Any, viewEnvironment: ViewEnvironment, modif i er: Modif i er = Modif i er ) {}

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

@zachklipp Overview Detail ViewFactory rendering Overview ViewFactory Detail ViewFactory rendering

Slide 64

Slide 64 text

@zachklipp

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

@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 = remember { viewEnvironment[ViewRegistry] .getFactoryForRendering(rendering) .asComposeViewFactory() } val lifecycleOwner = rememberChildLifecycleOwner() CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) { Box(modif i er, propagateMinConstraints = true) { viewFactory.Content(rendering, viewEnvironment) } } } }

Slide 71

Slide 71 text

@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 = remember { viewEnvironment[ViewRegistry] .getFactoryForRendering(rendering) .asComposeViewFactory() } val lifecycleOwner = rememberChildLifecycleOwner() CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) { Box(modif i er, propagateMinConstraints = true) { viewFactory.Content(rendering, viewEnvironment) } } } }

Slide 72

Slide 72 text

@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( * 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) } } } }

Slide 73

Slide 73 text

@zachklipp • ComposeViewFactory • ComposeScreen • WorkflowRendering

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

@zachklipp ComposeViewFactory

Slide 76

Slide 76 text

@zachklipp asComposeViewFactory

Slide 77

Slide 77 text

@zachklipp fun ViewFactory.asComposeViewFactory() : ComposeViewFactory

Slide 78

Slide 78 text

@zachklipp internal fun ViewFactory.asComposeViewFactory() : ComposeViewFactory

Slide 79

Slide 79 text

@zachklipp fun ViewFactory.asComposeViewFactory() : ComposeViewFactory = (this as? ComposeViewFactory)

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

@zachklipp val color = composeViewFactory { rendering, _ - > Text(rendering.toString()) } val red = composeViewFactory { _, viewEnvironment - > Row { Text("Red: ") key(Color.Red : : class) { val lifecycleOwner = rememberChildLifecycleOwner() CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) { Box(propagateMinConstraints = true) { color.Content(Color.Red, viewEnvironment) } } } } }

Slide 82

Slide 82 text

@zachklipp fun ViewFactory.asComposeViewFactory() : ComposeViewFactory = (this as? ComposeViewFactory)

Slide 83

Slide 83 text

@zachklipp fun ViewFactory.asComposeViewFactory() : ComposeViewFactory = (this as? ComposeViewFactory) ?: object : ComposeViewFactory() {}

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

@zachklipp fun ViewFactory.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) } } }

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

@zachklipp fun ViewFactory.asComposeViewFactory() : ComposeViewFactory = (this as? ComposeViewFactory) ?: object : ComposeViewFactory() { 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) } ) } }

Slide 91

Slide 91 text

@zachklipp fun ViewFactory.asComposeViewFactory() : ComposeViewFactory = (this as? ComposeViewFactory) ?: object : ComposeViewFactory() { 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) } ) } }

Slide 92

Slide 92 text

@zachklipp • ComposeViewFactory • ComposeScreen • WorkflowRendering • asComposeViewFactory

Slide 93

Slide 93 text

@zachklipp View-based Rendering View-based Rendering Compose-based Rendering Compose-based Rendering View-based Rendering

Slide 94

Slide 94 text

@zachklipp

Slide 95

Slide 95 text

@zachklipp Workflow Workflow + Compose Lessons learned Current status

Slide 96

Slide 96 text

@zachklipp Workflow Workflow + Compose Lessons learned Current status

Slide 97

Slide 97 text

@zachklipp Compose / View intro • Complicated implementation, simple API • Back in early 2020, very buggy – much be tt er now

Slide 98

Slide 98 text

@zachklipp class ComposeViewFactory( override val type: KClass, private val content: @Composable() (RenderingT, ViewEnvironment) - > Unit ) : ViewFactory { override fun buildView( initialRendering: RenderingT, initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? ) : View { val composeContainer = FrameLayout(contextForNewView) val renderState = mutableStateOf?>( / / 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) } } }

Slide 99

Slide 99 text

@zachklipp abstract class ComposeViewFactory : ViewFactory { @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) } } } }

Slide 100

Slide 100 text

@zachklipp ViewTree*Owners… 😐 • More fl exible than previous hooks • Gotcha: root views (e.g. modals) • Only a pa tt ern

Slide 101

Slide 101 text

@zachklipp Implementing view state restoration is hard • Multiple mechanisms • View onSaveInstanceState/onRestoreInstanceState • AndroidX SavedStateRegistry • Compose SaveableStateRegistry • IDs • Lifecycle-sensitive

Slide 102

Slide 102 text

@zachklipp CompositionLocals • For ViewEnvironment? • Strongly discouraged o ffi cially • Cleaner demos ≠ more maintainable code

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

@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.

Slide 105

Slide 105 text

@zachklipp Workflow Workflow + Compose Lessons learned Current status

Slide 106

Slide 106 text

@zachklipp Workflow Workflow + Compose Lessons learned Current status

Slide 107

Slide 107 text

@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.

Slide 108

Slide 108 text

@zachklipp Current status Work fl ow pe rf ormance • Immutable tree of immutable renderings • Eager rendering • Molecule?

Slide 109

Slide 109 text

@zachklipp Final thoughts…

Slide 110

Slide 110 text

@zachklipp Final thoughts…

Slide 111

Slide 111 text

@zachklipp Final thoughts…

Slide 112

Slide 112 text

@zachklipp Thanks: • Ray Ryan + Square Foundation team: Code reviews, talk feedback, the entire Work fl ow project • Leland Richardson: Troubleshooting early interop bugs

Slide 113

Slide 113 text

@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