SlideShare a Scribd company logo
Zach Klippenstein
The Workflow Pattern, Composed
● Android for 7+ years
● Square: Foundation, Design Systems
● Google: Compose
● Kotlin Slack: #compose
Zach who?
1.Workflow
2.Jetpack Compose
3.Square + Compose
4.Workflow + Compose
5.Lessons learned
6.Current status
1.Workflow
2.Jetpack Compose
3.Square + Compose
4.Workflow + Compose
5.Lessons learned
6.Current status
The What-flow pattern?
The Workflow pattern
The Workflow pattern
1/2
Workflow
PropsT
RenderingT
events
StateT
Overview
Detail
Workflow
Overview
Workflow
Detail
Workflow
props props
renderings
The Workflow pattern
2/2
data class StockRendering(
val price: String,
val currency: String,
val name: String
)
Rendering/
Model
View
ViewFactory
Overview
Detail
ViewFactory
Overview
ViewFactory
Detail
ViewFactory
rendering rendering
● Injectability
● Reusability
● Testability
● Modularizability
● (View) customizability
Advantages:
1.Workflow
2.Jetpack Compose
3.Square + Compose
4.Workflow + Compose
5.Lessons learned
6.Current status
The Workflow Pattern, Composed (2021)
1.Workflow
2.Jetpack Compose
3.Square + Compose
4.Workflow + Compose
5.Lessons learned
6.Current status
Compose @ Square
Workflow Integration
Design System
Features
Infrastructure
Compose @ Square
Summer
2021
Winter
2021
Winter/Sprin
g 2021
Summer
2020
Polish & land
Workflow integration
Design system
initial release
Initial design system
infra and
components, EAP
Design system
considering adoption,
evaluation pilot
Spring
2020
Prototyping Workflow
integration
Spring
2019
Jetpack Compose
first announced
Compose release
timeline announced
Compose 1.0 released
(timeline not to scale)
Compose @ Square
Workflow Integration
Design System
Features
Infrastructure
1.Workflow
2.Jetpack Compose
3.Square + Compose
4.Workflow + Compose
5.Lessons learned
6.Current status
Providing a view for a
rendering
data class NameRendering(
val name: String
) : AndroidViewRendering<NameRendering> {
override val viewFactory: ViewFactory<NameRendering>
get() = LayoutRunner.bind(
layoutId = layout.rendering_layout,
constructor = ::RenderingViewBinding
)
}
data class NameRendering(
val name: String
) : AndroidViewRendering<NameRendering> {
override val viewFactory: ViewFactory<NameRendering>
get() = LayoutRunner.bind(
layoutId = layout.rendering_layout,
constructor = ::RenderingViewBinding
)
}
data class NameRendering(
val name: String
) : AndroidViewRendering<NameRendering> {
override val viewFactory: ViewFactory<NameRendering>
get() = LayoutRunner.bind(
layoutId = layout.rendering_layout,
constructor = ::RenderingViewBinding
)
}
data class NameRendering(
val name: String
) : AndroidViewRendering<NameRendering> {
override val viewFactory: ViewFactory<NameRendering>
get() = LayoutRunner.bind(
layoutId = R.layout.rendering_layout,
constructor = ::NameLayoutRunner
)
}
class NameLayoutRunner(view: View) : LayoutRunner<NameRendering> {
private val nameView = view.findViewById<TextView>(R.id.name)
override fun showRendering(
rendering: NameRendering,
viewEnvironment: ViewEnvironment
) {
nameView.text = rendering.name
}
}
class NameLayoutRunner(view: View) : LayoutRunner<NameRendering> {
private val nameView = view.findViewById<TextView>(R.id.name)
override fun showRendering(
rendering: NameRendering,
viewEnvironment: ViewEnvironment
) {
nameView.text = rendering.name
}
}
data class NameRendering(
val name: String
) : AndroidViewRendering<NameRendering> {
override val viewFactory: ViewFactory<NameRendering>
get() = LayoutRunner.bind(
layoutId = R.layout.rendering_layout,
constructor = ::NameLayoutRunner
)
}
class NameLayoutRunner(view: View) : LayoutRunner<NameRendering> {
private val nameView = view.findViewById<TextView>(R.id.name)
override fun showRendering(
rendering: NameRendering,
viewEnvironment: ViewEnvironment
) {
nameView.text = rendering.name
}
}
data class NameRendering(
val name: String
) : ComposeRendering {
@Composable
override fun Content(viewEnvironment: ViewEnvironment) {
BasicText(name)
}
}
data class NameRendering(
val name: String
) : ComposeRendering {
@Composable
override fun Content(viewEnvironment: ViewEnvironment) {
BasicText(name)
}
}
data class NameRendering(
val name: String
) : ComposeRendering {
@Composable
override fun Content(viewEnvironment: ViewEnvironment) {
Text(name)
}
}
interface ComposeRendering : AndroidViewRendering<Nothing> {
override val viewFactory: ViewFactory<Nothing>
get() = ComposeRenderingViewFactory
@Composable
fun Content(viewEnvironment: ViewEnvironment)
}
interface ComposeRendering : AndroidViewRendering<Nothing> {
override val viewFactory: ViewFactory<Nothing>
get() = ComposeRenderingViewFactory
@Composable
fun Content(viewEnvironment: ViewEnvironment)
}
interface ComposeRendering : AndroidViewRendering<Nothing> {
override val viewFactory: ViewFactory<Nothing>
get() = object : ComposeViewFactory<ComposeRendering>() {
override val type: KClass<in ComposeRendering> =
ComposeRendering::class
@Composable override fun Content(
rendering: ComposeRendering,
viewEnvironment: ViewEnvironment
) {
rendering.Content(viewEnvironment)
}
}
}
interface ComposeRendering : AndroidViewRendering<Nothing> {
override val viewFactory: ViewFactory<Nothing>
get() = object : ComposeViewFactory<ComposeRendering>() {
@Composable override fun Content(
rendering: ComposeRendering,
viewEnvironment: ViewEnvironment
) {
rendering.Content(viewEnvironment)
}
}
}
interface ComposeRendering : AndroidViewRendering<Nothing> {
override val viewFactory: ViewFactory<Nothing>
get() = object : ComposeViewFactory<ComposeRendering>() {
@Composable override fun Content(
rendering: ComposeRendering,
viewEnvironment: ViewEnvironment
) {
rendering.Content(viewEnvironment)
}
}
}
data class NameRendering(
val name: String
) : ComposeRendering {
@Composable
override fun Content(viewEnvironment: ViewEnvironment) {
Text(name)
}
}
●ComposeRendering
●(ComposeViewFactory)
Showing another rendering
data class PersonRendering(
val name: String,
val details: Any
) : ComposeRendering {
@Composable
override fun Content(viewEnvironment: ViewEnvironment) {
Column {
BasicText(name)
WorkflowRendering(
details, viewEnvironment,
Modifier.weight(1f)
)
}
}
}
data class PersonRendering(
val name: String,
val details: Any
) : ComposeRendering {
@Composable
override fun Content(viewEnvironment: ViewEnvironment) {
Column {
Text(name)
WorkflowRendering(
details, viewEnvironment,
Modifier.weight(1f)
)
}
}
}
data class PersonRendering(
val name: String,
val details: Any
) : ComposeRendering {
@Composable
override fun Content(viewEnvironment: ViewEnvironment) {
Column {
Text(name)
WorkflowRendering(
details, viewEnvironment,
Modifier.weight(1f)
)
}
}
}
data class PersonRendering(
val name: String,
val details: Any
) : ComposeRendering {
@Composable
override fun Content(viewEnvironment: ViewEnvironment) {
Column {
Text(name)
WorkflowRendering(
details, viewEnvironment,
Modifier.weight(1f)
)
}
}
}
data class PersonRendering(
val name: String,
val details: Any
) : ComposeRendering {
@Composable
override fun Content(viewEnvironment: ViewEnvironment) {
Column {
Text(name)
WorkflowRendering(
details, viewEnvironment,
Modifier.weight(1f)
)
}
}
}
@Composable fun WorkflowRendering(
rendering: Any,
viewEnvironment: ViewEnvironment,
modifier: Modifier = Modifier
) {
val renderingCompatibilityKey = Compatible.keyFor(rendering)
key(renderingCompatibilityKey) {
val viewFactory = remember {
viewEnvironment[ViewRegistry]
.getFactoryForRendering(rendering)
.asComposeViewFactory()
}
val lifecycleOwner = rememberChildLifecycleOwner()
CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) {
Box(modifier, propagateMinConstraints = true) {
viewFactory.Content(rendering, viewEnvironment)
}
}
}
}
@Composable fun WorkflowRendering(
rendering: Any,
viewEnvironment: ViewEnvironment,
modifier: Modifier = Modifier
) {
val renderingCompatibilityKey = Compatible.keyFor(rendering)
key(renderingCompatibilityKey) {
val viewFactory: ComposeViewFactory<Any> = remember {
viewEnvironment[ViewRegistry]
.getFactoryForRendering(rendering)
.asComposeViewFactory()
}
val lifecycleOwner = rememberChildLifecycleOwner()
CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) {
Box(modifier, propagateMinConstraints = true) {
viewFactory.Content(rendering, viewEnvironment)
}
}
}
}
@Composable fun WorkflowRendering(
rendering: Any,
viewEnvironment: ViewEnvironment,
modifier: Modifier = Modifier
) {
val renderingCompatibilityKey = Compatible.keyFor(rendering)
key(renderingCompatibilityKey) {
val viewFactory: ComposeViewFactory<Any> = remember {
viewEnvironment[ViewRegistry]
.getFactoryForRendering(rendering)
.asComposeViewFactory()
}
val lifecycleOwner = rememberChildLifecycleOwner()
CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) {
Box(modifier, propagateMinConstraints = true) {
viewFactory.Content(rendering, viewEnvironment)
}
}
}
}
@Composable fun WorkflowRendering(
rendering: Any,
viewEnvironment: ViewEnvironment,
modifier: Modifier = Modifier
) {
val renderingCompatibilityKey = Compatible.keyFor(rendering)
key(renderingCompatibilityKey) {
val viewFactory: ComposeViewFactory<Any> = remember {
viewEnvironment[ViewRegistry]
.getFactoryForRendering(rendering)
.asComposeViewFactory()
}
val lifecycleOwner = rememberChildLifecycleOwner()
CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) {
Box(modifier, propagateMinConstraints = true) {
viewFactory.Content(rendering, viewEnvironment)
}
}
}
}
Overview
Detail
ViewFactory
Overview
ViewFactory
Detail
ViewFactory
rendering rendering
The Workflow Pattern, Composed (2021)
@Composable fun WorkflowRendering(
rendering: Any,
viewEnvironment: ViewEnvironment,
modifier: Modifier = Modifier
) {
val renderingCompatibilityKey = Compatible.keyFor(rendering)
key(renderingCompatibilityKey) {
val viewFactory: ComposeViewFactory<Any> = remember {
viewEnvironment[ViewRegistry]
.getFactoryForRendering(rendering)
.asComposeViewFactory()
}
val lifecycleOwner = rememberChildLifecycleOwner()
CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) {
Box(modifier, propagateMinConstraints = true) {
viewFactory.Content(rendering, viewEnvironment)
}
}
}
}
@Composable fun WorkflowRendering(
rendering: Any,
viewEnvironment: ViewEnvironment,
modifier: Modifier = Modifier
) {
val renderingCompatibilityKey = Compatible.keyFor(rendering)
key(renderingCompatibilityKey) {
val viewFactory: ComposeViewFactory<Any> = remember {
viewEnvironment[ViewRegistry]
.getFactoryForRendering(rendering)
.asComposeViewFactory()
}
val lifecycleOwner = rememberChildLifecycleOwner()
CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) {
Box(modifier, propagateMinConstraints = true) {
viewFactory.Content(rendering, viewEnvironment)
}
}
}
}
@Composable fun WorkflowRendering(
rendering: Any,
viewEnvironment: ViewEnvironment,
modifier: Modifier = Modifier
) {
val renderingCompatibilityKey = Compatible.keyFor(rendering)
key(renderingCompatibilityKey) {
val viewFactory: ComposeViewFactory<Any> = remember {
viewEnvironment[ViewRegistry]
.getFactoryForRendering(rendering)
.asComposeViewFactory()
}
val lifecycleOwner = rememberChildLifecycleOwner()
CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) {
Box(modifier, propagateMinConstraints = true) {
viewFactory.Content(rendering, viewEnvironment)
}
}
}
}
@Composable fun WorkflowRendering(
rendering: Any,
viewEnvironment: ViewEnvironment,
modifier: Modifier = Modifier
) {
val renderingCompatibilityKey = rendering::class
key(renderingCompatibilityKey) {
val viewFactory: ComposeViewFactory<Any> = remember {
viewEnvironment[ViewRegistry]
.getFactoryForRendering(rendering)
.asComposeViewFactory()
}
val lifecycleOwner = rememberChildLifecycleOwner()
CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) {
Box(modifier, propagateMinConstraints = true) {
viewFactory.Content(rendering, viewEnvironment)
}
}
}
}
@Composable fun WorkflowRendering(
rendering: Any,
viewEnvironment: ViewEnvironment,
modifier: Modifier = Modifier
) {
val renderingCompatibilityKey = Compatible.keyFor(rendering)
key(renderingCompatibilityKey) {
val viewFactory: ComposeViewFactory<Any> = remember {
viewEnvironment[ViewRegistry]
.getFactoryForRendering(rendering)
.asComposeViewFactory()
}
val lifecycleOwner = rememberChildLifecycleOwner()
CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) {
Box(modifier, propagateMinConstraints = true) {
viewFactory.Content(rendering, viewEnvironment)
}
}
}
}
@Composable fun WorkflowRendering(
rendering: Any,
viewEnvironment: ViewEnvironment,
modifier: Modifier = Modifier
) {
val renderingCompatibilityKey = Compatible.keyFor(rendering)
key(renderingCompatibilityKey) {
val viewFactory: ComposeViewFactory<Any> = remember(renderingCompatibilityKey) {
viewEnvironment[ViewRegistry]
.getFactoryForRendering(rendering)
.asComposeViewFactory()
}
val lifecycleOwner = rememberChildLifecycleOwner()
CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) {
Box(modifier, propagateMinConstraints = true) {
viewFactory.Content(rendering, viewEnvironment)
}
}
}
}
@Composable fun WorkflowRendering(
rendering: Any,
viewEnvironment: ViewEnvironment,
modifier: Modifier = Modifier
) {
val renderingCompatibilityKey = Compatible.keyFor(rendering)
key(renderingCompatibilityKey) {
val viewFactory: ComposeViewFactory<Any> = remember {
viewEnvironment[ViewRegistry]
.getFactoryForRendering(rendering)
.asComposeViewFactory()
}
val lifecycleOwner = rememberChildLifecycleOwner()
CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) {
Box(modifier, propagateMinConstraints = true) {
viewFactory.Content(rendering, viewEnvironment)
}
}
}
}
@Composable fun WorkflowRendering(
rendering: Any,
viewEnvironment: ViewEnvironment,
modifier: Modifier = Modifier
) {
val renderingCompatibilityKey = Compatible.keyFor(rendering)
key(renderingCompatibilityKey) {
val viewFactory: ComposeViewFactory<Any> = remember {
viewEnvironment[ViewRegistry]
.getFactoryForRendering(rendering)
.asComposeViewFactory()
}
val lifecycleOwner = rememberChildLifecycleOwner()
CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) {
Box(modifier, propagateMinConstraints = true) {
viewFactory.Content(rendering, viewEnvironment)
}
}
}
}
/**
* Renders [rendering] into the composition using this [ViewEnvironment]'s [ViewRegistry] to
* generate the view.
*
* This function fulfills 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 modifier A [Modifier] that will be applied to composable used to show [rendering].
*
* @throws IllegalArgumentException if no factory can be found for [rendering]'s type.
*/
@WorkflowUiExperimentalApi
@Composable public fun WorkflowRendering(
rendering: Any,
viewEnvironment: ViewEnvironment,
modifier: Modifier = Modifier
) {
// 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 modifier passed
// into this function is to directly control the layout of the child view – which means
// minimum constraints are likely to be significant.
Box(modifier, propagateMinConstraints = true) {
viewFactory.Content(rendering, viewEnvironment)
}
}
}
}
●ComposeRendering
●WorkflowRendering
●(ComposeViewFactory)
ComposeViewFactory
ComposeViewFactory
Composables in Views
interface ViewFactory<in RenderingT : Any> {
val type: KClass<in RenderingT>
fun buildView(
initialRendering: RenderingT,
initialViewEnvironment: ViewEnvironment,
contextForNewView: Context,
container: ViewGroup? = null
): View
}
interface ViewFactory<in RenderingT : Any> {
fun buildView(
initialRendering: RenderingT,
initialViewEnvironment: ViewEnvironment,
contextForNewView: Context,
container: ViewGroup? = null
): View
}
interface ViewFactory<in RenderingT : Any> {
fun buildView(
initialRendering: RenderingT,
initialViewEnvironment: ViewEnvironment,
contextForNewView: Context,
container: ViewGroup? = null
): View
}
interface ViewFactory<in RenderingT : Any> {
fun buildView(
initialRendering: RenderingT,
initialViewEnvironment: ViewEnvironment,
contextForNewView: Context,
container: ViewGroup? = null
): View
}
interface ViewFactory<in RenderingT : Any> {
fun buildView(
initialRendering: RenderingT,
initialViewEnvironment: ViewEnvironment,
contextForNewView: Context,
container: ViewGroup? = null
): View
}
interface ViewFactory<in RenderingT : Any> {
fun buildView(
initialRendering: RenderingT,
initialViewEnvironment: ViewEnvironment,
contextForNewView: Context,
container: ViewGroup? = null
): View
}
abstract class ComposeViewFactory<RenderingT : Any> : ViewFactory<RenderingT> {
@Composable abstract fun Content(
rendering: RenderingT,
viewEnvironment: ViewEnvironment
)
final 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)
}
}
}
}
abstract class ComposeViewFactory<RenderingT : Any> : ViewFactory<RenderingT> {
@Composable abstract fun Content(
rendering: RenderingT,
viewEnvironment: ViewEnvironment
)
final 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)
}
}
}
}
abstract class ComposeViewFactory<RenderingT : Any> : ViewFactory<RenderingT> {
@Composable abstract fun Content(
rendering: RenderingT,
viewEnvironment: ViewEnvironment
)
final 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)
}
}
}
}
abstract class ComposeViewFactory<RenderingT : Any> : ViewFactory<RenderingT> {
@Composable abstract fun Content(
rendering: RenderingT,
viewEnvironment: ViewEnvironment
)
final 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)
}
}
}
}
abstract class ComposeViewFactory<RenderingT : Any> : ViewFactory<RenderingT> {
@Composable abstract fun Content(
rendering: RenderingT,
viewEnvironment: ViewEnvironment
)
final 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)
}
}
}
}
abstract class ComposeViewFactory<RenderingT : Any> : ViewFactory<RenderingT> {
@Composable abstract fun Content(
rendering: RenderingT,
viewEnvironment: ViewEnvironment
)
final 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)
}
}
}
}
abstract class ComposeViewFactory<RenderingT : Any> : ViewFactory<RenderingT> {
@Composable abstract fun Content(
rendering: RenderingT,
viewEnvironment: ViewEnvironment
)
final 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)
}
}
}
}
abstract class ComposeViewFactory<RenderingT : Any> : ViewFactory<RenderingT> {
@Composable abstract fun Content(
rendering: RenderingT,
viewEnvironment: ViewEnvironment
)
final 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)
}
}
}
}
interface ComposeRendering : AndroidViewRendering<Nothing> {
override val viewFactory: ViewFactory<Nothing>
get() = object : ComposeViewFactory<ComposeRendering>() {
@Composable override fun Content(
rendering: ComposeRendering,
viewEnvironment: ViewEnvironment
) {
rendering.Content(viewEnvironment)
}
}
}
data class PersonRendering(
val name: String,
val details: Any
) : ComposeRendering {
@Composable override fun Content(viewEnvironment: ViewEnvironment) {
Column {
BasicText(name)
WorkflowRendering(
details, viewEnvironment,
Modifier.weight(1f)
)
}
}
}
data class PersonRendering(
val name: String,
val details: Any
)
object PersonViewFactory : ComposeViewFactory<PersonRendering>() {
@Composable override fun Content(
rendering: PersonRendering,
viewEnvironment: ViewEnvironment
) {
Column {
BasicText(rendering.name)
WorkflowRendering(
rendering.details, viewEnvironment,
Modifier.weight(1f)
)
}
}
}
data class PersonRendering(
val name: String,
val details: Any
)
val personViewFactory: ViewFactory<PersonRendering> =
composeViewFactory { rendering, viewEnvironment ->
Column {
BasicText(rendering.name)
WorkflowRendering(
rendering.details, viewEnvironment,
Modifier.weight(1f)
)
}
}
●ComposeRendering
●WorkflowRendering
●ComposeViewFactory
@Composable fun WorkflowRendering(
rendering: Any,
viewEnvironment: ViewEnvironment,
modifier: Modifier = Modifier
) {
val renderingCompatibilityKey = Compatible.keyFor(rendering)
key(renderingCompatibilityKey) {
val viewFactory: ComposeViewFactory<Any> = remember {
viewEnvironment[ViewRegistry]
.getFactoryForRendering(rendering)
.asComposeViewFactory()
}
val lifecycleOwner = rememberChildLifecycleOwner()
CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) {
Box(modifier, propagateMinConstraints = true) {
viewFactory.Content(rendering, viewEnvironment)
}
}
}
}
CoMpOsAbLeS iN vIeWs
CoMpOsAbLeS iN vIeWs
Views in Composables?
fun <R : Any> ViewFactory<R>.asComposeViewFactory(): ComposeViewFactory<R> =
(this as? ComposeViewFactory) ?: object : ComposeViewFactory<R>() {
private val originalFactory = this@asComposeViewFactory
override val type: KClass<in R> get() = originalFactory.type
@Composable override fun Content(
rendering: R,
viewEnvironment: ViewEnvironment
) {
AndroidView(
factory = { context ->
originalFactory.buildView(rendering, viewEnvironment, context, container = null)
.also { view ->
checkNotNull(view.getShowRendering<Any>()) {
"View.bindShowRendering should have been called for $view"
}
}
},
update = { view ->
view.showRendering(rendering, viewEnvironment)
}
)
}
}
fun <R : Any> ViewFactory<R>.asComposeViewFactory(): ComposeViewFactory<R> =
(this as? ComposeViewFactory) ?: object : ComposeViewFactory<R>() {
private val originalFactory = this@asComposeViewFactory
override val type: KClass<in R> get() = originalFactory.type
@Composable override fun Content(
rendering: R,
viewEnvironment: ViewEnvironment
) {
AndroidView(
factory = { context ->
originalFactory.buildView(rendering, viewEnvironment, context, container = null)
.also { view ->
checkNotNull(view.getShowRendering<Any>()) {
"View.bindShowRendering should have been called for $view"
}
}
},
update = { view ->
view.showRendering(rendering, viewEnvironment)
}
)
}
}
val color = composeViewFactory<Color> { rendering, _ ->
Text(rendering.toString())
}
val red = composeViewFactory<Unit> { _, viewEnvironment ->
Row {
Text("Red: ")
WorkflowRendering(Color.Red, viewEnvironment)
}
}
val color = composeViewFactory<Color> { rendering, _ ->
Text(rendering.toString())
}
val red = composeViewFactory<Unit> { _, viewEnvironment ->
Row {
Text("Red: ")
val renderingCompatibilityKey = Compatible.keyFor(Color.Red)
key(renderingCompatibilityKey) {
val lifecycleOwner = rememberChildLifecycleOwner()
CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) {
Box(propagateMinConstraints = true) {
color.Content(Color.Red, viewEnvironment)
}
}
}
}
}
fun <R : Any> ViewFactory<R>.asComposeViewFactory(): ComposeViewFactory<R> =
(this as? ComposeViewFactory) ?: object : ComposeViewFactory<R>() {
private val originalFactory = this@asComposeViewFactory
override val type: KClass<in R> get() = originalFactory.type
@Composable override fun Content(
rendering: R,
viewEnvironment: ViewEnvironment
) {
AndroidView(
factory = { context ->
originalFactory.buildView(rendering, viewEnvironment, context, container = null)
.also { view ->
checkNotNull(view.getShowRendering<Any>()) {
"View.bindShowRendering should have been called for $view"
}
}
},
update = { view ->
view.showRendering(rendering, viewEnvironment)
}
)
}
}
fun <R : Any> ViewFactory<R>.asComposeViewFactory(): ComposeViewFactory<R> =
(this as? ComposeViewFactory) ?: object : ComposeViewFactory<R>() {
private val originalFactory = this@asComposeViewFactory
override val type: KClass<in R> get() = originalFactory.type
@Composable override fun Content(
rendering: R,
viewEnvironment: ViewEnvironment
) {
AndroidView(
factory = { context ->
originalFactory.buildView(rendering, viewEnvironment, context, container = null)
.also { view ->
checkNotNull(view.getShowRendering<Any>()) {
"View.bindShowRendering should have been called for $view"
}
}
},
update = { view ->
view.showRendering(rendering, viewEnvironment)
}
)
}
}
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, container = null)
.also { view ->
checkNotNull(view.getShowRendering<Any>()) {
"View.bindShowRendering should have been called for $view"
}
}
},
update = { view ->
view.showRendering(rendering, viewEnvironment)
}
)
}
}
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, container = null)
.also { view ->
checkNotNull(view.getShowRendering<Any>()) {
"View.bindShowRendering should have been called for $view"
}
}
},
update = { view ->
view.showRendering(rendering, viewEnvironment)
}
)
}
}
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, container = null)
.also { view ->
checkNotNull(view.getShowRendering<Any>()) {
"View.bindShowRendering should have been called for $view"
}
}
},
update = { view ->
view.showRendering(rendering, viewEnvironment)
}
)
}
}
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)
.also { view ->
checkNotNull(view.getShowRendering<Any>()) {
"View.bindShowRendering should have been called for $view"
}
}
},
update = { view ->
view.showRendering(rendering, viewEnvironment)
}
)
}
}
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)
.also { view ->
checkNotNull(view.getShowRendering<Any>()) {
"View.bindShowRendering should have been called for $view"
}
}
},
update = { view ->
view.showRendering(rendering, viewEnvironment)
}
)
}
}
final 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)
}
}
}
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)
.also { view ->
checkNotNull(view.getShowRendering<Any>()) {
"View.bindShowRendering should have been called for $view"
}
}
},
update = { view ->
view.showRendering(rendering, viewEnvironment)
}
)
}
}
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)
.also { view ->
checkNotNull(view.getShowRendering<Any>()) {
"View.bindShowRendering should have been called for $view"
}
}
},
update = { view ->
view.showRendering(rendering, viewEnvironment)
}
)
}
}
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)
.also { view ->
checkNotNull(view.getShowRendering<Any>()) {
"View.bindShowRendering should have been called for $view"
}w, lifecycleOwner)
}
},
update = { view ->
view.showRendering(rendering, viewEnvironment)
}
)
}
}
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)
.also { view ->
checkNotNull(view.getShowRendering<Any>()) {
"View.bindShowRendering should have been called for $view"
}
}
},
update = { view ->
view.showRendering(rendering, viewEnvironment)
}
)
}
}
●ComposeRendering
●WorkflowRendering
●ComposeViewFactory
●asComposeViewFactory
View-based ViewFactory
View-based ViewFactory Compose-based ViewFactory
View-based ViewFactory
Compose-based ViewFactory
The Workflow Pattern, Composed (2021)
1.Workflow
2.Jetpack Compose
3.Square + Compose
4.Workflow + Compose
5.Lessons learned
6.Current status
● More flexible than previous hooks
● Gotcha: root views (e.g. modals)
● Only a pattern
ViewTree*Owners… 😐
● Multiple mechanisms
○ View onSaveInstanceState/onRestoreInstanceState
○ AndroidX SavedStateRegistry
○ Compose SaveableStateRegistry
● IDs
● Lifecycle-sensitive
Implementing view state restoration is hard
● Complicated implementation, simple API
● Very buggy back in early 2020
Compose / View interop
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)
}
}
}
abstract class ComposeViewFactory<RenderingT : Any> : ViewFactory<RenderingT> {
@Composable abstract fun Content(
rendering: RenderingT,
viewEnvironment: ViewEnvironment
)
final 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)
}
}
}
}
● Double-edged sword
CompositionLocals
● But there are benefits if you're willing to invest
Writing a navigation library is hard
1.Workflow
2.Jetpack Compose
3.Square + Compose
4.Workflow + Compose
5.Lessons learned
6.Current status
● Design system Compose components almost done
● Integration with internal app scaffolding
● Samples
● Will start using for features soon
Current status
Final thoughts…
Zach Klippenstein / twitter.com/zachklipp
kotlinlang.slack.com #squarelibraries
github.com/square/workflow
developer.android.com/jetpack/compose
bit.ly/workflow-compose-blog
Thank you! Questions?
The Workflow Pattern, Composed (2021)

