Skip to content

Commit 0860065

Browse files
Introduce ComposeWorkflowNode.
1 parent 05cad44 commit 0860065

File tree

55 files changed

+2983
-158
lines changed

Some content is hidden

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

55 files changed

+2983
-158
lines changed

benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/instrumentation/PerformanceTracingInterceptor.kt

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package com.squareup.benchmarks.performance.complex.poetry.instrumentation
22

3+
import androidx.compose.runtime.Composable
34
import androidx.tracing.Trace
45
import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemWorkflow
56
import com.squareup.benchmarks.performance.complex.poetry.PerformancePoemsBrowserWorkflow
7+
import com.squareup.benchmarks.performance.complex.poetry.instrumentation.PerformanceTracingInterceptor.Companion.NODES_TO_TRACE
68
import com.squareup.workflow1.BaseRenderContext
9+
import com.squareup.workflow1.WorkflowExperimentalApi
710
import com.squareup.workflow1.WorkflowInterceptor
811
import com.squareup.workflow1.WorkflowInterceptor.RenderContextInterceptor
912
import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession
@@ -27,6 +30,24 @@ class PerformanceTracingInterceptor(
2730
context: BaseRenderContext<P, S, O>,
2831
proceed: (P, S, RenderContextInterceptor<P, S, O>?) -> R,
2932
session: WorkflowSession
33+
): R = traceRender(session) {
34+
proceed(renderProps, renderState, null)
35+
}
36+
37+
@OptIn(WorkflowExperimentalApi::class)
38+
@Composable
39+
override fun <P, O, R> onRenderComposeWorkflow(
40+
renderProps: P,
41+
emitOutput: (O) -> Unit,
42+
proceed: @Composable (P, (O) -> Unit) -> R,
43+
session: WorkflowSession
44+
): R = traceRender(session) {
45+
proceed(renderProps, emitOutput)
46+
}
47+
48+
private inline fun <R> traceRender(
49+
session: WorkflowSession,
50+
render: () -> R
3051
): R {
3152
val isRoot = session.parent == null
3253
val traceIdIndex = NODES_TO_TRACE.indexOfFirst { it.second == session.identifier }
@@ -45,7 +66,7 @@ class PerformanceTracingInterceptor(
4566
Trace.beginSection(sectionName)
4667
}
4768

48-
return proceed(renderProps, renderState, null).also {
69+
return render().also {
4970
if (traceIdIndex > -1 && !sample) {
5071
Trace.endSection()
5172
}

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

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,36 +14,32 @@ import androidx.compose.material.Button
1414
import androidx.compose.material.Text
1515
import androidx.compose.runtime.Composable
1616
import androidx.compose.runtime.getValue
17+
import androidx.compose.runtime.mutableIntStateOf
18+
import androidx.compose.runtime.saveable.rememberSaveable
19+
import androidx.compose.runtime.setValue
1720
import androidx.compose.ui.tooling.preview.Preview
18-
import com.squareup.workflow1.Snapshot
19-
import com.squareup.workflow1.StatefulWorkflow
21+
import com.squareup.workflow1.WorkflowExperimentalApi
2022
import com.squareup.workflow1.WorkflowExperimentalRuntime
23+
import com.squareup.workflow1.compose.ComposeWorkflow
2124
import com.squareup.workflow1.config.AndroidRuntimeConfigTools
22-
import com.squareup.workflow1.parse
2325
import com.squareup.workflow1.ui.Screen
2426
import com.squareup.workflow1.ui.compose.ComposeScreen
2527
import com.squareup.workflow1.ui.compose.WorkflowRendering
2628
import com.squareup.workflow1.ui.compose.renderAsState
2729

28-
object InlineRenderingWorkflow : StatefulWorkflow<Unit, Int, Nothing, Screen>() {
30+
@OptIn(WorkflowExperimentalApi::class)
31+
object InlineRenderingWorkflow : ComposeWorkflow<Unit, Nothing, Screen>() {
2932

30-
override fun initialState(
33+
@Composable
34+
override fun produceRendering(
3135
props: Unit,
32-
snapshot: Snapshot?
33-
): Int = snapshot?.bytes?.parse { it.readInt() } ?: 0
34-
35-
override fun render(
36-
renderProps: Unit,
37-
renderState: Int,
38-
context: RenderContext<Unit, Int, Nothing>
39-
): ComposeScreen {
40-
val onClick = context.eventHandler("increment") { state += 1 }
36+
emitOutput: (Nothing) -> Unit
37+
): Screen {
38+
var state by rememberSaveable { mutableIntStateOf(0) }
4139
return ComposeScreen {
42-
Content(renderState, onClick)
40+
Content(state, onClick = { state++ })
4341
}
4442
}
45-
46-
override fun snapshotState(state: Int): Snapshot = Snapshot.of(state)
4743
}
4844

4945
@Composable

samples/compose-samples/src/main/java/com/squareup/sample/compose/nestedrenderings/NestedRenderingsActivity.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import androidx.compose.ui.graphics.Color
1010
import androidx.lifecycle.SavedStateHandle
1111
import androidx.lifecycle.ViewModel
1212
import androidx.lifecycle.viewModelScope
13+
import com.squareup.workflow1.SimpleLoggingWorkflowInterceptor
1314
import com.squareup.workflow1.WorkflowExperimentalRuntime
1415
import com.squareup.workflow1.android.renderWorkflowIn
1516
import com.squareup.workflow1.config.AndroidRuntimeConfigTools
@@ -47,7 +48,8 @@ class NestedRenderingsActivity : AppCompatActivity() {
4748
workflow = RecursiveWorkflow.mapRendering { it.withEnvironment(viewEnvironment) },
4849
scope = viewModelScope,
4950
savedStateHandle = savedState,
50-
runtimeConfig = AndroidRuntimeConfigTools.getAppWorkflowRuntimeConfig()
51+
runtimeConfig = AndroidRuntimeConfigTools.getAppWorkflowRuntimeConfig(),
52+
interceptors = listOf(SimpleLoggingWorkflowInterceptor())
5153
)
5254
}
5355
}

samples/compose-samples/src/main/java/com/squareup/sample/compose/nestedrenderings/RecursiveViewFactory.kt

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
package com.squareup.sample.compose.nestedrenderings
44

5+
import androidx.compose.animation.Animatable
6+
import androidx.compose.animation.core.FastOutLinearInEasing
7+
import androidx.compose.animation.core.LinearOutSlowInEasing
8+
import androidx.compose.animation.core.keyframes
9+
import androidx.compose.foundation.gestures.awaitEachGesture
10+
import androidx.compose.foundation.gestures.detectTapGestures
11+
import androidx.compose.foundation.interaction.MutableInteractionSource
12+
import androidx.compose.foundation.interaction.PressInteraction
513
import androidx.compose.foundation.layout.Arrangement.SpaceEvenly
614
import androidx.compose.foundation.layout.Column
715
import androidx.compose.foundation.layout.Row
@@ -13,12 +21,17 @@ import androidx.compose.material.Card
1321
import androidx.compose.material.Text
1422
import androidx.compose.runtime.Composable
1523
import androidx.compose.runtime.CompositionLocalProvider
24+
import androidx.compose.runtime.LaunchedEffect
1625
import androidx.compose.runtime.compositionLocalOf
26+
import androidx.compose.runtime.getValue
27+
import androidx.compose.runtime.mutableIntStateOf
1728
import androidx.compose.runtime.remember
29+
import androidx.compose.runtime.setValue
1830
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
1931
import androidx.compose.ui.Modifier
2032
import androidx.compose.ui.graphics.Color
2133
import androidx.compose.ui.graphics.compositeOver
34+
import androidx.compose.ui.input.pointer.pointerInput
2235
import androidx.compose.ui.res.dimensionResource
2336
import androidx.compose.ui.tooling.preview.Preview
2437
import com.squareup.sample.compose.R
@@ -27,6 +40,7 @@ import com.squareup.workflow1.ui.Screen
2740
import com.squareup.workflow1.ui.compose.ScreenComposableFactory
2841
import com.squareup.workflow1.ui.compose.WorkflowRendering
2942
import com.squareup.workflow1.ui.compose.tooling.Preview
43+
import kotlin.time.DurationUnit.MILLISECONDS
3044

3145
/**
3246
* Composition local of [Color] to use as the background color for a [RecursiveComposableFactory].
@@ -44,7 +58,26 @@ val RecursiveComposableFactory = ScreenComposableFactory<Rendering> { rendering
4458
.compositeOver(Color.Black)
4559
}
4660

47-
Card(backgroundColor = color) {
61+
var lastFlashedTrigger by remember { mutableIntStateOf(rendering.flashTrigger) }
62+
val flashAlpha = remember { Animatable(Color(0x00FFFFFF)) }
63+
64+
// Flash the card white when asked.
65+
LaunchedEffect(rendering.flashTrigger) {
66+
if (rendering.flashTrigger != 0) {
67+
lastFlashedTrigger = rendering.flashTrigger
68+
flashAlpha.animateTo(Color(0x00FFFFFF), animationSpec = keyframes {
69+
Color.White at (rendering.flashTime / 7).toInt(MILLISECONDS) using FastOutLinearInEasing
70+
Color(0x00FFFFFF) at rendering.flashTime.toInt(MILLISECONDS) using LinearOutSlowInEasing
71+
})
72+
}
73+
}
74+
75+
Card(
76+
backgroundColor = flashAlpha.value.compositeOver(color),
77+
modifier = Modifier.pointerInput(rendering) {
78+
detectTapGestures(onPress = { rendering.onSelfClicked() })
79+
}
80+
) {
4881
Column(
4982
Modifier
5083
.padding(dimensionResource(R.dimen.recursive_padding))
@@ -76,10 +109,14 @@ fun RecursiveViewFactoryPreview() {
76109
StringRendering("foo"),
77110
Rendering(
78111
children = listOf(StringRendering("bar")),
112+
flashTrigger = 0,
113+
onSelfClicked = {},
79114
onAddChildClicked = {},
80115
onResetClicked = {}
81116
)
82117
),
118+
flashTrigger = 0,
119+
onSelfClicked = {},
83120
onAddChildClicked = {},
84121
onResetClicked = {}
85122
),
Lines changed: 60 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,30 @@
11
package com.squareup.sample.compose.nestedrenderings
22

3+
import androidx.compose.runtime.Composable
4+
import androidx.compose.runtime.DisposableEffect
5+
import androidx.compose.runtime.LaunchedEffect
6+
import androidx.compose.runtime.getValue
7+
import androidx.compose.runtime.mutableIntStateOf
8+
import androidx.compose.runtime.mutableStateOf
9+
import androidx.compose.runtime.remember
10+
import androidx.compose.runtime.rememberCoroutineScope
11+
import androidx.compose.runtime.saveable.rememberSaveable
12+
import androidx.compose.runtime.setValue
313
import com.squareup.sample.compose.databinding.LegacyViewBinding
414
import com.squareup.sample.compose.nestedrenderings.RecursiveWorkflow.LegacyRendering
515
import com.squareup.sample.compose.nestedrenderings.RecursiveWorkflow.Rendering
6-
import com.squareup.sample.compose.nestedrenderings.RecursiveWorkflow.State
7-
import com.squareup.workflow1.Snapshot
8-
import com.squareup.workflow1.StatefulWorkflow
9-
import com.squareup.workflow1.action
10-
import com.squareup.workflow1.renderChild
16+
import com.squareup.workflow1.WorkflowExperimentalApi
17+
import com.squareup.workflow1.compose.ComposeWorkflow
18+
import com.squareup.workflow1.compose.renderChild
1119
import com.squareup.workflow1.ui.AndroidScreen
1220
import com.squareup.workflow1.ui.Screen
1321
import com.squareup.workflow1.ui.ScreenViewFactory
22+
import kotlinx.coroutines.CoroutineDispatcher
23+
import kotlinx.coroutines.delay
24+
import kotlinx.coroutines.launch
25+
import kotlin.time.Duration
26+
import kotlin.time.Duration.Companion.ZERO
27+
import kotlin.time.Duration.Companion.seconds
1428

1529
/**
1630
* A simple workflow that produces [Rendering]s of zero or more children.
@@ -20,9 +34,8 @@ import com.squareup.workflow1.ui.ScreenViewFactory
2034
* to force it to go through the legacy view layer. This way this sample both demonstrates pass-
2135
* through Composable renderings as well as adapting in both directions.
2236
*/
23-
object RecursiveWorkflow : StatefulWorkflow<Unit, State, Nothing, Screen>() {
24-
25-
data class State(val children: Int = 0)
37+
@OptIn(WorkflowExperimentalApi::class)
38+
object RecursiveWorkflow : ComposeWorkflow<Unit, Unit, Screen>() {
2639

2740
/**
2841
* A rendering from a [RecursiveWorkflow].
@@ -33,8 +46,11 @@ object RecursiveWorkflow : StatefulWorkflow<Unit, State, Nothing, Screen>() {
3346
*/
3447
data class Rendering(
3548
val children: List<Screen>,
36-
val onAddChildClicked: () -> Unit,
37-
val onResetClicked: () -> Unit
49+
val flashTrigger: Int = 0,
50+
val flashTime: Duration = ZERO,
51+
val onSelfClicked: () -> Unit = {},
52+
val onAddChildClicked: () -> Unit = {},
53+
val onResetClicked: () -> Unit = {}
3854
) : Screen
3955

4056
/**
@@ -49,33 +65,45 @@ object RecursiveWorkflow : StatefulWorkflow<Unit, State, Nothing, Screen>() {
4965
)
5066
}
5167

52-
override fun initialState(
68+
@OptIn(ExperimentalStdlibApi::class)
69+
@Composable override fun produceRendering(
5370
props: Unit,
54-
snapshot: Snapshot?
55-
): State = State()
71+
emitOutput: (Unit) -> Unit
72+
): Screen {
73+
var children by rememberSaveable { mutableStateOf(0) }
74+
var flashTrigger by remember { mutableIntStateOf(0) }
75+
val coroutineScope = rememberCoroutineScope()
76+
77+
DisposableEffect(Unit) {
78+
println("OMG coroutineScope dispatcher: ${coroutineScope.coroutineContext[CoroutineDispatcher]}")
79+
onDispose {}
80+
}
81+
82+
LaunchedEffect(Unit) {
83+
println("OMG LaunchedEffect dispatcher: ${coroutineScope.coroutineContext[CoroutineDispatcher]}")
84+
}
5685

57-
override fun render(
58-
renderProps: Unit,
59-
renderState: State,
60-
context: RenderContext<Unit, State, Nothing>
61-
): Rendering {
6286
return Rendering(
63-
children = List(renderState.children) { i ->
64-
val child = context.renderChild(RecursiveWorkflow, key = i.toString())
87+
children = List(children) { i ->
88+
val child = renderChild(RecursiveWorkflow, onOutput = {
89+
// When a child is clicked, cascade the flash up.
90+
coroutineScope.launch {
91+
delay(0.1.seconds)
92+
flashTrigger++
93+
emitOutput(Unit)
94+
}
95+
})
6596
if (i % 2 == 0) child else LegacyRendering(child)
6697
},
67-
onAddChildClicked = { context.actionSink.send(addChild()) },
68-
onResetClicked = { context.actionSink.send(reset()) }
98+
flashTrigger = flashTrigger,
99+
flashTime = 0.5.seconds,
100+
// Trigger a cascade of flashes when clicked.
101+
onSelfClicked = {
102+
flashTrigger++
103+
emitOutput(Unit)
104+
},
105+
onAddChildClicked = { children++ },
106+
onResetClicked = { children = 0 }
69107
)
70108
}
71-
72-
override fun snapshotState(state: State): Snapshot? = null
73-
74-
private fun addChild() = action("addChild") {
75-
state = state.copy(children = state.children + 1)
76-
}
77-
78-
private fun reset() = action("reset") {
79-
state = State()
80-
}
81109
}

samples/dungeon/common/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
plugins {
22
id("kotlin-jvm")
33
id("kotlinx-serialization")
4+
alias(libs.plugins.compose.compiler)
45
}
56

67
dependencies {

0 commit comments

Comments
 (0)