Skip to content

Commit 04c0440

Browse files
committed
safeAction, safeEventHandler
People are confused by the fact that a `WorkflowAction` can't assume that a sealed class / interface `StateT` is the same subtype that it was at render time when the action fires. And those that do understand it resent this boilerplate: ```kotlin action { (state as? SpecificState)?.let { currentState -> // whatever } } ``` So we introduce `StatefulWorkflow.safeAction` and `StatefulWorkflow.RenderContext.safeEventHandler` as conveniences to do that cast for you.
1 parent 24f45a7 commit 04c0440

File tree

3 files changed

+355
-37
lines changed

3 files changed

+355
-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;

0 commit comments

Comments
 (0)