More Related Content

PDF
Choisir entre une API RPC, SOAP, REST, GraphQL? 
Et si le problème était ai...
PDF
He stopped using for/while loops, you won't believe what happened next!
PDF
Development Principles & Philosophy
PDF
⛳️ Votre API passe-t-elle le contrôle technique ?
PDF
Introduction to Grunt.js on Taiwan JavaScript Conference
PDF
PyCon Korea - Real World Graphene
PPT
GWT Extreme!
PPTX
Introduction to Grails Framework
Choisir entre une API RPC, SOAP, REST, GraphQL? 
Et si le problème était ai...
He stopped using for/while loops, you won't believe what happened next!
Development Principles & Philosophy
⛳️ Votre API passe-t-elle le contrôle technique ?
Introduction to Grunt.js on Taiwan JavaScript Conference
PyCon Korea - Real World Graphene
GWT Extreme!
Introduction to Grails Framework

What's hot (20)

PPTX
Angular beans
PDF
Alexander Mostovenko "'Devide at impera' with GraphQL and SSR"
PPTX
iOSDC 2018 動画をなめらかに動かす技術
PPTX
A Tour of PostgREST
PDF
vJUG - The JavaFX Ecosystem
PPT
Scripting Oracle Develop 2007
PDF
REST API に疲れたあなたへ贈る GraphQL 入門
PDF
Developing web applications in 2010
PDF
Kandroid for nhn_deview_20131013_v5_final
PDF
점진적인 레거시 웹 애플리케이션 개선 과정
PDF
Introduction to Ruby on Rails
PDF
Node.js vs Play Framework (with Japanese subtitles)
PDF
Aligning Ember.js with Web Standards
PDF
GR8Conf 2011: Adopting Grails
PDF
Ugo Cei Presentation
PPT
Introduction To Grails
PDF
JavaFX – 10 things I love about you
KEY
Single Page Web Applications with CoffeeScript, Backbone and Jasmine
PDF
Flavors of Concurrency in Java
PDF
The JavaFX Ecosystem
Angular beans
Alexander Mostovenko "'Devide at impera' with GraphQL and SSR"
iOSDC 2018 動画をなめらかに動かす技術
A Tour of PostgREST
vJUG - The JavaFX Ecosystem
Scripting Oracle Develop 2007
REST API に疲れたあなたへ贈る GraphQL 入門
Developing web applications in 2010
Kandroid for nhn_deview_20131013_v5_final
점진적인 레거시 웹 애플리케이션 개선 과정
Introduction to Ruby on Rails
Node.js vs Play Framework (with Japanese subtitles)
Aligning Ember.js with Web Standards
GR8Conf 2011: Adopting Grails
Ugo Cei Presentation
Introduction To Grails
JavaFX – 10 things I love about you
Single Page Web Applications with CoffeeScript, Backbone and Jasmine
Flavors of Concurrency in Java
The JavaFX Ecosystem
Ad

