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

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"Fwdays
 
iOSDC 2018 動画をなめらかに動かす技術
iOSDC 2018 動画をなめらかに動かす技術iOSDC 2018 動画をなめらかに動かす技術
iOSDC 2018 動画をなめらかに動かす技術Yuji Hato
 
A Tour of PostgREST
A Tour of PostgRESTA Tour of PostgREST
A Tour of PostgRESTbegriffs
 
vJUG - The JavaFX Ecosystem
vJUG - The JavaFX EcosystemvJUG - The JavaFX Ecosystem
vJUG - The JavaFX EcosystemAndres Almiray
 
Scripting Oracle Develop 2007
Scripting Oracle Develop 2007Scripting Oracle Develop 2007
Scripting Oracle Develop 2007Tugdual Grall
 
REST API に疲れたあなたへ贈る GraphQL 入門
REST API に疲れたあなたへ贈る GraphQL 入門REST API に疲れたあなたへ贈る GraphQL 入門
REST API に疲れたあなたへ贈る GraphQL 入門Keisuke Tsukagoshi
 
Developing web applications in 2010
Developing web applications in 2010Developing web applications in 2010
Developing web applications in 2010Ignacio Coloma
 
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_finalNAVER D2
 
점진적인 레거시 웹 애플리케이션 개선 과정
점진적인 레거시 웹 애플리케이션 개선 과정점진적인 레거시 웹 애플리케이션 개선 과정
점진적인 레거시 웹 애플리케이션 개선 과정Arawn Park
 
Introduction to Ruby on Rails
Introduction to Ruby on RailsIntroduction to Ruby on Rails
Introduction to Ruby on RailsAgnieszka Figiel
 
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)Yevgeniy Brikman
 
Aligning Ember.js with Web Standards
Aligning Ember.js with Web StandardsAligning Ember.js with Web Standards
Aligning Ember.js with Web StandardsMatthew Beale
 
GR8Conf 2011: Adopting Grails
GR8Conf 2011: Adopting GrailsGR8Conf 2011: Adopting Grails
GR8Conf 2011: Adopting GrailsGR8Conf
 
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 youAlexander Casall
 
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 JasminePaulo Ragonha
 
Flavors of Concurrency in Java
Flavors of Concurrency in JavaFlavors of Concurrency in Java
Flavors of Concurrency in JavaJavaDayUA
 

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偉格 高
 
Groovy On Trading Desk (2010)
Groovy On Trading Desk (2010)Groovy On Trading Desk (2010)
Groovy On Trading Desk (2010)Jonathan Felch
 
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)Yongjun Kim
 
Data models in Angular 1 & 2
Data models in Angular 1 & 2Data models in Angular 1 & 2
Data models in Angular 1 & 2Adam Klein
 
Connect.js - Exploring React.Native
Connect.js - Exploring React.NativeConnect.js - Exploring React.Native
Connect.js - Exploring React.Nativejoshcjensen
 
JDD 2016 - Pawel Byszewski - Kotlin, why?
JDD 2016 - Pawel Byszewski - Kotlin, why?JDD 2016 - Pawel Byszewski - Kotlin, why?
JDD 2016 - Pawel Byszewski - Kotlin, why?PROIDEA
 
Hadoop Integration in Cassandra
Hadoop Integration in CassandraHadoop Integration in Cassandra
Hadoop Integration in CassandraJairam Chandar
 
mobl presentation @ IHomer
mobl presentation @ IHomermobl presentation @ IHomer
mobl presentation @ IHomerzefhemel
 
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 2017Mike Nakhimovich
 
Android Automated Testing
Android Automated TestingAndroid Automated Testing
Android Automated Testingroisagiv
 
Server Side Swift with Swag
Server Side Swift with SwagServer Side Swift with Swag
Server Side Swift with SwagJens Ravens
 
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!JSFestUA
 
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfpragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfHiroshi Ono
 
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfpragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfHiroshi Ono
 
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfpragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfHiroshi Ono
 
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfpragmaticrealworldscalajfokus2009-1233251076441384-2.pdf
pragmaticrealworldscalajfokus2009-1233251076441384-2.pdfHiroshi Ono
 
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 WorkbenchEelco Visser
 

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

