Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

safeAction, safeEventHandler #1221

Merged
merged 2 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import com.squareup.sample.gameworkflow.SyncState.SAVING
import com.squareup.workflow1.Snapshot
import com.squareup.workflow1.StatefulWorkflow
import com.squareup.workflow1.Workflow
import com.squareup.workflow1.action
import com.squareup.workflow1.runningWorker
import com.squareup.workflow1.rx2.asWorker
import com.squareup.workflow1.ui.Screen
Expand Down Expand Up @@ -88,8 +87,12 @@ class RealRunGameWorkflow(
namePrompt = NewGameScreen(
renderState.defaultXName,
renderState.defaultOName,
onCancel = context.eventHandler { setOutput(CanceledStart) },
onStartGame = context.eventHandler { x, o -> state = Playing(PlayerInfo(x, o)) }
onCancel = context.safeEventHandler<NewGame> {
setOutput(CanceledStart)
},
onStartGame = context.safeEventHandler<NewGame, String, String> { _, x, o ->
state = Playing(PlayerInfo(x, o))
}
)
)
}
Expand Down Expand Up @@ -119,15 +122,11 @@ class RealRunGameWorkflow(
message = "Do you really want to concede the game?",
positive = "I Quit",
negative = "No",
confirmQuit = context.eventHandler {
(state as? MaybeQuitting)?.let { oldState ->
state = MaybeQuittingForSure(oldState.playerInfo, oldState.completedGame)
}
confirmQuit = context.safeEventHandler<MaybeQuitting> { oldState ->
state = MaybeQuittingForSure(oldState.playerInfo, oldState.completedGame)
},
continuePlaying = context.eventHandler {
(state as? MaybeQuitting)?.let { oldState ->
state = Playing(oldState.playerInfo, oldState.completedGame.lastTurn)
}
continuePlaying = context.safeEventHandler<MaybeQuitting> { oldState ->
state = Playing(oldState.playerInfo, oldState.completedGame.lastTurn)
}
)
)
Expand All @@ -142,15 +141,11 @@ class RealRunGameWorkflow(
message = "Really?",
positive = "Yes!!",
negative = "Sigh, no",
confirmQuit = context.eventHandler {
(state as? MaybeQuittingForSure)?.let { oldState ->
state = GameOver(oldState.playerInfo, oldState.completedGame)
}
confirmQuit = context.safeEventHandler<MaybeQuittingForSure> { oldState ->
state = GameOver(oldState.playerInfo, oldState.completedGame)
},
continuePlaying = context.eventHandler {
(state as? MaybeQuittingForSure)?.let { oldState ->
state = Playing(oldState.playerInfo, oldState.completedGame.lastTurn)
}
continuePlaying = context.safeEventHandler<MaybeQuittingForSure> { oldState ->
state = Playing(oldState.playerInfo, oldState.completedGame.lastTurn)
}
)
)
Expand All @@ -169,43 +164,37 @@ class RealRunGameWorkflow(
renderState,
onTrySaveAgain = context.trySaveAgain(),
onPlayAgain = context.playAgain(),
onExit = context.eventHandler { setOutput(FinishedPlaying) }
onExit = context.safeEventHandler<GameOver> { setOutput(FinishedPlaying) }
)
)
}
}

private fun stopPlaying(game: CompletedGame) = action {
val oldState = state as Playing
private fun stopPlaying(game: CompletedGame) = safeAction<Playing>("stopPlaying") { oldState ->
state = when (game.ending) {
Quitted -> MaybeQuitting(oldState.playerInfo, game)
else -> GameOver(oldState.playerInfo, game)
}
}

private fun handleLogGame(result: GameLog.LogResult) = action {
val oldState = state as GameOver
private fun handleLogGame(result: GameLog.LogResult) = safeAction<GameOver> { oldState ->
state = when (result) {
TRY_LATER -> oldState.copy(syncState = SAVE_FAILED)
LOGGED -> oldState.copy(syncState = SAVED)
}
}

private fun RenderContext.playAgain() = eventHandler {
(state as? GameOver)?.let { oldState ->
val (x, o) = oldState.playerInfo
state = NewGame(x, o)
}
private fun RenderContext.playAgain() = safeEventHandler<GameOver> { oldState ->
val (x, o) = oldState.playerInfo
state = NewGame(x, o)
}

private fun RenderContext.trySaveAgain() = eventHandler {
(state as? GameOver)?.let { oldState ->
check(oldState.syncState == SAVE_FAILED) {
"Should only fire trySaveAgain in syncState $SAVE_FAILED, " +
"was ${oldState.syncState}"
}
state = oldState.copy(syncState = SAVING)
private fun RenderContext.trySaveAgain() = safeEventHandler<GameOver> { oldState ->
check(oldState.syncState == SAVE_FAILED) {
"Should only fire trySaveAgain in syncState $SAVE_FAILED, " +
"was ${oldState.syncState}"
}
state = oldState.copy(syncState = SAVING)
}

override fun snapshotState(state: RunGameState): Snapshot = state.toSnapshot()
Expand Down
1 change: 1 addition & 0 deletions workflow-core/api/workflow-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ public final class com/squareup/workflow1/Snapshots {
public abstract class com/squareup/workflow1/StatefulWorkflow : com/squareup/workflow1/IdCacheable, com/squareup/workflow1/Workflow {
public fun <init> ()V
public final fun asStatefulWorkflow ()Lcom/squareup/workflow1/StatefulWorkflow;
public final fun defaultOnFailedCast (Ljava/lang/String;Lkotlin/reflect/KClass;Ljava/lang/Object;)V
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this the only change caught by apiDump?

public fun getCachedIdentifier ()Lcom/squareup/workflow1/WorkflowIdentifier;
public abstract fun initialState (Ljava/lang/Object;Lcom/squareup/workflow1/Snapshot;)Ljava/lang/Object;
public fun initialState (Ljava/lang/Object;Lcom/squareup/workflow1/Snapshot;Lkotlinx/coroutines/CoroutineScope;)Ljava/lang/Object;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,45 @@ public interface BaseRenderContext<out PropsT, StateT, in OutputT> {
* given [update] function, and immediately passes it to [actionSink]. Handy for
* attaching event handlers to renderings.
*
* It is important to understand that the [update] lambda you provide here
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it!

* may not run synchronously. This function and its overloads provide a short cut
* that lets you replace this snippet:
*
* return SomeScreen(
* onClick = {
* context.actionSink.send(
* action { state = SomeNewState }
* }
* }
* )
*
* with this:
*
* return SomeScreen(
* onClick = context.eventHandler { state = SomeNewState }
* )
*
* Notice how your [update] function is passed to the [actionSink][BaseRenderContext.actionSink]
* to be eventually executed as the body of a [WorkflowAction]. If several actions get stacked
* up at once (think about accidental rapid taps on a button), that could take a while.
*
* If you require something to happen the instant a UI action happens, [eventHandler]
* is the wrong choice. You'll want to write your own call to `actionSink.send`:
*
* return SomeScreen(
* onClick = {
* // This happens immediately.
* MyAnalytics.log("SomeScreen was clicked")
*
* context.actionSink.send(
* action {
* // This happens eventually.
* state = SomeNewState
* }
* }
* }
* )
*
* @param name A string describing the update, included in the action's [toString]
* as a debugging aid
* @param update Function that defines the workflow update.
Expand Down
Loading
Loading