Skip to content

Commit 6395d0b

Browse files
authored
Merge pull request #1221 from square/ray/give-me-convenience-or-give-me-death
`safeAction`, `safeEventHandler`
2 parents 5e1c32b + 7071aa5 commit 6395d0b

File tree

4 files changed

+442
-37
lines changed

4 files changed

+442
-37
lines changed

samples/tictactoe/common/src/main/java/com/squareup/sample/gameworkflow/RunGameWorkflow.kt

Lines changed: 25 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import com.squareup.sample.gameworkflow.SyncState.SAVING
1818
import com.squareup.workflow1.Snapshot
1919
import com.squareup.workflow1.StatefulWorkflow
2020
import com.squareup.workflow1.Workflow
21-
import com.squareup.workflow1.action
2221
import com.squareup.workflow1.runningWorker
2322
import com.squareup.workflow1.rx2.asWorker
2423
import com.squareup.workflow1.ui.Screen
@@ -88,8 +87,12 @@ class RealRunGameWorkflow(
8887
namePrompt = NewGameScreen(
8988
renderState.defaultXName,
9089
renderState.defaultOName,
91-
onCancel = context.eventHandler { setOutput(CanceledStart) },
92-
onStartGame = context.eventHandler { x, o -> state = Playing(PlayerInfo(x, o)) }
90+
onCancel = context.safeEventHandler<NewGame> {
91+
setOutput(CanceledStart)
92+
},
93+
onStartGame = context.safeEventHandler<NewGame, String, String> { _, x, o ->
94+
state = Playing(PlayerInfo(x, o))
95+
}
9396
)
9497
)
9598
}
@@ -119,15 +122,11 @@ class RealRunGameWorkflow(
119122
message = "Do you really want to concede the game?",
120123
positive = "I Quit",
121124
negative = "No",
122-
confirmQuit = context.eventHandler {
123-
(state as? MaybeQuitting)?.let { oldState ->
124-
state = MaybeQuittingForSure(oldState.playerInfo, oldState.completedGame)
125-
}
125+
confirmQuit = context.safeEventHandler<MaybeQuitting> { oldState ->
126+
state = MaybeQuittingForSure(oldState.playerInfo, oldState.completedGame)
126127
},
127-
continuePlaying = context.eventHandler {
128-
(state as? MaybeQuitting)?.let { oldState ->
129-
state = Playing(oldState.playerInfo, oldState.completedGame.lastTurn)
130-
}
128+
continuePlaying = context.safeEventHandler<MaybeQuitting> { oldState ->
129+
state = Playing(oldState.playerInfo, oldState.completedGame.lastTurn)
131130
}
132131
)
133132
)
@@ -142,15 +141,11 @@ class RealRunGameWorkflow(
142141
message = "Really?",
143142
positive = "Yes!!",
144143
negative = "Sigh, no",
145-
confirmQuit = context.eventHandler {
146-
(state as? MaybeQuittingForSure)?.let { oldState ->
147-
state = GameOver(oldState.playerInfo, oldState.completedGame)
148-
}
144+
confirmQuit = context.safeEventHandler<MaybeQuittingForSure> { oldState ->
145+
state = GameOver(oldState.playerInfo, oldState.completedGame)
149146
},
150-
continuePlaying = context.eventHandler {
151-
(state as? MaybeQuittingForSure)?.let { oldState ->
152-
state = Playing(oldState.playerInfo, oldState.completedGame.lastTurn)
153-
}
147+
continuePlaying = context.safeEventHandler<MaybeQuittingForSure> { oldState ->
148+
state = Playing(oldState.playerInfo, oldState.completedGame.lastTurn)
154149
}
155150
)
156151
)
@@ -169,43 +164,37 @@ class RealRunGameWorkflow(
169164
renderState,
170165
onTrySaveAgain = context.trySaveAgain(),
171166
onPlayAgain = context.playAgain(),
172-
onExit = context.eventHandler { setOutput(FinishedPlaying) }
167+
onExit = context.safeEventHandler<GameOver> { setOutput(FinishedPlaying) }
173168
)
174169
)
175170
}
176171
}
177172

