Skip to content

1340: Changing RenderContext from an inner class to a nested class with its own generics #1341

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

Merged
merged 2 commits into from
Jun 10, 2025
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 @@ -27,7 +27,7 @@ class MaybeLoadingGatekeeperWorkflow<T : Any>(
override fun render(
renderProps: Unit,
renderState: IsLoading,
context: RenderContext
context: RenderContext<Unit, IsLoading, Unit>
): MayBeLoadingScreen {
context.runningWorker(isLoading.asTraceableWorker("GatekeeperLoading")) {
action("GatekeeperLoading") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class PerformancePoemWorkflow(
override fun render(
renderProps: Poem,
renderState: State,
context: RenderContext
context: RenderContext<Poem, State, ClosePoem>
): OverviewDetailScreen<*> {
if (simulatedPerfConfig.simultaneousActions > 0) {
repeat(simulatedPerfConfig.simultaneousActions) { index ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class PerformancePoemsBrowserWorkflow(
override fun render(
renderProps: ConfigAndPoems,
renderState: State,
context: RenderContext
context: RenderContext<ConfigAndPoems, State, Unit>
): OverviewDetailScreen<*> {
when (renderState) {
is Recurse -> {
Expand Down
7 changes: 3 additions & 4 deletions design-docs/compose-based-workflows-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ A more interesting, comprehensive example that ties this in with the First Primi
```kotlin
class IdentityWorkflow(
private val child: Workflow<Props, Output, Rendering>
) : StatelessWorkflow<Props, Output, Rendering {
) : StatelessWorkflow<Props, Output, Rendering> {
override fun render(props: Props, context: RenderContext): Rendering {
return context.renderComposable {
renderWorkflow(child, props, onOutput = { output ->
Expand Down Expand Up @@ -379,8 +379,7 @@ public abstract class ComposeWorkflow<
To implement the `Workflow` interface, we need to have a function that returns a `StatefulWorkflow` with the actual implementation. That's trivial: we just return a really simple workflow that does nothing but call `renderComposable` from above in its render method:

```kotlin
private inner class ComposeWorkflowWrapper :
StatefulWorkflow<PropsT, Unit, OutputT, RenderingT>() {
inner class ComposeWorkflowWrapper : StatefulWorkflow<PropsT, Unit, OutputT, RenderingT>() {

override fun initialState(
props: PropsT,
Expand All @@ -392,7 +391,7 @@ To implement the `Workflow` interface, we need to have a function that returns a
override fun render(
renderProps: PropsT,
renderState: Unit,
context: RenderContext
context: StatefulWorkflow.RenderContext<PropsT,Unit,OutputT>
): RenderingT = context.renderComposable {
// Explicitly remember the output function since we know that actionSink
// is stable even though Compose might not know that.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ object HelloComposeWorkflow : StatefulWorkflow<Unit, State, Nothing, HelloCompos
override fun render(
renderProps: Unit,
renderState: State,
context: RenderContext
context: RenderContext<Unit, State, Nothing>
): HelloComposeScreen = HelloComposeScreen(
message = renderState.name,
onClick = { context.actionSink.send(helloAction) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ object HelloWorkflow : StatefulWorkflow<Unit, State, Nothing, Rendering>() {
override fun render(
renderProps: Unit,
renderState: State,
context: RenderContext
context: RenderContext<Unit, State, Nothing>
): Rendering {
return Rendering(
message = renderState.name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ internal class ComposeWorkflowImpl<PropsT, OutputT : Any>(
override fun render(
renderProps: PropsT,
renderState: State<PropsT, OutputT>,
context: RenderContext
context: RenderContext<PropsT, State<PropsT, OutputT>, OutputT>
): ComposeScreen {
// The first render pass needs to cache the sink. The sink is reusable, so we can just pass the
// same one every time.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ object HelloWorkflow : StatefulWorkflow<Unit, State, Nothing, ComposeScreen>() {
override fun render(
renderProps: Unit,
renderState: State,
context: RenderContext
context: RenderContext<Unit, State, Nothing>
): ComposeScreen =
context.renderChild(HelloComposeWorkflow, renderState.name) { helloAction }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ object InlineRenderingWorkflow : StatefulWorkflow<Unit, Int, Nothing, Screen>()
override fun render(
renderProps: Unit,
renderState: Int,
context: RenderContext
context: RenderContext<Unit, Int, Nothing>
): ComposeScreen {
val onClick = context.eventHandler("increment") { state += 1 }
return ComposeScreen {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ object RecursiveWorkflow : StatefulWorkflow<Unit, State, Nothing, Screen>() {
override fun render(
renderProps: Unit,
renderState: State,
context: RenderContext
context: RenderContext<Unit, State, Nothing>
): Rendering {
return Rendering(
children = List(renderState.children) { i ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ object TextInputWorkflow : StatefulWorkflow<Unit, State, Nothing, Rendering>() {
override fun render(
renderProps: Unit,
renderState: State,
context: RenderContext
context: RenderContext<Unit, State, Nothing>
): Rendering = Rendering(
textController = if (renderState.showingTextA) renderState.textA else renderState.textB,
onSwapText = { context.actionSink.send(swapText) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ import kotlinx.parcelize.Parcelize
* Wraps [HelloBackButtonWorkflow] to (sometimes) pop a confirmation alert when the back
* button is pressed.
*/
object AreYouSureWorkflow :
StatefulWorkflow<Unit, State, Finished, Rendering>() {
object AreYouSureWorkflow : StatefulWorkflow<Unit, State, Finished, Rendering>() {
override fun initialState(
props: Unit,
snapshot: Snapshot?
Expand Down Expand Up @@ -64,7 +63,7 @@ object AreYouSureWorkflow :
override fun render(
renderProps: Unit,
renderState: State,
context: RenderContext
context: RenderContext<Unit, State, Finished>
): Rendering {
val ableBakerCharlie = context.renderChild(HelloBackButtonWorkflow, Unit) { noAction() }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ object HelloBackButtonWorkflow : StatefulWorkflow<Unit, State, Nothing, HelloBac
override fun render(
renderProps: Unit,
renderState: State,
context: RenderContext
context: RenderContext<Unit, State, Nothing>
): HelloBackButtonScreen {
return HelloBackButtonScreen(
message = "$renderState",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ object PoemListWorkflow : StatelessWorkflow<Props, Int, PoemListScreen>() {

override fun render(
renderProps: Props,
context: RenderContext
context: RenderContext<Props, Int>
): PoemListScreen {
return PoemListScreen(
poems = renderProps.poems,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class RealPoemWorkflow : PoemWorkflow,
override fun render(
renderProps: Poem,
renderState: SelectedStanza,
context: RenderContext
context: RenderContext<Poem, SelectedStanza, ClosePoem>
): OverviewDetailScreen<*> {
val previousStanzas: List<StanzaScreen> =
if (renderState == NO_SELECTED_STANZA) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class RealPoemsBrowserWorkflow(
override fun render(
renderProps: ConfigAndPoems,
renderState: SelectedPoem,
context: RenderContext
context: RenderContext<ConfigAndPoems, SelectedPoem, Unit>
): OverviewDetailScreen<*> {
val poems =
context.renderChild(PoemListWorkflow, Props(poems = renderProps.second)) { selected ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ object StanzaListWorkflow : StatelessWorkflow<Props, SelectedStanza, StanzaListS

override fun render(
renderProps: Props,
context: RenderContext
context: RenderContext<Props, SelectedStanza>
): StanzaListScreen {
val poem = renderProps.poem
return StanzaListScreen(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ object StanzaWorkflow : StatelessWorkflow<Props, Output, StanzaScreen>() {

override fun render(
renderProps: Props,
context: RenderContext
context: RenderContext<Props, Output>
): StanzaScreen {
with(renderProps) {
val onGoBack: (() -> Unit)? = when (index) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class DungeonAppWorkflow(
override fun render(
renderProps: Props,
renderState: State,
context: RenderContext
context: RenderContext<Props, State, Nothing>
): BodyAndOverlaysScreen<Screen, Overlay> = when (renderState) {
LoadingBoardList -> {
context.runningWorker(boardLoader.loadAvailableBoards()) { displayBoards(it) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class GameSessionWorkflow(
override fun render(
renderProps: Props,
renderState: State,
context: RenderContext
context: RenderContext<Props, State, Nothing>
): BodyAndOverlaysScreen<Screen, Overlay> = when (renderState) {
Loading -> {
context.runningWorker(boardLoader.loadBoard(renderProps.boardPath)) { StartRunning(it) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class TimeMachineAppWorkflow(

override fun render(
renderProps: BoardPath,
context: RenderContext
context: RenderContext<BoardPath, Nothing>
): ShakeableTimeMachineScreen {
val propsFactory = PropsFactory { recording ->
Props(paused = !recording)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class AiWorkflow(
override fun render(
renderProps: ActorProps,
renderState: State,
context: RenderContext
context: RenderContext<ActorProps, State, Nothing>
): ActorRendering {
context.runningWorker(renderState.directionTicker) { updateDirection }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ class GameWorkflow(
override fun render(
renderProps: Props,
renderState: State,
context: RenderContext
context: RenderContext<Props, State, Output>
): GameRendering {
val running = !renderProps.paused && !renderState.game.isPlayerEaten
// Stop actors from ticking if the game is paused or finished.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class PlayerWorkflow(
override fun render(
renderProps: ActorProps,
renderState: Movement,
context: RenderContext
context: RenderContext<ActorProps, Movement, Nothing>
): Rendering = Rendering(
actorRendering = ActorRendering(avatar = avatar, movement = renderState),
onStartMoving = { context.actionSink.send(StartMoving(it)) },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class ShakeableTimeMachineWorkflow<P, O : Any, out R : Screen>(
override fun render(
renderProps: PropsFactory<P>,
renderState: State,
context: RenderContext
context: RenderContext<PropsFactory<P>, State, O>
): ShakeableTimeMachineScreen {
// Only listen to shakes when recording.
if (renderState === Recording) context.runningWorker(shakeWorker) { onShake }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ internal class RecorderWorkflow<T>(
override fun render(
renderProps: RecorderProps<T>,
renderState: Recording<T>,
context: RenderContext
context: RenderContext<RecorderProps<T>, Recording<T>, Nothing>
): TimeMachineRendering<T> {
val value = when (renderProps) {
is RecordValue -> renderProps.value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class TimeMachineWorkflow<P, O : Any, out R>(

override fun render(
renderProps: TimeMachineProps<P>,
context: RenderContext
context: RenderContext<TimeMachineProps<P>, O>
): TimeMachineRendering<R> {
// Always render the delegate, even if in playback mode, to keep it alive.
val delegateRendering =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class BlinkingCursorWorkflow(
override fun render(
renderProps: Unit,
renderState: Boolean,
context: RenderContext
context: RenderContext<Unit, Boolean, Nothing>
): String {
context.runningWorker(intervalWorker) { setCursorShowing(it) }
return if (renderState) cursorString else ""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class HelloTerminalWorkflow : TerminalWorkflow,
override fun render(
renderProps: TerminalProps,
renderState: State,
context: RenderContext
context: StatefulWorkflow.RenderContext<TerminalProps, State, ExitCode>
Copy link
Contributor

Choose a reason for hiding this comment

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

Why the StatefulWorkflow prefix in some spots but not others?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think just sloppiness in my regex replacing 🤷🏻

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I prefer the StatefulWorkfow.RenderContext usage FWIW. but in most cases I think its unambiguous, so no need to be too strict.

): TerminalRendering {
val (rows, columns) = renderProps.size
val header = """
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class EditTextWorkflow : StatefulWorkflow<EditTextProps, EditTextState, String,
override fun render(
renderProps: EditTextProps,
renderState: EditTextState,
context: RenderContext
context: StatefulWorkflow.RenderContext<EditTextProps, EditTextState, String>
): String {
context.runningWorker(
renderProps.terminalProps.keyStrokes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class TodoWorkflow : TerminalWorkflow,
override fun render(
renderProps: TerminalProps,
renderState: TodoList,
context: RenderContext
context: StatefulWorkflow.RenderContext<TerminalProps, TodoList, ExitCode>
): TerminalRendering {
context.runningWorker(renderProps.keyStrokes) { onKeystroke(it) }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ object HelloWorkflow : StatefulWorkflow<Unit, State, Nothing, HelloRendering>()
override fun render(
renderProps: Unit,
renderState: State,
context: RenderContext
context: RenderContext<Unit, State, Nothing>
): HelloRendering {
return HelloRendering(
message = renderState.name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ object HelloWorkflow : StatefulWorkflow<Unit, State, Nothing, HelloRendering>()
override fun render(
renderProps: Unit,
renderState: State,
context: RenderContext
context: RenderContext<Unit, State, Nothing>
): HelloRendering {
return HelloRendering(
message = renderState.name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ object NestedOverlaysWorkflow : StatefulWorkflow<Unit, State, Nothing, Screen>()
override fun render(
renderProps: Unit,
renderState: State,
context: RenderContext
context: RenderContext<Unit, State, Nothing>
): Screen {
if (renderState.nuked) {
return ButtonBar(Button(R.string.reset, context.eventHandler("reset") { state = State() }))
Expand Down Expand Up @@ -126,7 +126,7 @@ object NestedOverlaysWorkflow : StatefulWorkflow<Unit, State, Nothing, Screen>()

override fun snapshotState(state: State) = null

private fun RenderContext.topBottomBar(
private fun RenderContext<Unit, State, Nothing>.topBottomBar(
top: Boolean,
renderState: State
): ButtonBar {
Expand All @@ -145,7 +145,7 @@ object NestedOverlaysWorkflow : StatefulWorkflow<Unit, State, Nothing, Screen>()
)
}

private fun RenderContext.toggleInnerSheetButton(
private fun RenderContext<Unit, State, Nothing>.toggleInnerSheetButton(
name: String,
renderState: State
) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ object StubVisibilityWorkflow : StatefulWorkflow<Unit, State, Nothing, OuterRend
override fun render(
renderProps: Unit,
renderState: State,
context: RenderContext
context: RenderContext<Unit, State, Nothing>
): OuterRendering = when (renderState) {
HideBottom -> OuterRendering(
top = ClickyTextRendering(message = "Click to show footer") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class RealAuthWorkflow(private val authService: AuthService) : AuthWorkflow,
override fun render(
renderProps: Unit,
renderState: AuthState,
context: RenderContext
context: RenderContext<Unit, AuthState, AuthResult>
): BackStackScreen<*> = when (renderState) {
is LoginPrompt -> {
BackStackScreen(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class RealRunGameWorkflow(
override fun render(
renderProps: Unit,
renderState: RunGameState,
context: RenderContext
context: RenderContext<Unit, RunGameState, RunGameResult>
): RunGameRendering =
when (renderState) {
is NewGame -> {
Expand Down Expand Up @@ -189,12 +189,14 @@ class RealRunGameWorkflow(
}
}

private fun RenderContext.playAgain() = safeEventHandler<GameOver>("playAgain") { oldState ->
private fun RenderContext<Unit, RunGameState, RunGameResult>.playAgain() = safeEventHandler<GameOver>(
"playAgain"
) { oldState ->
val (x, o) = oldState.playerInfo
state = NewGame(x, o)
}

private fun RenderContext.trySaveAgain() =
private fun RenderContext<Unit, RunGameState, RunGameResult>.trySaveAgain() =
safeEventHandler<GameOver>("trySaveAgain") { oldState ->
check(oldState.syncState == SAVE_FAILED) {
"Should only fire trySaveAgain in syncState $SAVE_FAILED, " +
Expand Down
Loading
Loading