Skip to content

Commit a643b94

Browse files
Merge pull request #887 from square/zachklipp/skip-onpropschanged
Don't onPropsChanged unless the old and new props are actually unequal.
2 parents b9ead65 + 4fd45b4 commit a643b94

File tree

4 files changed

+52
-48
lines changed

4 files changed

+52
-48
lines changed

kotlin/workflow-runtime/src/main/java/com/squareup/workflow/internal/WorkflowNode.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,9 +248,11 @@ internal class WorkflowNode<PropsT, StateT, OutputT : Any, RenderingT>(
248248
workflow: StatefulWorkflow<PropsT, StateT, OutputT, RenderingT>,
249249
newProps: PropsT
250250
) {
251-
val newState = workflow.onPropsChanged(lastProps, newProps, state)
252-
diagnosticListener?.onPropsChanged(diagnosticId, lastProps, newProps, state, newState)
253-
state = newState
251+
if (newProps != lastProps) {
252+
val newState = workflow.onPropsChanged(lastProps, newProps, state)
253+
diagnosticListener?.onPropsChanged(diagnosticId, lastProps, newProps, state, newState)
254+
state = newState
255+
}
254256
lastProps = newProps
255257
}
256258

kotlin/workflow-runtime/src/test/java/com/squareup/workflow/internal/WorkflowDiagnosticListenerIntegrationTest.kt

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import kotlinx.coroutines.cancel
3434
import kotlinx.coroutines.cancelChildren
3535
import kotlinx.coroutines.channels.Channel
3636
import kotlinx.coroutines.flow.consumeAsFlow
37-
import kotlinx.coroutines.flow.flowOf
3837
import kotlinx.coroutines.flow.launchIn
3938
import kotlinx.coroutines.flow.map
4039
import kotlinx.coroutines.flow.produceIn
@@ -74,12 +73,11 @@ class WorkflowDiagnosticListenerIntegrationTest {
7473
spec = Spec(state = "root state")
7574

7675
// Initial events.
77-
assertEquals("state: root state\n", renderings.receive())
76+
assertEquals("state: initial state\n", renderings.receive())
7877
assertEquals(
7978
listOf(
8079
"onWorkflowStarted",
8180
"onBeforeRenderPass",
82-
"onPropsChanged",
8381
"onBeforeWorkflowRendered",
8482
"onAfterWorkflowRendered",
8583
"onAfterRenderPass",
@@ -118,7 +116,6 @@ class WorkflowDiagnosticListenerIntegrationTest {
118116
"onPropsChanged",
119117
"onBeforeWorkflowRendered",
120118
"onWorkflowStarted",
121-
"onPropsChanged",
122119
"onBeforeWorkflowRendered",
123120
"onAfterWorkflowRendered",
124121
"onAfterWorkflowRendered",
@@ -139,11 +136,9 @@ class WorkflowDiagnosticListenerIntegrationTest {
139136
"onBeforeRenderPass",
140137
"onPropsChanged",
141138
"onBeforeWorkflowRendered",
142-
"onPropsChanged",
143139
"onBeforeWorkflowRendered",
144140
"onAfterWorkflowRendered",
145141
"onWorkflowStarted",
146-
"onPropsChanged",
147142
"onBeforeWorkflowRendered",
148143
"onAfterWorkflowRendered",
149144
"onAfterWorkflowRendered",
@@ -162,7 +157,6 @@ class WorkflowDiagnosticListenerIntegrationTest {
162157
"onBeforeRenderPass",
163158
"onPropsChanged",
164159
"onBeforeWorkflowRendered",
165-
"onPropsChanged",
166160
"onBeforeWorkflowRendered",
167161
"onAfterWorkflowRendered",
168162
"onAfterWorkflowRendered",
@@ -185,17 +179,18 @@ class WorkflowDiagnosticListenerIntegrationTest {
185179
}
186180
}
187181

188-
@UseExperimental(ExperimentalCoroutinesApi::class)
182+
@UseExperimental(ExperimentalCoroutinesApi::class, FlowPreview::class)
189183
@Test fun `workflow updates emit events in order`() {
184+
val propsChannel = Channel<String>(1).apply { offer("initial props") }
190185
val channel = Channel<String>()
191186
val worker = channel.asWorker()
192187
fun action(value: String) = action<Nothing, String> { setOutput("output:$value") }
193-
val workflow = Workflow.stateless<Unit, String, Unit> {
188+
val workflow = Workflow.stateless<String, String, Unit> {
194189
runningWorker(worker, "key", ::action)
195190
}
196191

197192
runBlocking {
198-
launchWorkflowIn(this, workflow, flowOf(Unit)) { session ->
193+
launchWorkflowIn(this, workflow, propsChannel.consumeAsFlow()) { session ->
199194
session.diagnosticListener = listener
200195
.andThen(SimpleLoggingDiagnosticListener())
201196
session.renderingsAndSnapshots.launchIn(this)
@@ -208,20 +203,11 @@ class WorkflowDiagnosticListenerIntegrationTest {
208203
"onRuntimeStarted",
209204
"onWorkflowStarted",
210205
"onBeforeRenderPass",
211-
"onPropsChanged",
212206
"onBeforeWorkflowRendered",
213207
"onWorkerStarted",
214208
"onAfterWorkflowRendered",
215209
"onAfterRenderPass",
216210
"onBeforeSnapshotPass",
217-
"onAfterSnapshotPass",
218-
"onPropsChanged",
219-
"onBeforeRenderPass",
220-
"onPropsChanged",
221-
"onBeforeWorkflowRendered",
222-
"onAfterWorkflowRendered",
223-
"onAfterRenderPass",
224-
"onBeforeSnapshotPass",
225211
"onAfterSnapshotPass"
226212
),
227213
actual = listener.consumeEventNames()
@@ -235,6 +221,21 @@ class WorkflowDiagnosticListenerIntegrationTest {
235221
"onWorkerOutput",
236222
"onWorkflowAction",
237223
"onBeforeRenderPass",
224+
"onBeforeWorkflowRendered",
225+
"onAfterWorkflowRendered",
226+
"onAfterRenderPass",
227+
"onBeforeSnapshotPass",
228+
"onAfterSnapshotPass"
229+
), listener.consumeEventNames()
230+
)
231+
232+
propsChannel.send("new props")
233+
yield()
234+
yield()
235+
assertEquals(
236+
listOf(
237+
"onPropsChanged",
238+
"onBeforeRenderPass",
238239
"onPropsChanged",
239240
"onBeforeWorkflowRendered",
240241
"onAfterWorkflowRendered",

kotlin/workflow-runtime/src/test/java/com/squareup/workflow/internal/WorkflowNodeTest.kt

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -94,17 +94,30 @@ class WorkflowNodeTest {
9494

9595
private val context: CoroutineContext = Unconfined
9696

97-
@Test fun `props are passed to on changed`() {
97+
@Test fun `onPropsChanged is called when props change`() {
9898
val oldAndNewProps = mutableListOf<Pair<String, String>>()
9999
val workflow = PropsRenderingWorkflow { old, new, state ->
100100
oldAndNewProps += old to new
101-
state
101+
return@PropsRenderingWorkflow state
102102
}
103-
val node = WorkflowNode(workflow.id(), workflow, "foo", null, context)
103+
val node = WorkflowNode(workflow.id(), workflow, "old", null, context)
104+
105+
node.render(workflow, "new")
106+
107+
assertEquals(listOf("old" to "new"), oldAndNewProps)
108+
}
109+
110+
@Test fun `onPropsChanged is not called when props are equal`() {
111+
val oldAndNewProps = mutableListOf<Pair<String, String>>()
112+
val workflow = PropsRenderingWorkflow { old, new, state ->
113+
oldAndNewProps += old to new
114+
return@PropsRenderingWorkflow state
115+
}
116+
val node = WorkflowNode(workflow.id(), workflow, "old", null, context)
104117

105-
node.render(workflow, "foo2")
118+
node.render(workflow, "old")
106119

107-
assertEquals(listOf("foo" to "foo2"), oldAndNewProps)
120+
assertTrue(oldAndNewProps.isEmpty())
108121
}
109122

110123
@Test fun `props are rendered`() {
@@ -690,13 +703,13 @@ class WorkflowNodeTest {
690703
)
691704
listener.consumeEvents()
692705

693-
node.render(workflow.asStatefulWorkflow(), "props")
706+
node.render(workflow.asStatefulWorkflow(), "new props")
694707

695708
assertEquals(
696709
listOf(
697-
"onPropsChanged(${node.diagnosticId}, props, props, (props:), (props:props:(props:)))",
698-
"onBeforeWorkflowRendered(${node.diagnosticId}, props, (props:props:(props:)))",
699-
"onAfterWorkflowRendered(${node.diagnosticId}, ((props:props:(props:)):props))"
710+
"onPropsChanged(${node.diagnosticId}, props, new props, (props:), (props:new props:(props:)))",
711+
"onBeforeWorkflowRendered(${node.diagnosticId}, new props, (props:new props:(props:)))",
712+
"onAfterWorkflowRendered(${node.diagnosticId}, ((props:new props:(props:)):new props))"
700713
), listener.consumeEvents()
701714
)
702715
}
@@ -728,7 +741,7 @@ class WorkflowNodeTest {
728741
parentDiagnosticId = 42,
729742
diagnosticListener = listener
730743
)
731-
node.render(workflow.asStatefulWorkflow(), "props")
744+
node.render(workflow.asStatefulWorkflow(), "new props")
732745
listener.consumeEvents()
733746

734747
runBlocking {
@@ -751,8 +764,8 @@ class WorkflowNodeTest {
751764
)
752765
assertEquals(
753766
listOf(
754-
"onWorkflowAction(${node.diagnosticId}, TestAction, (props:props:(props:))," +
755-
" (props:props:(props:)), action output)"
767+
"onWorkflowAction(${node.diagnosticId}, TestAction, (props:new props:(props:))," +
768+
" (props:new props:(props:)), action output)"
756769
), listener.consumeEvents()
757770
)
758771
}
@@ -784,7 +797,7 @@ class WorkflowNodeTest {
784797
parentDiagnosticId = 42,
785798
diagnosticListener = listener
786799
)
787-
val rendering = node.render(workflow.asStatefulWorkflow(), "props")
800+
val rendering = node.render(workflow.asStatefulWorkflow(), "new props")
788801
listener.consumeEvents()
789802

790803
runBlocking {
@@ -804,7 +817,7 @@ class WorkflowNodeTest {
804817
assertEquals(
805818
listOf(
806819
"onSinkReceived(${node.diagnosticId}, TestAction)",
807-
"onWorkflowAction(${node.diagnosticId}, TestAction, (props:props:(props:))," +
820+
"onWorkflowAction(0, TestAction, (props:new props:(props:))," +
808821
" state: foo, output: foo)"
809822
), listener.consumeEvents()
810823
)

0 commit comments

Comments
 (0)