178-
private fun stopPlaying(game: CompletedGame) = action {
179-
val oldState = state as Playing
173+
private fun stopPlaying(game: CompletedGame) = safeAction<Playing>("stopPlaying") { oldState ->
180174
state = when (game.ending) {
181175
Quitted -> MaybeQuitting(oldState.playerInfo, game)
182176
else -> GameOver(oldState.playerInfo, game)
183177
}
184178
}
185179

186-
private fun handleLogGame(result: GameLog.LogResult) = action {
187-
val oldState = state as GameOver
180+
private fun handleLogGame(result: GameLog.LogResult) = safeAction<GameOver> { oldState ->
188181
state = when (result) {
189182
TRY_LATER -> oldState.copy(syncState = SAVE_FAILED)
190183
LOGGED -> oldState.copy(syncState = SAVED)
191184
}
192185
}
193186

194-
private fun RenderContext.playAgain() = eventHandler {
195-
(state as? GameOver)?.let { oldState ->
196-
val (x, o) = oldState.playerInfo
197-
state = NewGame(x, o)
198-
}
187+
private fun RenderContext.playAgain() = safeEventHandler<GameOver> { oldState ->
188+
val (x, o) = oldState.playerInfo
189+
state = NewGame(x, o)
199190
}
200191

201-
private fun RenderContext.trySaveAgain() = eventHandler {
202-
(state as? GameOver)?.let { oldState ->
203-
check(oldState.syncState == SAVE_FAILED) {
204-
"Should only fire trySaveAgain in syncState $SAVE_FAILED, " +
205-
"was ${oldState.syncState}"
206-
}
207-
state = oldState.copy(syncState = SAVING)
192+
private fun RenderContext.trySaveAgain() = safeEventHandler<GameOver> { oldState ->
193+
check(oldState.syncState == SAVE_FAILED) {
194+
"Should only fire trySaveAgain in syncState $SAVE_FAILED, " +
195+
"was ${oldState.syncState}"
208196
}
197+
state = oldState.copy(syncState = SAVING)
209198
}
210199

211200
override fun snapshotState(state: RunGameState): Snapshot = state.toSnapshot()

workflow-core/api/workflow-core.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ public final class com/squareup/workflow1/Snapshots {
155155
public abstract class com/squareup/workflow1/StatefulWorkflow : com/squareup/workflow1/IdCacheable, com/squareup/workflow1/Workflow {
156156
public fun <init> ()V
157157
public final fun asStatefulWorkflow ()Lcom/squareup/workflow1/StatefulWorkflow;
158+
public final fun defaultOnFailedCast (Ljava/lang/String;Lkotlin/reflect/KClass;Ljava/lang/Object;)V
158159
public fun getCachedIdentifier ()Lcom/squareup/workflow1/WorkflowIdentifier;
159160
public abstract fun initialState (Ljava/lang/Object;Lcom/squareup/workflow1/Snapshot;)Ljava/lang/Object;
160161
public fun initialState (Ljava/lang/Object;Lcom/squareup/workflow1/Snapshot;Lkotlinx/coroutines/CoroutineScope;)Ljava/lang/Object;

workflow-core/src/commonMain/kotlin/com/squareup/workflow1/BaseRenderContext.kt

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,45 @@ public interface BaseRenderContext<out PropsT, StateT, in OutputT> {
130130
* given [update] function, and immediately passes it to [actionSink]. Handy for
131131
* attaching event handlers to renderings.
132132
*
133+
* It is important to understand that the [update] lambda you provide here
134+
* may not run synchronously. This function and its overloads provide a short cut
135+
* that lets you replace this snippet:
136+
*
137+
* return SomeScreen(
138+
* onClick = {
139+
* context.actionSink.send(
140+
* action { state = SomeNewState }
141+
* }
142+
* }
143+
* )
144+
*
145+
* with this:
146+
*
147+
* return SomeScreen(
148+
* onClick = context.eventHandler { state = SomeNewState }
149+
* )
150+
*
151+
* Notice how your [update] function is passed to the [actionSink][BaseRenderContext.actionSink]
152+
* to be eventually executed as the body of a [WorkflowAction]. If several actions get stacked
153+
* up at once (think about accidental rapid taps on a button), that could take a while.
154+
*
155+
* If you require something to happen the instant a UI action happens, [eventHandler]
156+
* is the wrong choice. You'll want to write your own call to `actionSink.send`:
157+
*
158+
* return SomeScreen(
159+
* onClick = {
160+
* // This happens immediately.
161+
* MyAnalytics.log("SomeScreen was clicked")
162+
*
163+
* context.actionSink.send(
164+
* action {
165+
* // This happens eventually.
166+
* state = SomeNewState
167+
* }
168+
* }
169+
* }
170+
* )
171+
*
133172
* @param name A string describing the update, included in the action's [toString]
134173
* as a debugging aid
135174
* @param update Function that defines the workflow update.

0 commit comments

Comments
 (0)