Skip to content
This repository was archived by the owner on Feb 5, 2021. It is now read-only.

Commit 5730afa

Browse files
Merge pull request #32 from square/zachklipp/renderasstate
Make renderAsState public, make WorkflowContainer take a ViewEnvironment.
2 parents 508029e + 4a5373a commit 5730afa

File tree

6 files changed

+570
-423
lines changed

6 files changed

+570
-423
lines changed

core-compose/api/core-compose.api

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,28 +31,31 @@ public final class com/squareup/workflow/ui/compose/CompositionRootKt {
3131
public static final fun withCompositionRoot (Lcom/squareup/workflow/ui/ViewRegistry;Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow/ui/ViewRegistry;
3232
}
3333

34+
public final class com/squareup/workflow/ui/compose/RenderAsStateKt {
35+
public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)Landroidx/compose/State;
36+
public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)Landroidx/compose/State;
37+
public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)Landroidx/compose/State;
38+
public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)Landroidx/compose/State;
39+
public static synthetic fun renderAsState$default (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)Landroidx/compose/State;
40+
public static synthetic fun renderAsState$default (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)Landroidx/compose/State;
41+
public static synthetic fun renderAsState$default (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)Landroidx/compose/State;
42+
public static synthetic fun renderAsState$default (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)Landroidx/compose/State;
43+
}
44+
3445
public final class com/squareup/workflow/ui/compose/ViewEnvironmentsKt {
3546
public static final fun WorkflowRendering (Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Landroidx/compose/Composer;)V
3647
public static synthetic fun WorkflowRendering$default (Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Landroidx/compose/Composer;ILjava/lang/Object;)V
3748
}
3849

3950
public final class com/squareup/workflow/ui/compose/WorkflowContainerKt {
40-
public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;)V
4151
public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)V
42-
public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Ljava/lang/Object;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)V
43-
public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)V
44-
public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)V
45-
public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;)V
46-
public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;)V
47-
public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;)V
48-
public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;ILjava/lang/Object;)V
52+
public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)V
53+
public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)V
54+
public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)V
4955
public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)V
50-
public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Ljava/lang/Object;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)V
51-
public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)V
52-
public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)V
53-
public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;ILjava/lang/Object;)V
54-
public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;ILjava/lang/Object;)V
55-
public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;ILjava/lang/Object;)V
56+
public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)V
57+
public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)V
58+
public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)V
5659
}
5760

