Overview
Shared runtime model for Rheo mobile SDKs — channel resolve, events, terminal snapshots, and platform guides.
Purpose
Rheo ships four first-party mobile SDKs (Expo, bare React Native, Flutter, and SwiftUI — Flutter and SwiftUI coming soon). Each resolves flows by channel, runs the same flow state machine from @getrheo/flow-runtime, renders dashboard-authored screens, batches analytics, and emits terminal snapshots when a run completes or is abandoned.
Install exactly one React Native flavor — never both @getrheo/react-native-expo and @getrheo/react-native-bare.
Mental model
| Concept | What it means |
|---|---|
| Publishable key | ob_pk_test_* or ob_pk_live_* — identifies your app and environment. Create keys in App settings → SDK credentials. |
| Channel | Public id (ch_test_… / ch_live_…) — pass to <Flow channelId={…} /> or FlowView(channelId:). Rheo decides which flow revision (or experiment arm) to serve. |
| Resolve | POST /v1/sdk/resolve with Authorization: Bearer <key> and X-Rheo-Channel: <channelId>. Returns the manifest, branding, mediaMap, and assignment metadata. |
| Manifest | JSON flow definition — screens, layers, decisions, external surfaces. |
| Events | Automatic analytics (flow_started, step_viewed, …) batched to POST /v1/sdk/events. See Event catalog. |
| Terminal snapshot | Versioned FlowTerminalSnapshot on completion or abandonment — JSON.stringify for your API, CRM, or LLM. |
How resolve works
- The SDK sends your publishable key and channel id (plus identity:
userId,locale, etc.). - Rheo returns the manifest — either the channel's pinned version or an experiment arm bucketed by stable
userId. - Response includes
flowId,versionId,assignmentVersion,experimentId/variantIdwhen applicable — attached to every analytics event. mediaMapmapsmediaAssetId→ CDN URL for image and Lottie layers.
Caching
Resolve responses carry an ETag ("{assignmentVersion}-{versionId}"). The SDK caches per channel + locale on device (AsyncStorage on React Native). If-None-Match can return 304 and reuse the cached manifest until the assignment changes.
Prefetch
Warm the cache before showing a flow to skip the loading spinner. All SDKs support:
- Provider prefetch on
RheoProvidermount ("all"or specific channel ids). - Imperative prefetch from navigation listeners or button handlers.
- On-demand resolve when a flow mounts — owns error and retry UI.
Prefetch is best-effort and silent — it never emits flow_started or experiment exposure.
Platform-specific APIs: Expo, bare React Native.
Diagnostics and logging
All mobile SDKs default to silent console output in production. Opt in with the top-level logLevel prop on RheoProvider:
logLevel | Emits |
|---|---|
silent (default) | Nothing |
warn | Event transport failures, RevenueCat integration warnings |
debug | Above plus dev-only diagnostics (manifest dump, prefetch misuse hints) — requires a dev build |
React Native: logLevel="warn" on <RheoProvider />. Kotlin will use the same levels from @getrheo/contracts.
Production apps should omit logLevel unless you are actively debugging transport issues.
Events and batching
Events queue in memory and POST to /v1/sdk/events every ~5 seconds, when the buffer hits 500 events, or immediately on flow_completed / flow_abandoned. Multi-channel batches split into separate POSTs (one X-Rheo-Channel per batch).
The queue is in-memory only — force-quit or offline periods can drop non-terminal events. Terminal events prioritize immediate flush.
Full event reference: Event catalog.
Terminal snapshot
Callbacks:
| SDK | Completed | Abandoned |
|---|---|---|
| React Native | onFlowCompleted on <Flow /> / useFlow | onFlowAbandoned |
FlowTerminalSnapshot (schemaVersion: 1) fields:
| Field | Contents |
|---|---|
terminal | completed | abandoned |
occurredAt | ISO timestamp |
correlation | channelId, flowId, versionId, assignmentVersion, environment, experimentId, variantId |
subject | appUserId, optional customUserId, sessionId |
device | platform, locale, optional appVersion, customProperties |
answers | Normalized field map (auth secrets omitted) |
traits | Merged sdkAttributes + attribution at terminal time |
Optional flags: includeManifestInTerminalPayload, includePathInTerminalPayload, includeAnswerDetailInTerminalPayload.
Call abandon() (RN useFlow) to transition to abandoned explicitly. Unmounting mid-flow also enqueues flow_abandoned when not already terminal.
Layer kinds
All SDKs render these manifest layer kinds:
stack, text, image, lottie, video, icon, button, back_button, progress, loader, counter, single_choice, multiple_choice, text_input, scale_input, oauth_login, email_password_auth, carousel, hyperlink, checkbox
Auth layers require host handlers — Authentication layers.
External surfaces (RevenueCat paywalls) are graph nodes, not layers — RevenueCat integration.
Production defaults
Default API host: https://api.getrheo.io (RHEO_DEFAULT_SDK_API_BASE_URL in @getrheo/contracts). Omit apiBaseUrl / apiBaseURL in production unless you self-host.
Match key environment to channel environment — ob_pk_test_* with ch_test_*, ob_pk_live_* with ch_live_*.
Troubleshooting
Blank or stale flow
- Validate publishable key, channel public id, and environment alignment.
- Confirm the channel has an assigned published flow.
- Use a stable
userIdfor experiment bucketing. - After publish, cached manifests persist until
assignmentVersionchanges.
Resolve errors
| Situation | Meaning |
|---|---|
| Channel missing | No X-Rheo-Channel header |
| Channel not found | Unknown id or key/channel environment mismatch |
| Channel unassigned | No flow or experiment pinned |
| Channel archived | Unarchive in dashboard or switch channel |
Use fallback on <Flow /> / FlowView for host-owned recovery UI.
Indie MAU cap / billing suspension
Indie workspaces pause SDK traffic above 400 live MAU per UTC month. Past-due paid subscriptions block after grace. See Billing & subscriptions.
Local development API URL
| Runtime | Host loopback |
|---|---|
| iOS Simulator | http://localhost:4000 |
| Android Emulator | http://10.0.2.2:4000 |
Set apiBaseUrl / apiBaseURL in provider config during local dev.