> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getflowpilot.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Results and outcomes

> Read how a user finished a flow, observe screen changes, and pull final variable state from a session.

When a flow ends, FlowPilot tells you how it ended. The presenter's `onComplete` hands you a `FlowPresentationResult` with an `outcome`. For richer lifecycle observation (screen changes, state transitions), a session exposes a delegate.

## `FlowPresentationResult`

`<FlowPilotPresenter onComplete={...} />` gives you this shape:

```ts theme={null}
interface FlowPresentationResult {
  outcome: 'completed' | 'dismissed' | 'error';
  error?: Error; // populated when outcome === 'error'
}
```

| Outcome     | Meaning                                                                                                       |
| ----------- | ------------------------------------------------------------------------------------------------------------- |
| `completed` | The user reached the end of the flow, or a `closeFlow` action completed it.                                   |
| `dismissed` | The user closed the flow before finishing (for example a back-out or a `dismiss()`).                          |
| `error`     | FlowPilot had nothing to present, or the presentation failed before a screen showed. `error` holds the cause. |

```tsx theme={null}
<FlowPilotPresenter
  session={session}
  onComplete={(result) => {
    switch (result.outcome) {
      case 'completed':
        grantAccessOrContinue();
        break;
      case 'dismissed':
        // Respect the user's choice to skip.
        break;
      case 'error':
        console.warn('FlowPilot error:', result.error?.message);
        showYourOwnUI();
        break;
    }
    setSession(null);
  }}
/>
```

<Note>
  Unlike the iOS SDK's `FlowResult`, the Expo `FlowPresentationResult` carries only `outcome` and `error`. It does not bundle `finalVariables`, `screensVisited`, `durationMs`, or `experimentAssignments`. Read final variable state from the session (below), and observe experiment assignment through the [analytics callback](/expo-sdk/analytics) (`experiment_exposure`).
</Note>

## Reading final variable state

On the declarative path you hold the `FlowSession`, so you can read its variable store after completion. The session's `variableStore` is public.

```tsx theme={null}
<FlowPilotPresenter
  session={session}
  onComplete={(result) => {
    if (result.outcome === 'completed' && session) {
      const vars = session.variableStore.getAll();
      // e.g. { 'selected_plan': 'annual', 'opted_in': true }
      console.log('final variables', vars);
    }
    setSession(null);
  }}
/>
```

`variableStore.getAll()` returns a `Record<string, VariableValue>` of the flow's variables at the moment the flow ended. See [Variables and context](/expo-sdk/variables-and-context) for the value types and how to read individual keys.

## Observing the flow lifecycle

A `FlowSession` accepts a delegate for finer-grained events than the final outcome. Set it with `session.setDelegate(...)` before `session.start()`.

```ts theme={null}
type FlowSessionDelegate = {
  onScreenChange(
    screen: ScreenNode,
    transition: TransitionConfig,
    direction: 'forward' | 'back',
  ): void;
  onFlowComplete(outcome: 'completed' | 'dismissed' | 'error'): void;
  onStateChange(state: FlowSessionState): void;
};

type FlowSessionState = 'idle' | 'loading' | 'active' | 'completed' | 'error';
```

```tsx theme={null}
const session = await FlowPilot.createSession('onboarding');

session.setDelegate({
  onScreenChange: (screen, _t, direction) => {
    console.log(`Now on ${screen.name} (${direction})`);
  },
  onFlowComplete: (outcome) => {
    console.log('Flow finished:', outcome);
  },
  onStateChange: (state) => {
    console.log('State:', state);
  },
});

session.start();
```

<Warning>
  `<FlowPilotPresenter />` installs its own internal delegate to drive the modal and call `onComplete`. If you call `setDelegate(...)` yourself **and** render the same session through `FlowPilotPresenter`, the presenter's delegate wins, so prefer the presenter's `onComplete` / `onError` props when using the component. Use a custom delegate when you drive presentation yourself with the lower-level `FlowPresenter`.
</Warning>

## Useful session accessors

A live `FlowSession` exposes read-only state you can inspect:

```ts theme={null}
session.state          // 'idle' | 'loading' | 'active' | 'completed' | 'error'
session.currentScreen  // the active ScreenNode, or null
session.isFlowClosed   // true once the flow has ended
session.placementId    // the placement key this session was resolved for
session.flow           // the resolved FlowDefinition
```

## Common mistakes

* **Expecting `finalVariables` on the result object.** It is not there in this build. Read `session.variableStore.getAll()` instead.
* **Reading the session after you cleared it.** If you `setSession(null)` before reading `variableStore`, the reference may be gone. Read variables first, then clear.
* **Treating `dismissed` as failure.** A dismissal is a normal user choice, not an error. Only `error` indicates something went wrong.

## Related pages

* [Presenting placements](/expo-sdk/presenting-placements)
* [Variables and context](/expo-sdk/variables-and-context)
* [Analytics integration](/expo-sdk/analytics)
* [Error handling](/expo-sdk/error-handling)
* [API reference](/expo-sdk/api-reference)
