Official Adfinia SDK for React Native / Expo. First-party event + identify
ingestion and native push, with the same wire contract as
@adfinia/sdk-web.
- Pure TypeScript core - no native modules. Analytics works in Expo Go and any RN runtime.
- Native push - FCM (Android) / APNs (iOS) via
expo-notifications(requires a development build). - Offline-durable - events persist to AsyncStorage and retry with backoff.
- Zero PII by default - device context is opt-in (
autoContext: true).
npx expo install @adfinia/sdk-react-native \
@react-native-async-storage/async-storage expo-crypto \
expo-localization expo-device expo-constants
# push only (optional):
npx expo install expo-notificationsimport { Adfinia } from '@adfinia/sdk-react-native'
// Once, at app start. The ingest host is baked in - do not configure it.
Adfinia.init({
writeKey: 'pk_live_xxx', // your tenant write key
autoContext: true, // opt in to device context
consent: () => userAllowedAnalytics,
})
// Identify - PredictStreet keys identity by the wallet hash (external_id).
Adfinia.identify({
externalId: walletHash,
traits: { source: 'sdk_react_native', country: 'AE', email: 'a@example.ae' },
})
// Track product events.
Adfinia.track('order_placed', { market_id: 'fifwc-fra-sen-2026', stake_usd: 50 })
// Screens (the mobile equivalent of page views).
Adfinia.screen('Markets')
// Drain before a critical moment.
await Adfinia.flush()
// Logout.
Adfinia.reset()| Method | Purpose |
|---|---|
init(config) |
Initialise. writeKey required. |
identify(idOrObj, traits?) |
Attach identity (customerId / externalId) + traits. |
track(event, props?, opts?) |
Record a product event. |
screen(name?, props?, opts?) |
Record a screen view (primary view event on mobile). |
page(name?, props?, opts?) |
Alias of screen for cross-SDK parity. |
setConsent(channels, status) |
Write-only consent. channels is a single channel string or an array; status is 'opted_in' or 'opted_out'. Channels are open strings (not an enum) - the backend owns the valid-channel registry. Emits one consent_updated event with channels always an array. No read method by design. |
optIn(channels) |
Shorthand for setConsent(channels, 'opted_in'). |
optOut(channels) |
Shorthand for setConsent(channels, 'opted_out'). |
alias(newId, prevId?) |
Merge an anonymous identity into a known one. |
reset() |
Clear identity, mint a new anonymous id (logout). |
flush() |
Drain the queue now. Returns a promise. |
registerPush(config?) |
Register for native push (see below). |
notifications.list({ status?, cursor?, limit? }) |
A page of in-app notifications: { data, nextCursor, hasMore }. See In-app inbox. |
notifications.markRead(id) / markAllRead() |
Mark one / all read. Returns Promise<boolean>. |
notifications.subscribe(handler, options?) |
Live subscription (SSE or long-poll). Returns { unsubscribe() }. |
notifications.trackOpened(n) / trackClicked(n) |
Emit notification_opened / notification_clicked track events. |
opts carries a per-call externalId and a context map (caller wins on key
collision).
Adfinia.init({
writeKey: string, // required
debug?: boolean, // log internals to console.debug
consent?: () => boolean, // gate; false drops the call silently
autoContext?: boolean, // collect device context (default false)
flushAt?: number, // batch size that triggers a flush (default 50)
flushIntervalMs?: number, // flush interval (default 5000)
maxQueueSize?: number, // max buffered events (default 1000)
})const res = await Adfinia.registerPush()
if (res.ok) {
// token registered with Adfinia; backend dispatches via FCM/APNs
} else {
// res.reason: 'unsupported' | 'permission_denied' | 'token_failed' | ...
}registerPush() uses expo-notifications getDevicePushTokenAsync() to get the
native device token (FCM on Android, APNs on iOS) and POSTs it to
/api/v1/push/register. Requirements:
expo-notificationsinstalled.- A development build (Expo Go cannot obtain native device tokens).
- FCM (
google-services.json) configured for Android; an APNs key + the Push capability for iOS.
See the SDK publishing/push runbook for the full account + credential setup.
Adfinia.notifications is a framework-agnostic data client for the in-app notification inbox. It ships no UI - you render the bell, list, and toasts; the SDK gives you typed notifications, a live subscription, and the open/click events.
// Page through the inbox (cursor-paginated).
const page = await Adfinia.notifications.list({ status: 'unread', limit: 20 })
if (page.hasMore) {
const next = await Adfinia.notifications.list({ cursor: page.nextCursor! })
}
// Subscribe to live updates. On connect the handler fires once per
// currently-unread notification (replay), then once per new one.
const sub = Adfinia.notifications.subscribe((n) => {
showToast(n) // { id, title, body, severity, deep_link?, ... }
})
// Record engagement + mark read when the user interacts.
Adfinia.notifications.trackOpened(n) // -> notification_opened
await Adfinia.notifications.markRead(n.id)
Adfinia.notifications.trackClicked(n) // -> notification_clicked
await Adfinia.notifications.markAllRead()
// Tear down on logout / unmount.
sub.unsubscribe()The InboxNotification shape is { id, contact_id, title, body, severity('info'|'success'|'warning'|'error'), dismissable, deep_link?, data?, read, created_at, read_at?, expires_at? }.
Identity. The inbox is keyed on the current contact - contact_id resolves from the active identity in the server's order (customer_id > external_id > anonymous_id). Call identify(...) first, or pass { contactId } to any method.
Live transport. React Native has no built-in EventSource. subscribe() uses a real SSE stream when an EventSource implementation is available, and otherwise long-polls the unread list:
// Preferred: pass an EventSource (e.g. react-native-sse) for a true stream.
import EventSource from 'react-native-sse'
const sub = Adfinia.notifications.subscribe(showToast, { eventSource: EventSource })
// No EventSource? It long-polls automatically; tune the interval:
const sub2 = Adfinia.notifications.subscribe(showToast, { pollIntervalMs: 30_000 })Both paths replay currently-unread on connect and de-dupe delivery by id, so the transport choice is invisible to your handler. Live SSE complements native push: registerPush() delivers background system notifications; the inbox stream powers the in-app list while the app is foregrounded.
import { usePathname } from 'expo-router'
import { useAdfiniaScreenTracking } from '@adfinia/sdk-react-native'
function RootLayout() {
useAdfiniaScreenTracking(usePathname())
// ...
}Identical to @adfinia/sdk-web: events POST to /api/v1/track[/batch] and
/api/v1/identify[/batch] with fields customer_id, external_id,
anonymous_id, event_name, properties, traits, context, occurred_at;
auth via Authorization: Bearer <writeKey> and X-Adfinia-SDK-Version. Server
identity resolution order: customer_id > external_id > anonymous_id.
MIT