(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...gurkirankumar98700
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software DevelopersVinodh Ram
 
Intelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmIntelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmSujith Sukumaran
 
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...OnePlan Solutions
 
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdf
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdfGOING AOT WITH GRAALVM – DEVOXX GREECE.pdf
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdfAlina Yurenko
 
chapter--4-software-project-planning.ppt
chapter--4-software-project-planning.pptchapter--4-software-project-planning.ppt
chapter--4-software-project-planning.pptkotipi9215
 
Salesforce Certified Field Service Consultant
Salesforce Certified Field Service ConsultantSalesforce Certified Field Service Consultant
Salesforce Certified Field Service ConsultantAxelRicardoTrocheRiq
 
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...stazi3110
 
software engineering Chapter 5 System modeling.pptx
software engineering Chapter 5 System modeling.pptxsoftware engineering Chapter 5 System modeling.pptx
software engineering Chapter 5 System modeling.pptxnada99848
 
MYjobs Presentation Django-based project
MYjobs Presentation Django-based projectMYjobs Presentation Django-based project
MYjobs Presentation Django-based projectAnoyGreter
 
Implementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with AzureImplementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with AzureDinusha Kumarasiri
 
EY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityEY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityNeo4j
 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...MyIntelliSource, Inc.
 
Folding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a seriesFolding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a seriesPhilip Schwarz
 
Asset Management Software - Infographic
Asset Management Software - InfographicAsset Management Software - Infographic
Asset Management Software - InfographicHr365.us smith
 
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024StefanoLambiase
 
Unveiling Design Patterns: A Visual Guide with UML Diagrams
Unveiling Design Patterns: A Visual Guide with UML DiagramsUnveiling Design Patterns: A Visual Guide with UML Diagrams
Unveiling Design Patterns: A Visual Guide with UML DiagramsAhmed Mohamed
 
Cloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackCloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackVICTOR MAESTRE RAMIREZ
 
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEBATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEOrtus Solutions, Corp
 
What is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need ItWhat is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need ItWave PLM
 

Recently uploaded (20)

(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
(Genuine) Escort Service Lucknow | Starting ₹,5K To @25k with A/C 🧑🏽‍❤️‍🧑🏻 89...
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software Developers
 
Intelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmIntelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalm
 
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...
 
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdf
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdfGOING AOT WITH GRAALVM – DEVOXX GREECE.pdf
GOING AOT WITH GRAALVM – DEVOXX GREECE.pdf
 
chapter--4-software-project-planning.ppt
chapter--4-software-project-planning.pptchapter--4-software-project-planning.ppt
chapter--4-software-project-planning.ppt
 
Salesforce Certified Field Service Consultant
Salesforce Certified Field Service ConsultantSalesforce Certified Field Service Consultant
Salesforce Certified Field Service Consultant
 
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
 
software engineering Chapter 5 System modeling.pptx
software engineering Chapter 5 System modeling.pptxsoftware engineering Chapter 5 System modeling.pptx
software engineering Chapter 5 System modeling.pptx
 
MYjobs Presentation Django-based project
MYjobs Presentation Django-based projectMYjobs Presentation Django-based project
MYjobs Presentation Django-based project
 
Implementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with AzureImplementing Zero Trust strategy with Azure
Implementing Zero Trust strategy with Azure
 
EY_Graph Database Powered Sustainability
EY_Graph Database Powered SustainabilityEY_Graph Database Powered Sustainability
EY_Graph Database Powered Sustainability
 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
 
Folding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a seriesFolding Cheat Sheet #4 - fourth in a series
Folding Cheat Sheet #4 - fourth in a series
 
Asset Management Software - Infographic
Asset Management Software - InfographicAsset Management Software - Infographic
Asset Management Software - Infographic
 
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
Dealing with Cultural Dispersion — Stefano Lambiase — ICSE-SEIS 2024
 
Unveiling Design Patterns: A Visual Guide with UML Diagrams
Unveiling Design Patterns: A Visual Guide with UML DiagramsUnveiling Design Patterns: A Visual Guide with UML Diagrams
Unveiling Design Patterns: A Visual Guide with UML Diagrams
 
Cloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackCloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStack
 
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEBATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
 
What is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need ItWhat is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need It
 

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?