5861
public final class com/squareup/workflow/ui/compose/internal/ComposeSupportKt {
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
/*
2+
* Copyright 2020 Square Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
@file:Suppress("RemoveEmptyParenthesesFromAnnotationEntry")
17+
18+
package com.squareup.workflow.ui.compose
19+
20+
import androidx.compose.FrameManager
21+
import androidx.compose.Providers
22+
import androidx.compose.mutableStateOf
23+
import androidx.test.ext.junit.runners.AndroidJUnit4
24+
import androidx.ui.savedinstancestate.UiSavedStateRegistry
25+
import androidx.ui.savedinstancestate.UiSavedStateRegistryAmbient
26+
import androidx.ui.test.createComposeRule
27+
import androidx.ui.test.runOnIdleCompose
28+
import androidx.ui.test.waitForIdle
29+
import com.google.common.truth.Truth.assertThat
30+
import com.squareup.workflow.RenderContext
31+
import com.squareup.workflow.Snapshot
32+
import com.squareup.workflow.StatefulWorkflow
33+
import com.squareup.workflow.Workflow
34+
import com.squareup.workflow.action
35+
import com.squareup.workflow.parse
36+
import com.squareup.workflow.readUtf8WithLength
37+
import com.squareup.workflow.stateless
38+
import com.squareup.workflow.ui.compose.RenderAsStateTest.SnapshottingWorkflow.SnapshottedRendering
39+
import com.squareup.workflow.writeUtf8WithLength
40+
import okio.ByteString
41+
import okio.ByteString.Companion.decodeBase64
42+
import org.junit.Rule
43+
import org.junit.Test
44+
import org.junit.runner.RunWith
45+
46+
@RunWith(AndroidJUnit4::class)
47+
class RenderAsStateTest {
48+
49+
@Rule @JvmField val composeRule = createComposeRule()
50+
51+
@Test fun passesPropsThrough() {
52+
val workflow = Workflow.stateless<String, Nothing, String> { it }
53+
lateinit var initialRendering: String
54+
55+
composeRule.setContent {
56+
initialRendering = workflow.renderAsState("foo").value
57+
}
58+
59+
runOnIdleCompose {
60+
assertThat(initialRendering).isEqualTo("foo")
61+
}
62+
}
63+
64+
@Test fun seesPropsAndRenderingUpdates() {
65+
val workflow = Workflow.stateless<String, Nothing, String> { it }
66+
val props = mutableStateOf("foo")
67+
lateinit var rendering: String
68+
69+
composeRule.setContent {
70+
rendering = workflow.renderAsState(props.value).value
71+
}
72+
73+
waitForIdle()
74+
assertThat(rendering).isEqualTo("foo")
75+
FrameManager.framed {
76+
props.value = "bar"
77+
}
78+
waitForIdle()
79+
assertThat(rendering).isEqualTo("bar")
80+
}
81+
82+
@Test fun invokesOutputCallback() {
83+
val workflow = Workflow.stateless<Unit, String, (String) -> Unit> {
84+
{ string -> actionSink.send(action { setOutput(string) }) }
85+
}
86+
val receivedOutputs = mutableListOf<String>()
87+
lateinit var rendering: (String) -> Unit
88+
89+
composeRule.setContent {
90+
rendering = workflow.renderAsState(onOutput = { receivedOutputs += it }).value
91+
}
92+
93+
waitForIdle()
94+
assertThat(receivedOutputs).isEmpty()
95+
rendering("one")
96+
97+
waitForIdle()
98+
assertThat(receivedOutputs).isEqualTo(listOf("one"))
99+
rendering("two")
100+
101+
waitForIdle()
102+
assertThat(receivedOutputs).isEqualTo(listOf("one", "two"))
103+
}
104+
105+
@Test fun savesSnapshot() {
106+
val workflow = SnapshottingWorkflow()
107+
val savedStateRegistry = UiSavedStateRegistry(emptyMap()) { true }
108+
lateinit var rendering: SnapshottedRendering
109+
110+
composeRule.setContent {
111+
Providers(UiSavedStateRegistryAmbient provides savedStateRegistry) {
112+
rendering = renderAsStateImpl(
113+
workflow,
114+
props = Unit,
115+
onOutput = {},
116+
diagnosticListener = null,
117+
snapshotKey = SNAPSHOT_KEY
118+
).value
119+
}
120+
}
121+
122+
waitForIdle()
123+
assertThat(rendering.string).isEmpty()
124+
rendering.updateString("foo")
125+
126+
waitForIdle()
127+
val savedValues = FrameManager.framed {
128+
savedStateRegistry.performSave()
129+
}
130+
println("saved keys: ${savedValues.keys}")
131+
// Relying on the int key across all runtimes is brittle, so use an explicit key.
132+
val snapshot = ByteString.of(*(savedValues.getValue(SNAPSHOT_KEY) as ByteArray))
133+
println("snapshot: ${snapshot.base64()}")
134+
assertThat(snapshot).isEqualTo(EXPECTED_SNAPSHOT)
135+
}
136+
137+
@Test fun restoresSnapshot() {
138+
val workflow = SnapshottingWorkflow()
139+
val restoreValues = mapOf(SNAPSHOT_KEY to EXPECTED_SNAPSHOT.toByteArray())
140+
val savedStateRegistry = UiSavedStateRegistry(restoreValues) { true }
141+
lateinit var rendering: SnapshottedRendering
142+
143+
composeRule.setContent {
144+
Providers(UiSavedStateRegistryAmbient provides savedStateRegistry) {
145+
rendering = renderAsStateImpl(
146+
workflow,
147+
props = Unit,
148+
onOutput = {},
149+
diagnosticListener = null,
150+
snapshotKey = "workflow-snapshot"
151+
).value
152+
}
153+
}
154+
155+
waitForIdle()
156+
assertThat(rendering.string).isEqualTo("foo")
157+
}
158+
159+
@Test fun restoresFromSnapshotWhenWorkflowChanged() {
160+
val workflow1 = SnapshottingWorkflow()
161+
val workflow2 = SnapshottingWorkflow()
162+
val currentWorkflow = mutableStateOf(workflow1)
163+
lateinit var rendering: SnapshottedRendering
164+
165+
var compositionCount = 0
166+
var lastCompositionCount = 0
167+
fun assertWasRecomposed() {
168+
assertThat(compositionCount).isGreaterThan(lastCompositionCount)
169+
lastCompositionCount = compositionCount
170+
}
171+
172+
composeRule.setContent {
173+
compositionCount++
174+
rendering = currentWorkflow.value.renderAsState().value
175+
}
176+
177+
// Initialize the first workflow.
178+
waitForIdle()
179+
assertThat(rendering.string).isEmpty()
180+
assertWasRecomposed()
181+
rendering.updateString("one")
182+
waitForIdle()
183+
assertWasRecomposed()
184+
assertThat(rendering.string).isEqualTo("one")
185+
186+
// Change the workflow instance being rendered. This should restart the runtime, but restore
187+
// it from the snapshot.
188+
FrameManager.framed {
189+
currentWorkflow.value = workflow2
190+
}
191+
192+
waitForIdle()
193+
assertWasRecomposed()
194+
assertThat(rendering.string).isEqualTo("one")
195+
}
196+
197+
private companion object {
198+
const val SNAPSHOT_KEY = "workflow-snapshot"
199+
200+
/** Golden value from [savesSnapshot]. */
201+
val EXPECTED_SNAPSHOT = "AAAABwAAAANmb28AAAAA".decodeBase64()!!
202+
}
203+
204+
// Seems to be a problem accessing Workflow.stateful.
205+
private class SnapshottingWorkflow :
206+
StatefulWorkflow<Unit, String, Nothing, SnapshottedRendering>() {
207+
208+
data class SnapshottedRendering(
209+
val string: String,
210+
val updateString: (String) -> Unit
211+
)
212+
213+
override fun initialState(
214+
props: Unit,
215+
snapshot: Snapshot?
216+
): String = snapshot?.bytes?.parse { it.readUtf8WithLength() } ?: ""
217+
218+
override fun render(
219+
props: Unit,
220+
state: String,
221+
context: RenderContext<String, Nothing>
222+
) = SnapshottedRendering(
223+
string = state,
224+
updateString = { newString -> context.actionSink.send(updateString(newString)) }
225+
)
226+
227+
override fun snapshotState(state: String): Snapshot =
228+
Snapshot.write { it.writeUtf8WithLength(state) }
229+
230+
private fun updateString(newString: String) = action {
231+
nextState = newString
232+
}
233+
}
234+
}

0 commit comments

Comments
 (0)