Skip to content

Commit 4211c0c

Browse files
committed
BREAKING: Replaces ComposeScreenViewFactory with ScreenComposableFactory
This commit takes advantage of the new `ViewRegistry.Key` (which allows renderings to be bound to multiple UI factories) to fix a long standing problem where wrapper screens -- things like `NamedScreen` and `EnvironmentScreen` -- used in a Compose context would cause needless calls to `@Composeable fun AndroidView()` and `ComposeView()`. For example, consider this rendering: ``` BodyAndOverlaysScreen( body = SomeComposeScreen( EnvironmentScreen( SomeOtherComposeScreen ) ) ) ``` Before this change, that would create a View hierarchy something like this: ``` BodyAndOverlaysContainer : FrameLayout { mChildren[0] = ComposeView { // compose land SomeComposeScreen.Content { AndroidView { ComposeView { // nested compose land SomeOtherComposeScreen.Content() ``` Now it will look this way: ``` BodyAndOverlaysContainer : FrameLayout { mChildren[0] = ComposeView { // compose land SomeComposeScreen.Content { SomeOtherComposeScreen.Content() ``` `ScreenComposableFactory` replaces `ComposeScreenViewFactory`, and `ComposeScreen` no longer extends `AndroidScreen`. Compose support is now a first class citizen, instead of a hack bolted on to View support. Unfortunately, `ViewEnvironment.withComposeInteropSupport()` (see below) now must be called near the root of a Workflow app to enable the seamless Compose > Classic > Compose handling that used to be built in to `WorkflowRendering` and `ComposeScreenViewFactory`. This means that call is required for Compose support for built in rendering types like `BodyAndOverlaysScreen` and `BackStackScreen`, which so far are backed only by classic View implementations. Other introductions, changes: - `Screen.toComposableFactory()`, used by `WorkflowRendering()` in the same way that `WorkflowViewStub` uses `Screen.toViewFactory()` - `ScreenComposableFactoryFinder`, a `ViewEnvironment`-based strategy object used by `Screen.toComposableFactory()` the same way that `Screen.toViewFactory()` uses `ScreenViewFactoryFinder`. The default implementation provides Compose bindings for `NamedScreen` and `EnvironmentScreen`, fixing #546. - `ScreenViewFactoryFinder.getViewFactoryForRendering()` can now return `null`. A `requireViewFactoryForRendering()` extension is introduced for use when `null` is not acceptable. - `ViewEnvironment.withComposeInteropSupport()`, which wraps the found `ScreenComposableFactoryFinder` and `ScreenViewFactoryFinder` with implementations that allow Compose contexts to handle renderings bound only to `ScreenViewFactory`, and classic contexts to handle renderings bound only to `ScreenComposableFactory`. Replaces the logic that used to be in the private `ScreenViewFactory.asComposeViewFactory()` extension in `WorkflowRendering()`. - `Screen.Preview()` is introduced. The existing `Preview()` extension functions were tied to `ScreenViewFactory`, making them much less useful. It is still the case that previews work for non-Compose UI code just fine. Which is pretty cool, really. Fixes #546
1 parent 09bf09f commit 4211c0c

File tree

41 files changed

+1081
-839
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1081
-839
lines changed

samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocompose/App.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,16 @@ import androidx.compose.ui.unit.dp
1414
import com.squareup.workflow1.WorkflowExperimentalRuntime
1515
import com.squareup.workflow1.config.AndroidRuntimeConfigTools
1616
import com.squareup.workflow1.ui.ViewEnvironment
17-
import com.squareup.workflow1.ui.ViewRegistry
1817
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
1918
import com.squareup.workflow1.ui.compose.WorkflowRendering
2019
import com.squareup.workflow1.ui.compose.renderAsState
21-
import com.squareup.workflow1.ui.plus
20+
import com.squareup.workflow1.ui.compose.withComposeInteropSupport
2221

23-
private val viewEnvironment = ViewEnvironment.EMPTY + ViewRegistry(HelloBinding)
22+
private val viewEnvironment = ViewEnvironment.EMPTY.withComposeInteropSupport()
2423

