Skip to content
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 @@ -43,6 +43,16 @@ import com.squareup.workflow.WorkflowAction.Updater
* when they're no longer needed. [Props][PropsT] propagate down the tree, [outputs][OutputT] and
* [renderings][RenderingT] propagate up the tree.
*
* ## Avoid capturing stale state
*
* Workflows may not perform side effects in their `render` methods, but may perform side effects by
* running [Worker]s and getting events from [RenderingT]s via [WorkflowAction]s. A [WorkflowAction]
* defines how to update the [StateT] and what [OutputT]s to emit. Actions get access to the current
* workflow's state, and they must use that view of the state. If an action is defined inline, it is
* incorrect to capture, or close over, the [StateT] passed to [render] in the action. Workflows are
* executed synchronously, but external events may not be, so captured state may be stale when the
* action is invoked.
*
* @param PropsT Typically a data class that is used to pass configuration information or bits of
* state that the workflow can always get from its parent and needn't duplicate in its own state.
* May be [Unit] if the workflow does not need any props data.
Expand Down
44 changes: 37 additions & 7 deletions workflow-core/src/main/java/com/squareup/workflow/Workflow.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,27 +38,57 @@ package com.squareup.workflow
* extends `Workflow`, and keep the implementation (e.g. is your workflow state*ful* or
* state*less* a private implementation detail.
*
* You should almost never implement [Workflow] directly, however. There are two abstract classes
* that you should subclass instead: [StatefulWorkflow] and [StatelessWorkflow]. The differences
* between them are described below, but both type have a `render` method that you implement to
* generate [renderings][RenderingT] from your [props][PropsT] and interact with the runtime (e.g.
* by changing state or emitting outputs).
*
* ### [Stateful Workflows][StatefulWorkflow]
*
* If your workflow needs to keep track of internal state, implement the [StatefulWorkflow]
* interface. That interface has an additional type parameter, `StateT`, and allows you to specify
* If your workflow needs to keep track of internal state, subclass [StatefulWorkflow]. It has an
* additional type parameter, `StateT`, requires you to specify
* [how to create the initial state][StatefulWorkflow.initialState] and how to
* [snapshot][StatefulWorkflow.snapshotState]/restore your state.
* [snapshot][StatefulWorkflow.snapshotState]/restore your state, and passes the current state to
* the [StatefulWorkflow.render] method.
*
* ### [Stateless Workflows][StatelessWorkflow]
*
* If your workflow simply needs to delegate to other workflows, maybe transforming propss, outputs,
* or renderings, extend [StatelessWorkflow], or just pass a lambda to the [stateless] function
* below.
* If your workflow does not have any state of its own and simply needs to delegate to other
* workflows (e.g. transforming props, outputs, or renderings), subclass [StatelessWorkflow] and
* implement its sole [StatelessWorkflow.render] method, or just pass a lambda to the [stateless]
* function.
*
* ## Interacting with Events and Other Workflows
* ## Interacting with events and other workflows
*
* All workflows are passed a [RenderContext] in their render methods. This context allows the
* workflow to interact with the outside world by doing things like listening for events,
* subscribing to streams of data, rendering child workflows, and performing cleanup when the
* workflow is about to be torn down by its parent. See the documentation on [RenderContext] for
* more information about what it can do.
*
* ## Things to avoid
*
* ### Mutable instance state
*
* Classes that implement [Workflow] should not contain mutable properties. Such properties are
* instance-specific state and can introduce buggy behavior. Instead, subclass [StatefulWorkflow]
* and move all your state to the workflow's `StateT` type. For example, setting a property will not
* cause your workflow to be re-rendered – the runtime has no way of knowing that some data it
* doesn't know about has changed. It can also break consumers of your workflows, who may be
* expecting to be able to re-use the same instance of your workflow type in different places in the
* workflow tree – this works if all the workflow's state is contained in its `StateT`, but not if
* the instance has its own properties. _(Note that storing dependencies, which are effectively
* static relative to the workflow instance, in properties is fine.)_
*
* ### Render side effects
*
* Workflows' `render` methods must not perform side effects or read mutable state. They can contain
* logic, but the logic must be based on the [PropsT], the `StateT` if present, and the renderings
* from other workflows. They must _declare_ what work to perform (via [Worker]s), and what data to
* render (via [RenderingT]s and child workflows). For this reason, programming with workflows can
* be considered declarative-style programming.
*
* @param PropsT Typically a data class that is used to pass configuration information or bits of
* state that the workflow can always get from its parent and needn't duplicate in its own state.
* May be [Unit] if the workflow does not need any props data.
Expand Down