> ## 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.

# Media preloading

> How the Expo SDK preloads a flow's images in screen order so users do not see loading states while navigating.

A flow full of images can flash blank placeholders as the user moves between screens. Media preloading fetches a flow's images ahead of the user in screen order, so each screen paints with its images already in the cache. It runs automatically when a session is created, and you can also drive it manually.

## Automatic preloading

When a session is created (and `mediaPreloadingEnabled` is not `false`), the SDK preloads media in the background using the shared `mediaPreloader`. It does not block presentation: the first screen shows as soon as it is ready while later screens warm behind it.

```tsx theme={null}
FlowPilot.configure({
  apiKey: 'fp_live_...',
  appId: 'your-app-id',
  mediaPreloadingEnabled: true, // default
});
```

The preloader walks media in priority order:

1. **Persistent zones first** (nav bar, footer, overlay), since they are visible on every screen.
2. **Screens in navigation order**, following edges from the entry node.
3. **Within a screen**, all images load concurrently; **between screens**, sequentially, so the next screen is prioritized over far-off ones.

Images are prefetched through `expo-image` (falling back to React Native's `Image.prefetch` if needed). Fonts are handled separately by the font manager.

## Manual preloading with progress

For a custom loading experience (a progress bar before a media-heavy flow), use the `MediaPreloader` directly. Create your own instance so you do not disturb the session's automatic preload.

```ts theme={null}
import { MediaPreloader } from '@flowpilotjs/react-native-sdk';

const preloader = new MediaPreloader();

const progress = await preloader.preload(
  session.flow,                 // FlowDefinition
  session.mediaBaseUrl,         // from the session
  session.iconBaseUrl,
  (p) => {
    console.log(`${Math.round(p.progress * 100)}% (${p.completedItems}/${p.totalItems})`);
  },
  { firstScreenOnly: false },   // true to bound to the first screen + zones
);

console.log('done', progress.isComplete, 'failed:', progress.failedItems);
```

### `preload(...)` signature

```ts theme={null}
preload(
  flow: FlowDefinition,
  mediaBaseUrl: string,
  iconBaseUrl: string,
  onProgress?: (progress: PreloadProgress) => void,
  options?: { firstScreenOnly?: boolean },
): Promise<PreloadProgress>
```

`preload` aborts any previous preload on the same instance before starting, and never rejects. Each `onProgress` call (one per screen group) and the resolved value are a `PreloadProgress`:

```ts theme={null}
interface PreloadProgress {
  totalItems: number;
  completedItems: number;   // includes failures (they still advance progress)
  failedItems: number;
  currentScreenIndex: number;
  progress: number;         // 0..1
  isComplete: boolean;
}
```

A failed image still counts toward `completedItems` (so progress always reaches 1) and is also counted in `failedItems`, so you can tell a fully-successful preload from a partial one.

Call `preloader.abort()` to cancel an in-progress preload (for example if the user leaves the screen).

## The `mediaPreloader` singleton

The same `mediaPreloader` singleton the SDK uses for automatic preloading is exported, alongside the `MediaPreloader` class. Prefer a fresh `new MediaPreloader()` for manual work, because calling `preload` on the singleton aborts the session's automatic preload (they share the instance).

## Relationship to prefetch

Media preloading and [prefetch](/expo-sdk/prefetching) overlap but serve different moments:

* **Media preloading** warms images for a flow you are **about to present** (a session already exists).
* **Prefetch `{ warmMedia: true }`** warms images for a flow you have **not presented yet**, bounded by `prefetchMediaStrategy`. It uses a fresh `MediaPreloader` internally so it never disturbs an active session.

## Common mistakes

* **Calling `preload` on the `mediaPreloader` singleton during a live session.** It aborts the session's own preload. Use `new MediaPreloader()` for manual work.
* **Awaiting the full preload before showing the flow.** Automatic preloading is non-blocking by design; do not gate presentation on it unless you intentionally want a pre-flow progress screen.
* **Expecting fonts in `PreloadProgress`.** The preloader handles images and icons; fonts load through the font manager separately.
* **Disabling preloading then wondering about flashes.** With `mediaPreloadingEnabled: false`, images load on demand as screens appear. Leave it on for smooth navigation.

## Troubleshooting

* **Images still flash on navigation.** The flow may have more images than warm in time on a slow network, or `mediaPreloadingEnabled` is `false`. Consider `prefetch(..., { warmMedia: true })` ahead of time, or a manual preload with a progress UI.
* **`failedItems` is non-zero.** Some image URLs failed to fetch (offline, 404, bad CDN URL). The flow still renders; those images fall back to blank. Check the URLs in the flow and the network.
* **Manual `preload` seems to do nothing.** Confirm you passed the session's `mediaBaseUrl` / `iconBaseUrl`, and that the flow actually references images.

## Related pages

* [Caching](/expo-sdk/caching)
* [Prefetching and readiness](/expo-sdk/prefetching)
* [Offline and bundled flows](/expo-sdk/offline-bundled-flows)
* [Configuration](/expo-sdk/configuration)
* [API reference](/expo-sdk/api-reference)
