-
Notifications
You must be signed in to change notification settings - Fork 104
Add CoroutineScope to initialState; SessionWorkflow to aid rollout #1106
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
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
175 changes: 175 additions & 0 deletions
175
workflow-core/src/commonMain/kotlin/com/squareup/workflow1/SessionWorkflow.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
package com.squareup.workflow1 | ||
|
||
import kotlinx.coroutines.CoroutineScope | ||
|
||
/** | ||
* An extension of [StatefulWorkflow] that gives [initialState] a [CoroutineScope] | ||
* that corresponds with the lifetime of _session_ driven by this Workflow. | ||
* | ||
* A session begins the first time a parent passes a child [Workflow] of a particular type to | ||
* [renderChild] with a particular [key] parameter. It ends when the parent executes [render] | ||
* without making a matching [renderChild] call. The [CoroutineScope] that is passed to | ||
* [initialState] is created when a session starts (when [renderChild] is first called), and | ||
* [cancelled][kotlinx.coroutines.Job.cancel] when the session ends. | ||
* | ||
* This API extension exists on [StatefulWorkflow] as well, but it is confusing because the version | ||
* of [initialState] that does not have the [CoroutineScope] must also be implemented as it is | ||
* an abstract fun, even though it would never be used. | ||
* With this version, that confusion is removed and only the version of [initialState] with the | ||
* [CoroutineScope] must be implemented. | ||
*/ | ||
@WorkflowExperimentalApi | ||
public abstract class SessionWorkflow< | ||
in PropsT, | ||
StateT, | ||
out OutputT, | ||
out RenderingT | ||
> : StatefulWorkflow<PropsT, StateT, OutputT, RenderingT>() { | ||
|
||
/** | ||
* @see [StatefulWorkflow.initialState] for kdoc on the base function of this method. | ||
* | ||
* This version adds the following: | ||
* @param workflowScope A [CoroutineScope] that has been created when this Workflow is first | ||
* rendered and canceled when it is no longer rendered. | ||
* | ||
* This [CoroutineScope] can be used to: | ||
* | ||
* - set reliable teardown hooks, e.g. via [Job.invokeOnCompletion][kotlinx.coroutines.Job.invokeOnCompletion]. | ||
* | ||
* - own the transforms on a [StateFlow][kotlinx.coroutines.flow.StateFlow], | ||
* linking them to the lifetime of a Workflow session. For example, | ||
* here is how you might safely combine two `StateFlow`s: | ||
* | ||
* data class MyState( | ||
* val derivedValue: String, | ||
* val derivedWorker: Worker<String> | ||
* ) | ||
* | ||
* override fun initialState( | ||
* props: Unit, | ||
* snapshot: Snapshot?, | ||
* workflowScope: CoroutineScope | ||
* ): MyState { | ||
* val transformedStateFlow = stateFlow1.combine(stateFlow2, {val1, val2 -> val1 - val2}). | ||
* stateIn(workflowScope, SharingStarted.Eagerly, ${stateFlow1.value}-${stateFlow2.value}) | ||
* | ||
* return MyState( | ||
* transformedStateFlow.value, | ||
* transformedStateFlow.asWorker() | ||
* ) | ||
* } | ||
* | ||
* **Note Carefully**: Neither [workflowScope] nor any of these transformed/computed dependencies | ||
* should be stored by this Workflow instance. This could be re-created, or re-used unexpectedly | ||
* and should not have its own state. Instead, the transformed/computed dependencies must be | ||
* put into the [StateT] of this Workflow in order to be properly maintained. | ||
*/ | ||
public abstract override fun initialState( | ||
props: PropsT, | ||
snapshot: Snapshot?, | ||
workflowScope: CoroutineScope | ||
): StateT | ||
|
||
/** | ||
* Do not use this in favor of the version of [initialState] above that includes the Workflow's | ||
* [CoroutineScope] | ||
*/ | ||
public final override fun initialState( | ||
props: PropsT, | ||
snapshot: Snapshot? | ||
): StateT { | ||
error("SessionWorkflow should never call initialState without the CoroutineScope.") | ||
} | ||
} | ||
|
||
/** | ||
* Returns a [SessionWorkflow] implemented via the given functions. | ||
*/ | ||
@WorkflowExperimentalApi | ||
public inline fun <PropsT, StateT, OutputT, RenderingT> Workflow.Companion.sessionWorkflow( | ||
crossinline initialState: (PropsT, Snapshot?, CoroutineScope) -> StateT, | ||
crossinline render: BaseRenderContext<PropsT, StateT, OutputT>.( | ||
props: PropsT, | ||
state: StateT | ||
) -> RenderingT, | ||
crossinline snapshot: (StateT) -> Snapshot?, | ||
crossinline onPropsChanged: ( | ||
old: PropsT, | ||
new: PropsT, | ||
state: StateT | ||
) -> StateT = { _, _, state -> state } | ||
): SessionWorkflow<PropsT, StateT, OutputT, RenderingT> = | ||
object : SessionWorkflow<PropsT, StateT, OutputT, RenderingT>() { | ||
override fun initialState( | ||
props: PropsT, | ||
snapshot: Snapshot?, | ||
workflowScope: CoroutineScope | ||
): StateT = initialState(props, snapshot, workflowScope) | ||
|
||
override fun onPropsChanged( | ||
old: PropsT, | ||
new: PropsT, | ||
state: StateT | ||
): StateT = onPropsChanged(old, new, state) | ||
|
||
override fun render( | ||
renderProps: PropsT, | ||
renderState: StateT, | ||
context: RenderContext | ||
): RenderingT = render(context, renderProps, renderState) | ||
|
||
override fun snapshotState(state: StateT) = snapshot(state) | ||
} | ||
|
||
/** | ||
* Returns a [SessionWorkflow], with no props, implemented via the given functions. | ||
*/ | ||
@WorkflowExperimentalApi | ||
public inline fun <StateT, OutputT, RenderingT> Workflow.Companion.sessionWorkflow( | ||
crossinline initialState: (Snapshot?, CoroutineScope) -> StateT, | ||
crossinline render: BaseRenderContext<Unit, StateT, OutputT>.(state: StateT) -> RenderingT, | ||
crossinline snapshot: (StateT) -> Snapshot? | ||
): SessionWorkflow<Unit, StateT, OutputT, RenderingT> = sessionWorkflow( | ||
{ _, initialSnapshot, workflowScope -> initialState(initialSnapshot, workflowScope) }, | ||
{ _, state -> render(state) }, | ||
snapshot | ||
) | ||
|
||
/** | ||
* Returns a [SessionWorkflow] implemented via the given functions. | ||
* | ||
* This overload does not support snapshotting, but there are other overloads that do. | ||
*/ | ||
@WorkflowExperimentalApi | ||
public inline fun <PropsT, StateT, OutputT, RenderingT> Workflow.Companion.sessionWorkflow( | ||
crossinline initialState: (PropsT, CoroutineScope) -> StateT, | ||
crossinline render: BaseRenderContext<PropsT, StateT, OutputT>.( | ||
props: PropsT, | ||
state: StateT | ||
) -> RenderingT, | ||
crossinline onPropsChanged: ( | ||
old: PropsT, | ||
new: PropsT, | ||
state: StateT | ||
) -> StateT = { _, _, state -> state } | ||
): SessionWorkflow<PropsT, StateT, OutputT, RenderingT> = sessionWorkflow( | ||
{ props, _, workflowScope -> initialState(props, workflowScope) }, | ||
render, | ||
{ null }, | ||
onPropsChanged | ||
) | ||
|
||
/** | ||
* Returns a [SessionWorkflow], with no props, implemented via the given function. | ||
* | ||
* This overload does not support snapshots, but there are others that do. | ||
*/ | ||
@WorkflowExperimentalApi | ||
public inline fun <StateT, OutputT, RenderingT> Workflow.Companion.sessionWorkflow( | ||
crossinline initialState: (CoroutineScope) -> StateT, | ||
crossinline render: BaseRenderContext<Unit, StateT, OutputT>.(state: StateT) -> RenderingT | ||
): SessionWorkflow<Unit, StateT, OutputT, RenderingT> = sessionWorkflow( | ||
{ _, workflowScope -> initialState(workflowScope) }, | ||
{ _, state -> render(state) } | ||
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 18 additions & 0 deletions
18
workflow-core/src/commonMain/kotlin/com/squareup/workflow1/WorkflowExperimentApi.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package com.squareup.workflow1 | ||
|
||
import kotlin.RequiresOptIn.Level.ERROR | ||
import kotlin.annotation.AnnotationRetention.BINARY | ||
|
||
/** | ||
* This is used to mark new core Workflow API that is still considered experimental. | ||
*/ | ||
@Target( | ||
AnnotationTarget.CLASS, | ||
AnnotationTarget.PROPERTY, | ||
AnnotationTarget.FUNCTION, | ||
AnnotationTarget.TYPEALIAS | ||
) | ||
@MustBeDocumented | ||
@Retention(value = BINARY) | ||
@RequiresOptIn(level = ERROR) | ||
public annotation class WorkflowExperimentalApi |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.