Every way you present a flow ends with a FlowResult. It tells you how the flow finished, which screens the user saw, what variables the flow captured, and which experiment variant (if any) the user got. Read it to branch your app logic, persist a captured choice, or attribute a conversion.
You get a FlowResult from:
- the awaited return of
presentPlacement(_:from:options:),
- the
completion of the callback overload,
- the
fallback overload’s return value,
- the
onResult closure of .flowPresenter,
await session.waitForCompletion() when embedding a FlowPresenterView.
The FlowResult shape
Outcomes
.completed means the flow reached its end (a closeFlow action or the final screen). This is the success case.
.dismissed means the user left before finishing (swiped away, a back action off the first screen). This is not a completion; do not treat it as one.
.error means FlowPilot had nothing presentable, or the presentation failed. Read error for the reason. The never-throws fallback overload returns this when it falls back to your native UI.
Reading captured variables
finalVariables maps each variable key to a VariableValue, an enum with typed accessors. Use the accessor for the type you expect; it returns nil if the value is absent or the wrong type.
Completion callbacks on the SDK
For analytics and errors that span flows, set closures on the SDK once. These are the confirmed, public callbacks:
See Analytics integration for the AnalyticsEvent shape and Error handling for error codes.
There is no global flow-completion or flow-dismissal callback to register. Learn how a specific presentation ended from its FlowResult (returned, delivered to completion, or passed to onResult), not from a global callback.
A FlowPilotDelegate protocol (flowPilotDidComplete / flowPilotDidDismiss / flowPilotDidFail) is declared public, but FlowPilot exposes no property to set a delegate and never calls these methods. It is not usable today. Use the FlowResult and the setAnalyticsCallback / setErrorCallback closures above.
Common mistakes
- Treating
.dismissed as success. A dismissed flow did not complete. Only .completed means the user reached the end.
- Assuming a variable exists.
finalVariables["key"] is optional, and so is the typed accessor. Always use optional binding or a default; never force-unwrap.
- Ignoring
error on .error. When outcome == .error, error carries the reason (for example flow_not_found, timeout). Log or branch on it.
- Reading the wrong variable type.
.boolValue on a string variable returns nil (unless the string coerces). Match the accessor to the variable’s declared type.
Related pages