SlideShare a Scribd company logo
1 of 115
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
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
@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
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

What's hot

Kandroid for nhn_deview_20131013_v5_final
Kandroid for nhn_deview_20131013_v5_finalKandroid for nhn_deview_20131013_v5_final
Kandroid for nhn_deview_20131013_v5_final
NAVER D2
 
GR8Conf 2011: Adopting Grails
GR8Conf 2011: Adopting GrailsGR8Conf 2011: Adopting Grails
GR8Conf 2011: Adopting Grails
GR8Conf
 
Single Page Web Applications with CoffeeScript, Backbone and Jasmine
Single Page Web Applications with CoffeeScript, Backbone and JasmineSingle Page Web Applications with CoffeeScript, Backbone and Jasmine
Single Page Web Applications with CoffeeScript, Backbone and Jasmine
Paulo Ragonha
 

What's hot (20)

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

Similar to The Workflow Pattern, Composed (2021)

Functional programming using underscorejs
Functional programming using underscorejsFunctional programming using underscorejs
Functional programming using underscorejs
偉格 高
 
mobl presentation @ IHomer
mobl presentation @ IHomermobl presentation @ IHomer
mobl presentation @ IHomer
zefhemel
 
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfpragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
Hiroshi Ono
 
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfpragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
Hiroshi Ono
 
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfpragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
Hiroshi Ono
 
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfpragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
Hiroshi Ono
 

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

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

Recently uploaded

+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
Health
 
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
masabamasaba
 
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
masabamasaba
 
Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...
Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...
Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...
Medical / Health Care (+971588192166) Mifepristone and Misoprostol tablets 200mg
 
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM TechniquesAI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
VictorSzoltysek
 
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
masabamasaba
 
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
9953056974 Low Rate Call Girls In Saket, Delhi NCR
 

Recently uploaded (20)

%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
 
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
 
VTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnVTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learn
 
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
 
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
 
Right Money Management App For Your Financial Goals
Right Money Management App For Your Financial GoalsRight Money Management App For Your Financial Goals
Right Money Management App For Your Financial Goals
 
%in Soweto+277-882-255-28 abortion pills for sale in soweto
%in Soweto+277-882-255-28 abortion pills for sale in soweto%in Soweto+277-882-255-28 abortion pills for sale in soweto
%in Soweto+277-882-255-28 abortion pills for sale in soweto
 
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
 
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdfPayment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
 
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
 
%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein
%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein
%in kaalfontein+277-882-255-28 abortion pills for sale in kaalfontein
 
AI & Machine Learning Presentation Template
AI & Machine Learning Presentation TemplateAI & Machine Learning Presentation Template
AI & Machine Learning Presentation Template
 
tonesoftg
tonesoftgtonesoftg
tonesoftg
 
Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...
Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...
Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...
 
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM TechniquesAI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
 
WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...
WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...
WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...
 
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
 
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
 
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
 

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
  • 15.
  • 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) } } } }
  • 50.
  • 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
  • 102.
  • 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?