Optimize flow & paywall fetching in React Native SDK

A reliable flow or paywall fetch on React Native does three things: renders fast, returns the audience-targeted variation, and falls back gracefully when the network is slow. The rules below cover the timing, caching, and fallback patterns to get there.

Tip

The rules assume adapty.activate() and adapty.identify() have already resolved. See Call order in React Native SDK.

Rules and pitfalls

Do thisDon’t do thisWhy
Fetch the placement you’re about to show, or warm the cache with preloadFlows (SDK 4.3.0+).Fan out your own concurrent getFlow calls on launch.A hand-rolled prefetch burst blocks the JS thread and produces a black screen. preloadFlows is built for this and sends the batch to the native SDK in one call.
Fetch getFlow after attribution has had a chance to resolve — for example, 1–2 seconds after activate or after onLatestProfileLoad fires.Call getFlow at the root component mount.Attribution hasn’t landed yet. The flow resolves against the default audience and silently bypasses segments and ASA personalization.
Set a loadTimeoutMs and configure a fallback paywall for every placement.Wait on getFlow indefinitely.Without a timeout, users on poor connectivity see a blank screen until the network resolves — or close the app.

See Fetch custom paywalls and products for the fetchPolicy and loadTimeoutMs parameter reference, and Placements for picking the right placement.

Preload placements

Info

preloadFlows and preloadFlowsForDefaultAudience are available starting from SDK version 4.3.0.

preloadFlows caches the flow JSON ahead of time — one request per placement. You then use it as usual: getFlow for the flow, createFlowView for its view.

fetchPolicy decides which layer a later getFlow reads first, not whether it can reach the cache at all:

  • 'return_cache_data_else_load' reads the preloaded copy first and goes to the network only when nothing is cached. 'return_cache_data_if_not_expired_else_load' does the same while the copy is younger than maxAgeSeconds.
  • The default, 'reload_revalidating_cache_data', goes to the network first and falls back to the preloaded copy when the request fails or times out.

A preload pays off either way, but differently: a cache-first policy removes the request, while the default keeps it and gains a warm copy to fall back on.

Use it when you know which placements the session will need but don’t want to show them yet — for example, right after activate and identify resolve, for the flow behind a button the user hasn’t tapped.

try {
  await adapty.preloadFlows(['onboarding', 'main_paywall'], {
    loadTimeoutMs: 5000,
  });
} catch (error) {
  // at least one placement failed to preload
}

// Later, when the user reaches the paywall:
const flow = await adapty.getFlow('main_paywall', {
  fetchPolicy: 'return_cache_data_else_load',
});

Parameters:

  • placementIds (required): the placements to preload.
  • loadTimeoutMs (optional): the timeout in milliseconds. Defaults to 5000, and values below 1000 are raised to 1000. On iOS, the timeout applies to the whole batch; on Android, to each placement.

Behavior worth knowing:

  • The promise rejects only after every placement has been attempted, and the error aggregates the per-placement failures. A failure for one placement doesn’t stop the others, and the placements that loaded stay in the cache.
  • If a placement times out or fails with a network error, the SDK falls back to the default-audience variation for that placement. Other failures are reported as-is.
  • Preloading only warms the cache. It doesn’t return content — you still call getFlow to display it.

What a preload covers

A flow reaches the screen in layers. A preload covers the first, exactly as getFlow does:

LayerFetched byWarmed by a preload
Flow JSON — the chosen variation, its product IDs, and the remote configgetFlowYes
UI layout — the screen’s structure, styling, and textcreateFlowViewNo
Images, including the still frame that stands in for a video elementcreateFlowView, in the backgroundNo
Video filesThe system player, as the screen rendersNot cached by the SDK

So the first createFlowView for a given layout still costs a round trip after a preload. The native SDK then keeps that layout in its disk cache, which survives app restarts, so the cost falls on the first request rather than on every one.

Skip audience segmentation

To warm the cache without waiting for audience segmentation at all, use the default-audience variant. It takes no timeout:

await adapty.preloadFlowsForDefaultAudience(['main_paywall']);

Show first-screen media from the app bundle

A flow downloads its images and videos from Adapty. To show the first screen’s media instantly, serve it from the app bundle instead. This is a good way to reuse media you already ship, such as the visuals of an existing native onboarding.

  1. In the Flow & Paywall Builder, set a custom media ID on the image or video. The file you upload there stays as the fallback.
  2. Add the file to the native projects: the iOS app bundle and the Android assets folder.
  3. When you create the flow view, pass the bundled file for that ID in customAssets:
// "welcome_video" is the custom media ID set in the Flow & Paywall Builder
const bundledAssets: Record<string, AdaptyCustomAsset> = {
  'welcome_video': {
    type: 'video',
    fileLocation: {
      ios: { fileName: 'welcome.mp4' },
      android: { relativeAssetPath: 'videos/welcome.mp4' },
    },
  },
};

const view = await createFlowView(flow, { customAssets: bundledAssets });

Bundled files add to your app’s download size, so bundle only the media users see first.

Media you don’t bundle still appears right away: the flow’s layout includes a small low-resolution copy of each image, including a video’s still frame, and shows it until the full file loads.

For the full customAssets reference, see Customize assets.

Tune for poor connectivity

For markets with consistently poor connectivity (rural areas, transit, regions affected by routing):

  • Set fetchPolicy: 'return_cache_data_else_load' on every fetch except the very first.
  • Configure a fallback paywall for every placement in the Adapty dashboard.
  • Set loadTimeoutMs to 3–5 seconds and accept the fallback when the timeout fires.
  • Don’t gate flow display on getProfile(). Call getFlow independently so a slow profile doesn’t block the UI.