Similar to The Workflow Pattern, Composed (2021) (20)

KEY
Scala on Your Phone
PDF
Angular Schematics
PDF
Functional programming using underscorejs
PDF
Groovy On Trading Desk (2010)
PDF
Android DataBinding (ViewModel, UI Modularization and Testing)
PDF
Data models in Angular 1 & 2
PDF
Connect.js - Exploring React.Native
PDF
JDD 2016 - Pawel Byszewski - Kotlin, why?
PDF
Hadoop Integration in Cassandra
PDF
mobl presentation @ IHomer
PDF
Intro to GraphQL on Android with Apollo DroidconNYC 2017
PDF
Android Automated Testing
PDF
Server Side Swift with Swag
PDF
JS Fest 2019. Glenn Reyes. With great power comes great React hooks!
PDF
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
PDF
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
PDF
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
PDF
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
PDF
Building DSLs with the Spoofax Language Workbench
PDF
IN4308 Lecture 3
Scala on Your Phone
Angular Schematics
Functional programming using underscorejs
Groovy On Trading Desk (2010)
Android DataBinding (ViewModel, UI Modularization and Testing)
Data models in Angular 1 & 2
Connect.js - Exploring React.Native
JDD 2016 - Pawel Byszewski - Kotlin, why?
Hadoop Integration in Cassandra
mobl presentation @ IHomer
Intro to GraphQL on Android with Apollo DroidconNYC 2017
Android Automated Testing
Server Side Swift with Swag
JS Fest 2019. Glenn Reyes. With great power comes great React hooks!
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
Building DSLs with the Spoofax Language Workbench
IN4308 Lecture 3
Ad