2524
@Composable fun App() {
2625
MaterialTheme {
27-
val rendering by HelloWorkflow.renderAsState(
26+
val rendering by HelloComposeWorkflow.renderAsState(
2827
props = Unit,
2928
runtimeConfig = AndroidRuntimeConfigTools.getAppWorkflowRuntimeConfig(),
3029
onOutput = {}

samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocompose/HelloBinding.kt

Lines changed: 0 additions & 22 deletions
This file was deleted.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.squareup.sample.compose.hellocompose
2+
3+
import androidx.compose.foundation.clickable
4+
import androidx.compose.foundation.layout.fillMaxSize
5+
import androidx.compose.foundation.layout.wrapContentSize
6+
import androidx.compose.material.Text
7+
import androidx.compose.runtime.Composable
8+
import androidx.compose.ui.Alignment
9+
import androidx.compose.ui.Modifier
10+
import androidx.compose.ui.tooling.preview.Preview
11+
import com.squareup.workflow1.ui.ViewEnvironment
12+
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
13+
import com.squareup.workflow1.ui.compose.ComposeScreen
14+
import com.squareup.workflow1.ui.compose.tooling.Preview
15+
16+
@OptIn(WorkflowUiExperimentalApi::class)
17+
data class HelloComposeScreen(
18+
val message: String,
19+
val onClick: () -> Unit
20+
) : ComposeScreen {
21+
@Composable override fun Content(viewEnvironment: ViewEnvironment) {
22+
Text(
23+
message,
24+
modifier = Modifier
25+
.clickable(onClick = onClick)
26+
.fillMaxSize()
27+
.wrapContentSize(Alignment.Center)
28+
)
29+
}
30+
}
31+
32+
@OptIn(WorkflowUiExperimentalApi::class)
33+
@Preview(heightDp = 150, showBackground = true)
34+
@Composable
35+
private fun HelloPreview() {
36+
HelloComposeScreen(
37+
"Hello!",
38+
onClick = {}
39+
).Preview()
40+
}

samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocompose/HelloWorkflow.kt renamed to samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocompose/HelloComposeWorkflow.kt

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
package com.squareup.sample.compose.hellocompose
22

3-
import com.squareup.sample.compose.hellocompose.HelloWorkflow.Rendering
4-
import com.squareup.sample.compose.hellocompose.HelloWorkflow.State
5-
import com.squareup.sample.compose.hellocompose.HelloWorkflow.State.Goodbye
6-
import com.squareup.sample.compose.hellocompose.HelloWorkflow.State.Hello
3+
import com.squareup.sample.compose.hellocompose.HelloComposeWorkflow.State
4+
import com.squareup.sample.compose.hellocompose.HelloComposeWorkflow.State.Goodbye
5+
import com.squareup.sample.compose.hellocompose.HelloComposeWorkflow.State.Hello
76
import com.squareup.workflow1.Snapshot
87
import com.squareup.workflow1.StatefulWorkflow
98
import com.squareup.workflow1.action
109
import com.squareup.workflow1.parse
11-
import com.squareup.workflow1.ui.Screen
12-
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
1310

14-
object HelloWorkflow : StatefulWorkflow<Unit, State, Nothing, Rendering>() {
11+
object HelloComposeWorkflow : StatefulWorkflow<Unit, State, Nothing, HelloComposeScreen>() {
1512
enum class State {
1613
Hello,
1714
Goodbye;
@@ -22,12 +19,6 @@ object HelloWorkflow : StatefulWorkflow<Unit, State, Nothing, Rendering>() {
2219
}
2320
}
2421

25-
@OptIn(WorkflowUiExperimentalApi::class)
26-
data class Rendering(
27-
val message: String,
28-
val onClick: () -> Unit
29-
) : Screen
30-
3122
private val helloAction = action {
3223
state = state.theOtherState()
3324
}
@@ -42,7 +33,7 @@ object HelloWorkflow : StatefulWorkflow<Unit, State, Nothing, Rendering>() {
4233
renderProps: Unit,
4334
renderState: State,
4435
context: RenderContext
45-
): Rendering = Rendering(
36+
): HelloComposeScreen = HelloComposeScreen(
4637
message = renderState.name,
4738
onClick = { context.actionSink.send(helloAction) }
4839
)

samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocomposebinding/HelloBinding.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ import androidx.compose.ui.Modifier
99
import androidx.compose.ui.tooling.preview.Preview
1010
import com.squareup.sample.compose.hellocomposebinding.HelloWorkflow.Rendering
1111
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
12-
import com.squareup.workflow1.ui.compose.composeScreenViewFactory
12+
import com.squareup.workflow1.ui.compose.ScreenComposableFactory
1313
import com.squareup.workflow1.ui.compose.tooling.Preview
1414

1515
@OptIn(WorkflowUiExperimentalApi::class)
16-
val HelloBinding = composeScreenViewFactory<Rendering> { rendering, _ ->
16+
val HelloBinding = ScreenComposableFactory<Rendering> { rendering, _ ->
1717
Text(
1818
rendering.message,
1919
modifier = Modifier

samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocomposebinding/HelloBindingActivity.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import com.squareup.workflow1.ui.ViewEnvironment
1717
import com.squareup.workflow1.ui.ViewRegistry
1818
import com.squareup.workflow1.ui.WorkflowLayout
1919
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
20+
import com.squareup.workflow1.ui.compose.withComposeInteropSupport
2021
import com.squareup.workflow1.ui.compose.withCompositionRoot
2122
import com.squareup.workflow1.ui.plus
2223
import com.squareup.workflow1.ui.renderWorkflowIn
@@ -25,13 +26,15 @@ import kotlinx.coroutines.flow.StateFlow
2526

2627
@OptIn(WorkflowUiExperimentalApi::class)
2728
private val viewEnvironment =
28-
(ViewEnvironment.EMPTY + ViewRegistry(HelloBinding)).withCompositionRoot { content ->
29-
MaterialTheme(content = content)
30-
}
29+
(ViewEnvironment.EMPTY + ViewRegistry(HelloBinding))
30+
.withCompositionRoot { content ->
31+
MaterialTheme(content = content)
32+
}
33+
.withComposeInteropSupport()
3134

3235
/**
3336
* Demonstrates how to create and display a view factory with
34-
* [composeScreenViewFactory][com.squareup.workflow1.ui.compose.composeScreenViewFactory].
37+
* [screenComposableFactory][com.squareup.workflow1.ui.compose.ScreenComposableFactory].
3538
*/
3639
class HelloBindingActivity : AppCompatActivity() {
3740
@OptIn(WorkflowUiExperimentalApi::class)

samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocomposeworkflow/HelloComposeWorkflowActivity.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@ import androidx.lifecycle.ViewModel
1010
import androidx.lifecycle.viewModelScope
1111
import com.squareup.workflow1.WorkflowExperimentalRuntime
1212
import com.squareup.workflow1.config.AndroidRuntimeConfigTools
13+
import com.squareup.workflow1.mapRendering
1314
import com.squareup.workflow1.ui.Screen
15+
import com.squareup.workflow1.ui.ViewEnvironment
1416
import com.squareup.workflow1.ui.WorkflowLayout
1517
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
18+
import com.squareup.workflow1.ui.compose.withComposeInteropSupport
1619
import com.squareup.workflow1.ui.renderWorkflowIn
20+
import com.squareup.workflow1.ui.withEnvironment
1721
import kotlinx.coroutines.flow.StateFlow
1822

1923
class HelloComposeWorkflowActivity : AppCompatActivity() {
@@ -30,7 +34,9 @@ class HelloComposeWorkflowActivity : AppCompatActivity() {
3034
@OptIn(WorkflowUiExperimentalApi::class)
3135
val renderings: StateFlow<Screen> by lazy {
3236
renderWorkflowIn(
33-
workflow = HelloWorkflow,
37+
workflow = HelloWorkflow.mapRendering {
38+
it.withEnvironment(ViewEnvironment.EMPTY.withComposeInteropSupport())
39+
},
3440
scope = viewModelScope,
3541
savedStateHandle = savedState,
3642
runtimeConfig = AndroidRuntimeConfigTools.getAppWorkflowRuntimeConfig()

samples/compose-samples/src/main/java/com/squareup/sample/compose/inlinerendering/InlineRenderingActivity.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@ import androidx.lifecycle.ViewModel
1010
import androidx.lifecycle.viewModelScope
1111
import com.squareup.workflow1.WorkflowExperimentalRuntime
1212
import com.squareup.workflow1.config.AndroidRuntimeConfigTools
13+
import com.squareup.workflow1.mapRendering
1314
import com.squareup.workflow1.ui.Screen
15+
import com.squareup.workflow1.ui.ViewEnvironment
1416
import com.squareup.workflow1.ui.WorkflowLayout
1517
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
18+
import com.squareup.workflow1.ui.compose.withComposeInteropSupport
1619
import com.squareup.workflow1.ui.renderWorkflowIn
20+
import com.squareup.workflow1.ui.withEnvironment
1721
import kotlinx.coroutines.flow.StateFlow
1822

1923
/**
@@ -34,7 +38,9 @@ class InlineRenderingActivity : AppCompatActivity() {
3438
@OptIn(WorkflowUiExperimentalApi::class)
3539
val renderings: StateFlow<Screen> by lazy {
3640
renderWorkflowIn(
37-
workflow = InlineRenderingWorkflow,
41+
workflow = InlineRenderingWorkflow.mapRendering {
42+
it.withEnvironment(ViewEnvironment.EMPTY.withComposeInteropSupport())
43+
},
3844
scope = viewModelScope,
3945
savedStateHandle = savedState,
4046
runtimeConfig = AndroidRuntimeConfigTools.getAppWorkflowRuntimeConfig()

samples/compose-samples/src/main/java/com/squareup/sample/compose/inlinerendering/InlineRenderingWorkflow.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ import com.squareup.workflow1.StatefulWorkflow
2121
import com.squareup.workflow1.WorkflowExperimentalRuntime
2222
import com.squareup.workflow1.config.AndroidRuntimeConfigTools
2323
import com.squareup.workflow1.parse
24-
import com.squareup.workflow1.ui.AndroidScreen
24+
import com.squareup.workflow1.ui.Screen
2525
import com.squareup.workflow1.ui.ViewEnvironment
2626
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
2727
import com.squareup.workflow1.ui.compose.ComposeScreen
2828
import com.squareup.workflow1.ui.compose.WorkflowRendering
2929
import com.squareup.workflow1.ui.compose.renderAsState
3030

31-
object InlineRenderingWorkflow : StatefulWorkflow<Unit, Int, Nothing, AndroidScreen<*>>() {
31+
object InlineRenderingWorkflow : StatefulWorkflow<Unit, Int, Nothing, Screen>() {
3232

3333
override fun initialState(
3434
props: Unit,
@@ -39,7 +39,7 @@ object InlineRenderingWorkflow : StatefulWorkflow<Unit, Int, Nothing, AndroidScr
3939
renderProps: Unit,
4040
renderState: Int,
4141
context: RenderContext
42-
): AndroidScreen<*> = ComposeScreen {
42+
) = ComposeScreen {
4343
Box {
4444
Button(onClick = context.eventHandler { state += 1 }) {
4545
Text("Counter: ")

samples/compose-samples/src/main/java/com/squareup/sample/compose/launcher/Samples.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ val samples = listOf(
3737
Sample(
3838
"Hello Compose Binding",
3939
HelloBindingActivity::class,
40-
"Creates a ViewFactory using composeViewFactory."
40+
"Binds a Screen to a UI factory using ScreenComposableFactory()."
4141
) { DrawHelloRenderingPreview() },
4242
Sample(
4343
"Nested Renderings",

0 commit comments

Comments
 (0)