React Native
Not released yet. The React Native SDK for e-volv Launch is built and conformance-tested, but its first release with flags is not on the registry yet. This page documents that release: until it ships, npm install @e-volv/logs-react-native finds either nothing or an earlier Observer-only version without flags. The SDKs index shows what can be installed today.
@e-volv/logs-react-native is the e-volv Launch SDK for React Native 0.74 and later, New or Old Architecture. It is a client SDK: with a public (evk_pub_…) key it receives evaluated values for the current context — never targeting rules — so a flag check is synchronous, never throws and never blocks on the network. A server (evk_) key is refused at init with one warning: it has no app-id allowlist and no per-install quota, so it must never ship in an app binary. Alongside the key, init() requires an appId — the Android application id or iOS bundle id — which the SDK sends as x-evolve-app-id. Values are cached on device, refreshed in the background, and exposures ride the same client as your logs. The behaviour is pinned by the Launch SDK contract and its delivery conformance suite runs in CI.
Install
npm install @e-volv/logs-react-native # iOS: cd ios && pod install # New Architecture: codegen runs from package.json's codegenConfig
Native API
import EvolveLogs from '@e-volv/logs-react-native';
EvolveLogs.init({
key: 'evk_pub_…', // public client key — the ONLY kind allowed in an app
appId: 'com.acme.shop', // Android application id / iOS bundle id
service: 'shop-app',
});
await EvolveLogs.flags.ready(); // optional: true once the first values arrived
if (EvolveLogs.flags.bool('checkout.new', false)) renderNewCheckout();
// On login/logout, re-identify: values for the new context are fetched and
// cached under the same key.
await EvolveLogs.flags.identify({
targetingKey: nextUser.id,
plan: nextUser.plan,
});Evaluation methods: bool, string, number, json and detail — all synchronous, all safe — plus typedDetail for the full answer with the type check applied. A flag absent from the served values returns your default with FLAG_NOT_FOUND; a value of the wrong type returns your default with TYPE_MISMATCH.
// The full answer when you need the variant and the reason:
const d = EvolveLogs.flags.detail('checkout.banner', 'default');
// d.value, d.variant ('on' | 'off' | 'control' | …), d.reason
// ('ROLLOUT' | 'TARGET_MATCH' | 'DEFAULT' | 'FLAG_NOT_FOUND' | …)One API, two engines
A flag read never crosses the bridge: whichever engine is active holds a snapshot of evaluated values in memory, and the JS side reads that snapshot. With the native module linked (the default), the native flags client is the single fetcher — it owns fetching, the on-device cache (contract §6), polling and lifecycle refresh, and pushes a new JSON snapshot over the bridge on every change. Without one — Expo Go, or nativeTransport: false — the shared @e-volv/flags-client core fetches with fetch, caches in @react-native-async-storage/async-storage when that optional peer is installed (hydrated before ready() resolves), and re-fetches when AppState turns active, collapsed to one refresh per 15 seconds. The two are never active at once.
Exposures are recorded on the JS side in both configurations — where reads happen — and drained in batches of up to 1,000, so a read is recorded exactly once and a FLAG_NOT_FOUND or TYPE_MISMATCH records nothing (contract §7). Before the first snapshot or cache entry exists, reads honestly return your default (degradation ladder L6).
nativeTransport: false also makes the JS layer ship log events itself with fetch — identity encoding, no disk queue. It exists for development, tests and the conformance runner; it is not a production configuration.
Options
Flags are configured under the flags key of the existing EvolveLogs.init() options:
| Option | Default | Meaning |
|---|---|---|
flags.enabled | true | Start the flags client. `false` gives a defaults-only stub; `flags: false` at init keeps flags off entirely (e.g. for a server key or an explicit opt-out). |
flags.mode | 'poll' | poll | offline. Client SDKs never stream; offline never contacts the control plane (bootstrap values or defaults only). The flags.mode property still reads poll while flags are enabled; it reads offline only with flags: false. |
flags.pollIntervalSeconds | 60 (min 15) | Poll period. The native client also refreshes on lifecycle events; the JS fallback re-fetches on AppState active. |
flags.url | derived | Base URL ending in /api/public/v1/flags; defaults to the origin of the Observer url option, else the e-volv cloud. |
flags.cache | on-device storage | Where the last-known values are kept: the native cache in the default mode, one AsyncStorage entry per key in the JS fallback. `false` disables. |
flags.bootstrap | none | A values map served before the first fetch completes. |
flags.context | {} | The initial context, set at init time. |
flags.exposures.enabled | true | Record an exposure on each evaluation of a flag present in the payload. |
flags.exposures.sampleRate | 1 | Probability an evaluation is recorded; sent as sampleRate. |
flags.exposures.dedupeWindowSeconds | 300 | Suppress repeats of the same (flag, variant, subject) within the window; suppressed counts ride the next exposure. The client default is 300 s (the server default is 60). |
flags.exposures.sendAttributes | false | Send context attributes with exposures (private attributes are always removed). |
flags.privateAttributes | [] | Attribute names never sent anywhere. |
Install check and lifecycle
// Install check: is this key bound, and to which environment?
const ping = await EvolveLogs.flags.verify();
// { environment: 'production', keyKind: 'client', flags: 42, etag: '3f2a…' }
// null when the control plane did not answer.flags.ready(timeoutMs) waits for the first values (only if you choose to), flags.onChange(fn) notifies with the keys whose values changed and returns an unsubscribe, flags.lastUpdatedAt reports the last confirmed update, and flags.mode reads 'poll' while flags are enabled (whatever flags.mode was set to) and 'offline' only with flags: false.
// Teardown (e.g. sign-out): release listeners, stop timers, flush // what reads recorded. The native client's own lifecycle continues. await EvolveLogs.flags.close(); // Or leave flags running and just flush everything pending: await EvolveLogs.flush(); // log events + recorded exposures
Troubleshooting by status code
Evaluation never breaks because of the control plane — it degrades. What each response means for the SDK (contract §2.6):
| Response | SDK behaviour |
|---|---|
401 / 404 | Key invalid or revoked, Launch not enabled for the workspace, or the key not bound to an environment. The SDK keeps serving held values or defaults, logs once, and retries every 5 minutes. |
403 — lacks the scope flags:read | Flags are disabled for this key with one warning; telemetry is unaffected. No retry until re-init. |
403 — app id refused | This appId is not on the environment key's allowlist — edited under the environment's Access action. Log once naming the app id, keep defaults, retry every 5 minutes. |
429 | Honours Retry-After (seconds). The delivery rate limit is 3,000 requests/min per key. |
5xx, timeout, network error | Retries with full-jitter exponential backoff while serving the last held values. |
Guarantees
- A flag check is a read of an in-memory snapshot — bounded by CPU, not the network;
ready()is the only call that waits, and only when you ask it to. - An unreachable or slow control plane never changes an answer mid-read: the last held values (or a valid cache, or your defaults) keep serving while the SDK backs off and retries.
- Cold start serves the on-device cache before the network answer is required — in the JS fallback the cache is hydrated before
ready()resolves. A cache from another context serves only until the first fetch for the new context completes, then is overwritten; a corrupt one is ignored and starts clean (L5–L7). - A server key can never leak into evaluation: init refuses it, warns once, and the client stays a no-op.
- Exposures are recorded exactly once per read, de-duplicated (300 s default window), sampled, batched at up to 1,000 per
POST /exposures— and aFLAG_NOT_FOUNDorTYPE_MISMATCHrecords nothing.
Next: the e-volv Launch overview for the concepts, the HTTP API page for the wire format the SDK speaks, and the SDKs index for every platform.