Recently uploaded (20)

PDF
wealthsignaloriginal-com-DS-text-... (1).pdf
PDF
How to Choose the Right IT Partner for Your Business in Malaysia
PDF
Upgrade and Innovation Strategies for SAP ERP Customers
PPTX
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
PDF
Why TechBuilder is the Future of Pickup and Delivery App Development (1).pdf
PDF
Design an Analysis of Algorithms II-SECS-1021-03
PDF
SAP S4 Hana Brochure 3 (PTS SYSTEMS AND SOLUTIONS)
PDF
How Creative Agencies Leverage Project Management Software.pdf
PDF
EN-Survey-Report-SAP-LeanIX-EA-Insights-2025.pdf
PDF
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
PDF
Softaken Excel to vCard Converter Software.pdf
PDF
Adobe Premiere Pro 2025 (v24.5.0.057) Crack free
PDF
Internet Downloader Manager (IDM) Crack 6.42 Build 42 Updates Latest 2025
PDF
top salesforce developer skills in 2025.pdf
PPTX
ai tools demonstartion for schools and inter college
PDF
Digital Strategies for Manufacturing Companies
PPTX
Reimagine Home Health with the Power of Agentic AI​
PDF
medical staffing services at VALiNTRY
PDF
System and Network Administraation Chapter 3
PDF
Understanding Forklifts - TECH EHS Solution
wealthsignaloriginal-com-DS-text-... (1).pdf
How to Choose the Right IT Partner for Your Business in Malaysia
Upgrade and Innovation Strategies for SAP ERP Customers
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
Why TechBuilder is the Future of Pickup and Delivery App Development (1).pdf
Design an Analysis of Algorithms II-SECS-1021-03
SAP S4 Hana Brochure 3 (PTS SYSTEMS AND SOLUTIONS)
How Creative Agencies Leverage Project Management Software.pdf
EN-Survey-Report-SAP-LeanIX-EA-Insights-2025.pdf
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
Softaken Excel to vCard Converter Software.pdf
Adobe Premiere Pro 2025 (v24.5.0.057) Crack free
Internet Downloader Manager (IDM) Crack 6.42 Build 42 Updates Latest 2025
top salesforce developer skills in 2025.pdf
ai tools demonstartion for schools and inter college
Digital Strategies for Manufacturing Companies
Reimagine Home Health with the Power of Agentic AI​
medical staffing services at VALiNTRY
System and Network Administraation Chapter 3
Understanding Forklifts - TECH EHS Solution

