render Workflow In
Launches the workflow in a new coroutine in scope and returns a StateFlow of its renderings and Snapshot. The workflow tree is seeded with initialSnapshot and the current value value of props. Subsequent values emitted from props will be used to re-render the workflow.
This is the primary low-level entry point into the workflow runtime. If you are writing an app, you should probably be using a higher-level entry point that will also let you define UI bindings for your renderings.
Initialization
When this function is called, the workflow runtime is started immediately, before the function even returns. The current value of the StateFlow is used to perform the initial render pass. The result of this render pass is used to initialize the StateFlow of renderings and snapshots that is returned.
Once the initial render pass is complete, the workflow runtime will continue executing in a new coroutine launched in scope.
Runtime Loop
After the first render pass, the runtime loop executes as follows:
suspendawaiting the application of an action.If DRAIN_EXCLUSIVE_ACTIONS is enabled, process any other exclusive actions synchronously.
Render Pass: call recurse the tree to call
render(). (If PARTIAL_TREE_RENDERING is enabled then only call this for dirty subtrees.)If CONFLATE_STALE_RENDERINGS is enabled, then continue to synchronously process any available actions and do another render pass.
Pass the updated rendering into the StateFlow returned from this method.
When there is UI involved, we recommend using a CoroutineDispatcher in scope's CoroutineContext that runs the above runtime loop until it is stable before the next 'frame', whatever frame means on your platform. Specifically, ensure that all dispatched coroutines are run before the next 'frame', so that the Workflow runtime has done all the work it can.
One way to achieve that guarantee is with an "immediate" dispatcher on the main thread - like, Dispatchers.Main.immediate - since it will continue to run until the runtime is stable before it lets any frame get updated by the main thread. However, if an "immediate" dispatcher is used, then only 1 action will ever be available since as soon as it is available it will resume (step #1 above) and start processing the rest of the loop immediately. This means that DRAIN_EXCLUSIVE_ACTIONS and CONFLATE_STALE_RENDERINGS will have no effect.
A preferred way to achieve that is to have your dispatcher drain coroutines for each frame explicitly. On Android, for example, that can be done with Compose UI's AndroidUiDispatcher.Main.
Scoping
The workflow runtime makes use of structured concurrency.
The runtime is started in scope, which defines the context for the entire workflow tree – most importantly, the Job that governs the runtime's lifetime and exception reporting path, and the kotlinx.coroutines.CoroutineDispatcher that decides on what thread(s) to run workflow code. Note that if the scope's dispatcher executes on threads different than the caller, then the initial render pass will occur on the current thread but all subsequent render passes, and actions, will be executed on that dispatcher. This shouldn't affect well-written workflows, since the render method should not perform side effects anyway.
All workers that are run by this runtime will be collected in coroutines that are children of scope. When the root workflow emits an output, onOutput will be invoked in a child of scope.
To stop the workflow runtime, simply cancel scope. Any running workers will be cancelled, and if onOutput is currently running it will be cancelled as well.
Error handling
If the initial render pass throws an exception, that exception will be thrown from this function. Any exceptions thrown from the runtime (and any workflows or workers) after that will bubble up and be handled by scope (usually by cancelling it).
Since the onOutput function is executed in scope, any exceptions it throws will also bubble up to scope. Any exceptions thrown by subscribers of the returned StateFlow will not cancel scope or cancel the runtime, but will be handled in the CoroutineScope of the subscriber.
Return
A StateFlow of RenderingAndSnapshots that will emit any time the root workflow creates a new rendering.
Parameters
The root workflow to render.
The CoroutineScope in which to launch the workflow runtime. Any exceptions thrown in any workflows, after the initial render pass, will be handled by this scope, and cancelling this scope will cancel the workflow runtime and any running workers. Note that any dispatcher in this scope will not be used to execute the very first render pass.
Specifies the initial PropsT to use to render the root workflow, and will cause a re-render when new props are emitted. If this flow completes after emitting at least one value, the runtime will not fail or stop, it will continue running with the last-emitted input. To only pass a single props value, simply create a MutableStateFlow with the value.
If not null or empty, used to restore the workflow. Should be obtained from a previous runtime's RenderingAndSnapshot.
An optional list of WorkflowInterceptors that will wrap every workflow rendered by the runtime. Interceptors will be invoked in 0-to-length order: the interceptor at index 0 will process the workflow first, then the interceptor at index 1, etc.
A function that will be called whenever the root workflow emits an OutputT. This is a suspend function, and is invoked synchronously within the runtime: if it suspends, the workflow runtime will effectively be paused until it returns. This means that it will propagate backpressure if used to forward outputs to a Flow or kotlinx.coroutines.channels.Channel, for example.
Configuration parameters for the Workflow Runtime.