The Workflow Pattern, Composed (2021)

  • 1. Zach Klippenstein The Workflow Pattern, Composed
  • 2. ● Android for 7+ years ● Square: Foundation, Design Systems ● Google: Compose ● Kotlin Slack: #compose Zach who?
  • 3. 1.Workflow 2.Jetpack Compose 3.Square + Compose 4.Workflow + Compose 5.Lessons learned 6.Current status
  • 4. 1.Workflow 2.Jetpack Compose 3.Square + Compose 4.Workflow + Compose 5.Lessons learned 6.Current status
  • 11. data class StockRendering( val price: String, val currency: String, val name: String ) Rendering/ Model View ViewFactory
  • 13. ● Injectability ● Reusability ● Testability ● Modularizability ● (View) customizability Advantages:
  • 14. 1.Workflow 2.Jetpack Compose 3.Square + Compose 4.Workflow + Compose 5.Lessons learned 6.Current status
  • 16. 1.Workflow 2.Jetpack Compose 3.Square + Compose 4.Workflow + Compose 5.Lessons learned 6.Current status
  • 17. Compose @ Square Workflow Integration Design System Features Infrastructure
  • 18. Compose @ Square Summer 2021 Winter 2021 Winter/Sprin g 2021 Summer 2020 Polish & land Workflow integration Design system initial release Initial design system infra and components, EAP Design system considering adoption, evaluation pilot Spring 2020 Prototyping Workflow integration Spring 2019 Jetpack Compose first announced Compose release timeline announced Compose 1.0 released (timeline not to scale)
  • 19. Compose @ Square Workflow Integration Design System Features Infrastructure
  • 20. 1.Workflow 2.Jetpack Compose 3.Square + Compose 4.Workflow + Compose 5.Lessons learned 6.Current status
  • 21. Providing a view for a rendering
  • 22. data class NameRendering( val name: String ) : AndroidViewRendering<NameRendering> { override val viewFactory: ViewFactory<NameRendering> get() = LayoutRunner.bind( layoutId = layout.rendering_layout, constructor = ::RenderingViewBinding ) }
  • 23. data class NameRendering( val name: String ) : AndroidViewRendering<NameRendering> { override val viewFactory: ViewFactory<NameRendering> get() = LayoutRunner.bind( layoutId = layout.rendering_layout, constructor = ::RenderingViewBinding ) }
  • 24. data class NameRendering( val name: String ) : AndroidViewRendering<NameRendering> { override val viewFactory: ViewFactory<NameRendering> get() = LayoutRunner.bind( layoutId = layout.rendering_layout, constructor = ::RenderingViewBinding ) }
  • 25. data class NameRendering( val name: String ) : AndroidViewRendering<NameRendering> { override val viewFactory: ViewFactory<NameRendering> get() = LayoutRunner.bind( layoutId = R.layout.rendering_layout, constructor = ::NameLayoutRunner ) }
  • 26. class NameLayoutRunner(view: View) : LayoutRunner<NameRendering> { private val nameView = view.findViewById<TextView>(R.id.name) override fun showRendering( rendering: NameRendering, viewEnvironment: ViewEnvironment ) { nameView.text = rendering.name } }
  • 27. class NameLayoutRunner(view: View) : LayoutRunner<NameRendering> { private val nameView = view.findViewById<TextView>(R.id.name) override fun showRendering( rendering: NameRendering, viewEnvironment: ViewEnvironment ) { nameView.text = rendering.name } }
  • 28. data class NameRendering( val name: String ) : AndroidViewRendering<NameRendering> { override val viewFactory: ViewFactory<NameRendering> get() = LayoutRunner.bind( layoutId = R.layout.rendering_layout, constructor = ::NameLayoutRunner ) } class NameLayoutRunner(view: View) : LayoutRunner<NameRendering> { private val nameView = view.findViewById<TextView>(R.id.name) override fun showRendering( rendering: NameRendering, viewEnvironment: ViewEnvironment ) { nameView.text = rendering.name } }
  • 29. data class NameRendering( val name: String ) : ComposeRendering { @Composable override fun Content(viewEnvironment: ViewEnvironment) { BasicText(name) } }
  • 30. data class NameRendering( val name: String ) : ComposeRendering { @Composable override fun Content(viewEnvironment: ViewEnvironment) { BasicText(name) } }
  • 31. data class NameRendering( val name: String ) : ComposeRendering { @Composable override fun Content(viewEnvironment: ViewEnvironment) { Text(name) } }
  • 32. interface ComposeRendering : AndroidViewRendering<Nothing> { override val viewFactory: ViewFactory<Nothing> get() = ComposeRenderingViewFactory @Composable fun Content(viewEnvironment: ViewEnvironment) }
  • 33. interface ComposeRendering : AndroidViewRendering<Nothing> { override val viewFactory: ViewFactory<Nothing> get() = ComposeRenderingViewFactory @Composable fun Content(viewEnvironment: ViewEnvironment) }
  • 34. interface ComposeRendering : AndroidViewRendering<Nothing> { override val viewFactory: ViewFactory<Nothing> get() = object : ComposeViewFactory<ComposeRendering>() { override val type: KClass<in ComposeRendering> = ComposeRendering::class @Composable override fun Content( rendering: ComposeRendering, viewEnvironment: ViewEnvironment ) { rendering.Content(viewEnvironment) } } }
  • 35. interface ComposeRendering : AndroidViewRendering<Nothing> { override val viewFactory: ViewFactory<Nothing> get() = object : ComposeViewFactory<ComposeRendering>() { @Composable override fun Content( rendering: ComposeRendering, viewEnvironment: ViewEnvironment ) { rendering.Content(viewEnvironment) } } }
  • 36. interface ComposeRendering : AndroidViewRendering<Nothing> { override val viewFactory: ViewFactory<Nothing> get() = object : ComposeViewFactory<ComposeRendering>() { @Composable override fun Content( rendering: ComposeRendering, viewEnvironment: ViewEnvironment ) { rendering.Content(viewEnvironment) } } }
  • 37. data class NameRendering( val name: String ) : ComposeRendering { @Composable override fun Content(viewEnvironment: ViewEnvironment) { Text(name) } }
  • 40. data class PersonRendering( val name: String, val details: Any ) : ComposeRendering { @Composable override fun Content(viewEnvironment: ViewEnvironment) { Column { BasicText(name) WorkflowRendering( details, viewEnvironment, Modifier.weight(1f) ) } } }
  • 41. data class PersonRendering( val name: String, val details: Any ) : ComposeRendering { @Composable override fun Content(viewEnvironment: ViewEnvironment) { Column { Text(name) WorkflowRendering( details, viewEnvironment, Modifier.weight(1f) ) } } }
  • 42. data class PersonRendering( val name: String, val details: Any ) : ComposeRendering { @Composable override fun Content(viewEnvironment: ViewEnvironment) { Column { Text(name) WorkflowRendering( details, viewEnvironment, Modifier.weight(1f) ) } } }
  • 43. data class PersonRendering( val name: String, val details: Any ) : ComposeRendering { @Composable override fun Content(viewEnvironment: ViewEnvironment) { Column { Text(name) WorkflowRendering( details, viewEnvironment, Modifier.weight(1f) ) } } }
  • 44. data class PersonRendering( val name: String, val details: Any ) : ComposeRendering { @Composable override fun Content(viewEnvironment: ViewEnvironment) { Column { Text(name) WorkflowRendering( details, viewEnvironment, Modifier.weight(1f) ) } } }
  • 45. @Composable fun WorkflowRendering( rendering: Any, viewEnvironment: ViewEnvironment, modifier: Modifier = Modifier ) { val renderingCompatibilityKey = Compatible.keyFor(rendering) key(renderingCompatibilityKey) { val viewFactory = remember { viewEnvironment[ViewRegistry] .getFactoryForRendering(rendering) .asComposeViewFactory() } val lifecycleOwner = rememberChildLifecycleOwner() CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) { Box(modifier, propagateMinConstraints = true) { viewFactory.Content(rendering, viewEnvironment) } } } }
  • 46. @Composable fun WorkflowRendering( rendering: Any, viewEnvironment: ViewEnvironment, modifier: Modifier = Modifier ) { val renderingCompatibilityKey = Compatible.keyFor(rendering) key(renderingCompatibilityKey) { val viewFactory: ComposeViewFactory<Any> = remember { viewEnvironment[ViewRegistry] .getFactoryForRendering(rendering) .asComposeViewFactory() } val lifecycleOwner = rememberChildLifecycleOwner() CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) { Box(modifier, propagateMinConstraints = true) { viewFactory.Content(rendering, viewEnvironment) } } } }
  • 47. @Composable fun WorkflowRendering( rendering: Any, viewEnvironment: ViewEnvironment, modifier: Modifier = Modifier ) { val renderingCompatibilityKey = Compatible.keyFor(rendering) key(renderingCompatibilityKey) { val viewFactory: ComposeViewFactory<Any> = remember { viewEnvironment[ViewRegistry] .getFactoryForRendering(rendering) .asComposeViewFactory() } val lifecycleOwner = rememberChildLifecycleOwner() CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) { Box(modifier, propagateMinConstraints = true) { viewFactory.Content(rendering, viewEnvironment) } } } }
  • 48. @Composable fun WorkflowRendering( rendering: Any, viewEnvironment: ViewEnvironment, modifier: Modifier = Modifier ) { val renderingCompatibilityKey = Compatible.keyFor(rendering) key(renderingCompatibilityKey) { val viewFactory: ComposeViewFactory<Any> = remember { viewEnvironment[ViewRegistry] .getFactoryForRendering(rendering) .asComposeViewFactory() } val lifecycleOwner = rememberChildLifecycleOwner() CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) { Box(modifier, propagateMinConstraints = true) { viewFactory.Content(rendering, viewEnvironment) } } } }
  • 51. @Composable fun WorkflowRendering( rendering: Any, viewEnvironment: ViewEnvironment, modifier: Modifier = Modifier ) { val renderingCompatibilityKey = Compatible.keyFor(rendering) key(renderingCompatibilityKey) { val viewFactory: ComposeViewFactory<Any> = remember { viewEnvironment[ViewRegistry] .getFactoryForRendering(rendering) .asComposeViewFactory() } val lifecycleOwner = rememberChildLifecycleOwner() CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) { Box(modifier, propagateMinConstraints = true) { viewFactory.Content(rendering, viewEnvironment) } } } }
  • 52. @Composable fun WorkflowRendering( rendering: Any, viewEnvironment: ViewEnvironment, modifier: Modifier = Modifier ) { val renderingCompatibilityKey = Compatible.keyFor(rendering) key(renderingCompatibilityKey) { val viewFactory: ComposeViewFactory<Any> = remember { viewEnvironment[ViewRegistry] .getFactoryForRendering(rendering) .asComposeViewFactory() } val lifecycleOwner = rememberChildLifecycleOwner() CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) { Box(modifier, propagateMinConstraints = true) { viewFactory.Content(rendering, viewEnvironment) } } } }
  • 53. @Composable fun WorkflowRendering( rendering: Any, viewEnvironment: ViewEnvironment, modifier: Modifier = Modifier ) { val renderingCompatibilityKey = Compatible.keyFor(rendering) key(renderingCompatibilityKey) { val viewFactory: ComposeViewFactory<Any> = remember { viewEnvironment[ViewRegistry] .getFactoryForRendering(rendering) .asComposeViewFactory() } val lifecycleOwner = rememberChildLifecycleOwner() CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) { Box(modifier, propagateMinConstraints = true) { viewFactory.Content(rendering, viewEnvironment) } } } }
  • 54. @Composable fun WorkflowRendering( rendering: Any, viewEnvironment: ViewEnvironment, modifier: Modifier = Modifier ) { val renderingCompatibilityKey = rendering::class key(renderingCompatibilityKey) { val viewFactory: ComposeViewFactory<Any> = remember { viewEnvironment[ViewRegistry] .getFactoryForRendering(rendering) .asComposeViewFactory() } val lifecycleOwner = rememberChildLifecycleOwner() CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) { Box(modifier, propagateMinConstraints = true) { viewFactory.Content(rendering, viewEnvironment) } } } }
  • 55. @Composable fun WorkflowRendering( rendering: Any, viewEnvironment: ViewEnvironment, modifier: Modifier = Modifier ) { val renderingCompatibilityKey = Compatible.keyFor(rendering) key(renderingCompatibilityKey) { val viewFactory: ComposeViewFactory<Any> = remember { viewEnvironment[ViewRegistry] .getFactoryForRendering(rendering) .asComposeViewFactory() } val lifecycleOwner = rememberChildLifecycleOwner() CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) { Box(modifier, propagateMinConstraints = true) { viewFactory.Content(rendering, viewEnvironment) } } } }
  • 56. @Composable fun WorkflowRendering( rendering: Any, viewEnvironment: ViewEnvironment, modifier: Modifier = Modifier ) { val renderingCompatibilityKey = Compatible.keyFor(rendering) key(renderingCompatibilityKey) { val viewFactory: ComposeViewFactory<Any> = remember(renderingCompatibilityKey) { viewEnvironment[ViewRegistry] .getFactoryForRendering(rendering) .asComposeViewFactory() } val lifecycleOwner = rememberChildLifecycleOwner() CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) { Box(modifier, propagateMinConstraints = true) { viewFactory.Content(rendering, viewEnvironment) } } } }
  • 57. @Composable fun WorkflowRendering( rendering: Any, viewEnvironment: ViewEnvironment, modifier: Modifier = Modifier ) { val renderingCompatibilityKey = Compatible.keyFor(rendering) key(renderingCompatibilityKey) { val viewFactory: ComposeViewFactory<Any> = remember { viewEnvironment[ViewRegistry] .getFactoryForRendering(rendering) .asComposeViewFactory() } val lifecycleOwner = rememberChildLifecycleOwner() CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) { Box(modifier, propagateMinConstraints = true) { viewFactory.Content(rendering, viewEnvironment) } } } }
  • 58. @Composable fun WorkflowRendering( rendering: Any, viewEnvironment: ViewEnvironment, modifier: Modifier = Modifier ) { val renderingCompatibilityKey = Compatible.keyFor(rendering) key(renderingCompatibilityKey) { val viewFactory: ComposeViewFactory<Any> = remember { viewEnvironment[ViewRegistry] .getFactoryForRendering(rendering) .asComposeViewFactory() } val lifecycleOwner = rememberChildLifecycleOwner() CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) { Box(modifier, propagateMinConstraints = true) { viewFactory.Content(rendering, viewEnvironment) } } } }
  • 59. /** * Renders [rendering] into the composition using this [ViewEnvironment]'s [ViewRegistry] to * generate the view. * * This function fulfills 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 modifier A [Modifier] that will be applied to composable used to show [rendering]. * * @throws IllegalArgumentException if no factory can be found for [rendering]'s type. */ @WorkflowUiExperimentalApi @Composable public fun WorkflowRendering( rendering: Any, viewEnvironment: ViewEnvironment, modifier: Modifier = Modifier ) { // 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 modifier passed // into this function is to directly control the layout of the child view – which means // minimum constraints are likely to be significant. Box(modifier, propagateMinConstraints = true) { viewFactory.Content(rendering, viewEnvironment) } } } }
  • 63. interface ViewFactory<in RenderingT : Any> { val type: KClass<in RenderingT> fun buildView( initialRendering: RenderingT, initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? = null ): View }
  • 64. interface ViewFactory<in RenderingT : Any> { fun buildView( initialRendering: RenderingT, initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? = null ): View }
  • 65. interface ViewFactory<in RenderingT : Any> { fun buildView( initialRendering: RenderingT, initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? = null ): View }
  • 66. interface ViewFactory<in RenderingT : Any> { fun buildView( initialRendering: RenderingT, initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? = null ): View }
  • 67. interface ViewFactory<in RenderingT : Any> { fun buildView( initialRendering: RenderingT, initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? = null ): View }
  • 68. interface ViewFactory<in RenderingT : Any> { fun buildView( initialRendering: RenderingT, initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? = null ): View }
  • 69. abstract class ComposeViewFactory<RenderingT : Any> : ViewFactory<RenderingT> { @Composable abstract fun Content( rendering: RenderingT, viewEnvironment: ViewEnvironment ) final 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) } } } }
  • 70. abstract class ComposeViewFactory<RenderingT : Any> : ViewFactory<RenderingT> { @Composable abstract fun Content( rendering: RenderingT, viewEnvironment: ViewEnvironment ) final 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) } } } }
  • 71. abstract class ComposeViewFactory<RenderingT : Any> : ViewFactory<RenderingT> { @Composable abstract fun Content( rendering: RenderingT, viewEnvironment: ViewEnvironment ) final 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) } } } }
  • 72. abstract class ComposeViewFactory<RenderingT : Any> : ViewFactory<RenderingT> { @Composable abstract fun Content( rendering: RenderingT, viewEnvironment: ViewEnvironment ) final 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) } } } }
  • 73. abstract class ComposeViewFactory<RenderingT : Any> : ViewFactory<RenderingT> { @Composable abstract fun Content( rendering: RenderingT, viewEnvironment: ViewEnvironment ) final 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) } } } }
  • 74. abstract class ComposeViewFactory<RenderingT : Any> : ViewFactory<RenderingT> { @Composable abstract fun Content( rendering: RenderingT, viewEnvironment: ViewEnvironment ) final 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) } } } }
  • 75. abstract class ComposeViewFactory<RenderingT : Any> : ViewFactory<RenderingT> { @Composable abstract fun Content( rendering: RenderingT, viewEnvironment: ViewEnvironment ) final 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) } } } }
  • 76. abstract class ComposeViewFactory<RenderingT : Any> : ViewFactory<RenderingT> { @Composable abstract fun Content( rendering: RenderingT, viewEnvironment: ViewEnvironment ) final 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) } } } }
  • 77. interface ComposeRendering : AndroidViewRendering<Nothing> { override val viewFactory: ViewFactory<Nothing> get() = object : ComposeViewFactory<ComposeRendering>() { @Composable override fun Content( rendering: ComposeRendering, viewEnvironment: ViewEnvironment ) { rendering.Content(viewEnvironment) } } }
  • 78. data class PersonRendering( val name: String, val details: Any ) : ComposeRendering { @Composable override fun Content(viewEnvironment: ViewEnvironment) { Column { BasicText(name) WorkflowRendering( details, viewEnvironment, Modifier.weight(1f) ) } } }
  • 79. data class PersonRendering( val name: String, val details: Any ) object PersonViewFactory : ComposeViewFactory<PersonRendering>() { @Composable override fun Content( rendering: PersonRendering, viewEnvironment: ViewEnvironment ) { Column { BasicText(rendering.name) WorkflowRendering( rendering.details, viewEnvironment, Modifier.weight(1f) ) } } }
  • 80. data class PersonRendering( val name: String, val details: Any ) val personViewFactory: ViewFactory<PersonRendering> = composeViewFactory { rendering, viewEnvironment -> Column { BasicText(rendering.name) WorkflowRendering( rendering.details, viewEnvironment, Modifier.weight(1f) ) } }
  • 82. @Composable fun WorkflowRendering( rendering: Any, viewEnvironment: ViewEnvironment, modifier: Modifier = Modifier ) { val renderingCompatibilityKey = Compatible.keyFor(rendering) key(renderingCompatibilityKey) { val viewFactory: ComposeViewFactory<Any> = remember { viewEnvironment[ViewRegistry] .getFactoryForRendering(rendering) .asComposeViewFactory() } val lifecycleOwner = rememberChildLifecycleOwner() CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) { Box(modifier, propagateMinConstraints = true) { viewFactory.Content(rendering, viewEnvironment) } } } }
  • 84. CoMpOsAbLeS iN vIeWs Views in Composables?
  • 85. fun <R : Any> ViewFactory<R>.asComposeViewFactory(): ComposeViewFactory<R> = (this as? ComposeViewFactory) ?: object : ComposeViewFactory<R>() { private val originalFactory = this@asComposeViewFactory override val type: KClass<in R> get() = originalFactory.type @Composable override fun Content( rendering: R, viewEnvironment: ViewEnvironment ) { AndroidView( factory = { context -> originalFactory.buildView(rendering, viewEnvironment, context, container = null) .also { view -> checkNotNull(view.getShowRendering<Any>()) { "View.bindShowRendering should have been called for $view" } } }, update = { view -> view.showRendering(rendering, viewEnvironment) } ) } }
  • 86. fun <R : Any> ViewFactory<R>.asComposeViewFactory(): ComposeViewFactory<R> = (this as? ComposeViewFactory) ?: object : ComposeViewFactory<R>() { private val originalFactory = this@asComposeViewFactory override val type: KClass<in R> get() = originalFactory.type @Composable override fun Content( rendering: R, viewEnvironment: ViewEnvironment ) { AndroidView( factory = { context -> originalFactory.buildView(rendering, viewEnvironment, context, container = null) .also { view -> checkNotNull(view.getShowRendering<Any>()) { "View.bindShowRendering should have been called for $view" } } }, update = { view -> view.showRendering(rendering, viewEnvironment) } ) } }
  • 87. val color = composeViewFactory<Color> { rendering, _ -> Text(rendering.toString()) } val red = composeViewFactory<Unit> { _, viewEnvironment -> Row { Text("Red: ") WorkflowRendering(Color.Red, viewEnvironment) } }
  • 88. val color = composeViewFactory<Color> { rendering, _ -> Text(rendering.toString()) } val red = composeViewFactory<Unit> { _, viewEnvironment -> Row { Text("Red: ") val renderingCompatibilityKey = Compatible.keyFor(Color.Red) key(renderingCompatibilityKey) { val lifecycleOwner = rememberChildLifecycleOwner() CompositionLocalProvider(LocalLifecycleOwner provides lifecycleOwner) { Box(propagateMinConstraints = true) { color.Content(Color.Red, viewEnvironment) } } } } }
  • 89. fun <R : Any> ViewFactory<R>.asComposeViewFactory(): ComposeViewFactory<R> = (this as? ComposeViewFactory) ?: object : ComposeViewFactory<R>() { private val originalFactory = this@asComposeViewFactory override val type: KClass<in R> get() = originalFactory.type @Composable override fun Content( rendering: R, viewEnvironment: ViewEnvironment ) { AndroidView( factory = { context -> originalFactory.buildView(rendering, viewEnvironment, context, container = null) .also { view -> checkNotNull(view.getShowRendering<Any>()) { "View.bindShowRendering should have been called for $view" } } }, update = { view -> view.showRendering(rendering, viewEnvironment) } ) } }
  • 90. fun <R : Any> ViewFactory<R>.asComposeViewFactory(): ComposeViewFactory<R> = (this as? ComposeViewFactory) ?: object : ComposeViewFactory<R>() { private val originalFactory = this@asComposeViewFactory override val type: KClass<in R> get() = originalFactory.type @Composable override fun Content( rendering: R, viewEnvironment: ViewEnvironment ) { AndroidView( factory = { context -> originalFactory.buildView(rendering, viewEnvironment, context, container = null) .also { view -> checkNotNull(view.getShowRendering<Any>()) { "View.bindShowRendering should have been called for $view" } } }, update = { view -> view.showRendering(rendering, viewEnvironment) } ) } }
  • 91. 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, container = null) .also { view -> checkNotNull(view.getShowRendering<Any>()) { "View.bindShowRendering should have been called for $view" } } }, update = { view -> view.showRendering(rendering, viewEnvironment) } ) } }
  • 92. 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, container = null) .also { view -> checkNotNull(view.getShowRendering<Any>()) { "View.bindShowRendering should have been called for $view" } } }, update = { view -> view.showRendering(rendering, viewEnvironment) } ) } }
  • 93. 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, container = null) .also { view -> checkNotNull(view.getShowRendering<Any>()) { "View.bindShowRendering should have been called for $view" } } }, update = { view -> view.showRendering(rendering, viewEnvironment) } ) } }
  • 94. 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) .also { view -> checkNotNull(view.getShowRendering<Any>()) { "View.bindShowRendering should have been called for $view" } } }, update = { view -> view.showRendering(rendering, viewEnvironment) } ) } }
  • 95. 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) .also { view -> checkNotNull(view.getShowRendering<Any>()) { "View.bindShowRendering should have been called for $view" } } }, update = { view -> view.showRendering(rendering, viewEnvironment) } ) } } final 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) } } }
  • 96. 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) .also { view -> checkNotNull(view.getShowRendering<Any>()) { "View.bindShowRendering should have been called for $view" } } }, update = { view -> view.showRendering(rendering, viewEnvironment) } ) } }
  • 97. 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) .also { view -> checkNotNull(view.getShowRendering<Any>()) { "View.bindShowRendering should have been called for $view" } } }, update = { view -> view.showRendering(rendering, viewEnvironment) } ) } }
  • 98. 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) .also { view -> checkNotNull(view.getShowRendering<Any>()) { "View.bindShowRendering should have been called for $view" }w, lifecycleOwner) } }, update = { view -> view.showRendering(rendering, viewEnvironment) } ) } }
  • 99. 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) .also { view -> checkNotNull(view.getShowRendering<Any>()) { "View.bindShowRendering should have been called for $view" } } }, update = { view -> view.showRendering(rendering, viewEnvironment) } ) } }
  • 101. View-based ViewFactory View-based ViewFactory Compose-based ViewFactory View-based ViewFactory Compose-based ViewFactory
  • 103. 1.Workflow 2.Jetpack Compose 3.Square + Compose 4.Workflow + Compose 5.Lessons learned 6.Current status
  • 104. ● More flexible than previous hooks ● Gotcha: root views (e.g. modals) ● Only a pattern ViewTree*Owners… 😐
  • 105. ● Multiple mechanisms ○ View onSaveInstanceState/onRestoreInstanceState ○ AndroidX SavedStateRegistry ○ Compose SaveableStateRegistry ● IDs ● Lifecycle-sensitive Implementing view state restoration is hard
  • 106. ● Complicated implementation, simple API ● Very buggy back in early 2020 Compose / View interop
  • 107. 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) } } }
  • 108. abstract class ComposeViewFactory<RenderingT : Any> : ViewFactory<RenderingT> { @Composable abstract fun Content( rendering: RenderingT, viewEnvironment: ViewEnvironment ) final 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) } } } }
  • 110. ● But there are benefits if you're willing to invest Writing a navigation library is hard
  • 111. 1.Workflow 2.Jetpack Compose 3.Square + Compose 4.Workflow + Compose 5.Lessons learned 6.Current status
  • 112. ● Design system Compose components almost done ● Integration with internal app scaffolding ● Samples ● Will start using for features soon Current status
  • 114. Zach Klippenstein / twitter.com/zachklipp kotlinlang.slack.com #squarelibraries github.com/square/workflow developer.android.com/jetpack/compose bit.ly/workflow-compose-blog Thank you! Questions?