EnfinitOSEnfinitOS
DevelopersConsumer render
Production-ready scaffold

Mobile SDK

iOS (Swift) + Android (Kotlin) + React Native (TurboModules + Fabric) + Flutter. App Tracking Transparency and Privacy Sandbox aware.

@enfinitos/sdk-mobileSubstrate MOBILESwift, Kotlin, TypeScript, Dart
Install

Get the SDK

npm install @enfinitos/sdk-mobile

About this status badge

Typed, tested, documented, and grounded in the 2026 platform reality. Awaiting first customer-integration validation.

README

The developer-facing documentation in full

Rendered from packages/sdks/mobile/README.md at build time — the same source the package ships with.

@enfinitos/sdk-mobile

EnfinitOS reference SDK for the MOBILE substrate. Four sibling implementations — iOS (Swift), Android (Kotlin), React Native (New Architecture), Flutter — all backed by the canonical Swift + Kotlin modules and built on top of @enfinitos/sdk-renderer-core.

Architecture

                    ┌──────────────────────────────────────────┐
                    │   @enfinitos/sdk-renderer-core (TS)      │
                    │   resolve / grant / event-ingest / health│
                    └──────────────────────────────────────────┘
                                       ▲
                                       │ bridges
                                       │
       ┌───────────────┬───────────────┼───────────────┬───────────────┐
       │               │               │               │               │
   ┌───────┐      ┌─────────┐    ┌────────────┐   ┌───────────┐
   │  iOS  │      │ Android │    │ React Nat. │   │  Flutter  │
   │ Swift │      │ Kotlin  │    │ TurboModule│   │   Dart    │
   └───────┘      └─────────┘    └────────────┘   └───────────┘
       │               │               │               │
       │               │               │               │
   AppTracking-     Privacy        Both iOS &      MethodChannel
   Transparency,    Sandbox        Android via     to canonical
   SKAdNetwork v4,  (Topics +      Codegen spec    Swift / Kotlin
   Universal Links  AR API),       (Native…Spec.ts) modules.
                    Topics API,
                    App Links

The Swift + Kotlin modules are canonical: every feature gets implemented there first. React Native + Flutter wrap them via TurboModule (RN 0.76+ New Architecture) and MethodChannel respectively.

Getting started

iOS (Swift)

Add the package to your Xcode project via SwiftPM:

.package(url: "https://github.com/enfinitos/sdk-mobile-ios.git", from: "0.0.1")

The package ships its own PrivacyInfo.xcprivacy; SwiftPM bundles it into your app's resources at build time. Add the NSUserTrackingUsageDescription to your app's Info.plist:

<key>NSUserTrackingUsageDescription</key>
<string>We use anonymous data to show relevant ads.</string>

30-line minimum integration:

import EnfinitOSMobile

let client = EnfinitOSMobileClient(
    apiBaseUrl: URL(string: "https://api.enfinitos.com")!,
    appId: "com.acme.shop",
    deviceId: sessionDeviceId,
    apiKey: jwt,
)
try await client.start()
_ = await client.requestTrackingAuthorization()

let slot = MobileAdSlot(
    position: .banner,
    surfaceId: "home-feed-card-7",
    size: SurfaceSize(widthPx: 320, heightPx: 50),
)
if let resolved = try await client.fetchAdSlot(slot) {
    renderInBannerSurface(resolved.asset)
    await client.reportImpression(resolved.asset)
}

client.stop()

Android (Kotlin)

Gradle:

implementation("com.enfinitos:sdk-mobile:0.0.1")

Manifest:

<!-- For App Links click-through tracking. -->
<intent-filter android:autoVerify="true">
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="https" android:host="link.enfinitos.com" />
</intent-filter>

30-line minimum integration:

val client = EnfinitOSMobileClient(
    context = applicationContext,
    apiBaseUrl = "https://api.enfinitos.com",
    appId = "com.acme.shop",
    deviceId = sessionDeviceId,
    apiKey = jwt,
)
client.start()

val slot = MobileAdSlot(
    position = AdSlotPosition.BANNER,
    surfaceId = "home-feed-card-7",
    size = SurfaceSize(320, 50),
)
val resolved = client.fetchAdSlot(slot)
if (resolved != null) {
    renderInBannerSurface(resolved.asset)
    client.reportImpression(resolved.asset)
}

client.stop()

React Native (0.76+ New Architecture)

npm install @enfinitos/sdk-mobile-react-native
cd ios && pod install

The TurboModule spec lives in src/NativeEnfinitOSMobileSpec.ts; the Codegen pipeline picks it up automatically at build time.

import { EnfinitOSMobileClient } from '@enfinitos/sdk-mobile-react-native';
import NativeEnfinitOSMobile from '@enfinitos/sdk-mobile-react-native/NativeEnfinitOSMobileSpec';

const client = new EnfinitOSMobileClient({
  apiBaseUrl: 'https://api.enfinitos.com',
  appId: 'com.acme.shop',
  deviceId: sessionDeviceId,
  apiKey: jwt,
  bridge: makeBridgeFromTurboModule(NativeEnfinitOSMobile),
});
await client.start();
const resolved = await client.fetchAdSlot('banner');

Flutter

dependencies:
  enfinitos_mobile: ^0.0.1
import 'package:enfinitos_mobile/enfinitos_mobile.dart';

final client = EnfinitOSMobileClient(
  apiBaseUrl: 'https://api.enfinitos.com',
  appId: 'com.acme.shop',
  deviceId: sessionDeviceId,
  apiKey: jwt,
);
await client.start();
final resolved = await client.fetchAdSlot(const MobileAdSlot(
  position: AdSlotPosition.banner,
  surfaceId: 'home-feed-card-7',
));

2026 platform notes

iOS

  • Minimum target: iOS 17. Buys us the Observable macro, Swift Concurrency 5.10's stricter Sendable diagnostics, and SKAdNetwork v4's coarse-grain conversion callbacks without #available shims. iOS 16-targeted apps stick to v0.0.x; v0.1 onwards requires 17.
  • Swift Concurrency is mandatory. Every async method is async throws. No (Result<T>) -> Void completion handlers in the public API. Combine publisher + AsyncSequence accessors are available for reactive code (ConsentManager.publisher, ConsentManager.changes).
  • App Tracking Transparency uses the four-state enum: notDetermined / restricted / denied / authorized. The SDK treats anything that isn't authorized as "do not ship IDFA".
  • PrivacyInfo.xcprivacy ships in the package — declares the four Required Reason API categories (UserDefaults C617.1, system boot time DDA9.1, file timestamp 35F9.1, active keyboard 54BD.1) and the data types the SDK puts on the wire (Advertising Data + Device ID under the user's tracking-consented state, Crash + Performance diagnostics unlinked + non-tracking).
  • SKAdNetwork v4 is the canonical post-IDFA attribution path. The conduit in SKAdNetwork.swift wires the host app's conversion events through SKAdNetwork.updatePostbackConversionValue (the iOS 16.1+ async-throws API). The deprecated v3 updateConversionValue: completion-handler path is never called.
  • visionOS is out of scope for this SDK — see packages/sdks/spatial-ar/visionos/ for the visionOS SDK.

Android

  • Minimum SDK: 33 (Android 13 Tiramisu). Target SDK 34 (Android 14, the Play Store floor since Aug 2024). Compile SDK 35 (Android 15 Vanilla Ice Cream) so the build picks up the latest stub APIs.
  • Kotlin Coroutines + Flow are the primary async surface. No RxJava, no LiveData. The consent state is exposed as StateFlow<ConsentState> so host apps using Jetpack Compose can collectAsState() it.
  • Privacy Sandbox is the modern attribution path: - Topics API: host app calls TopicsManager.getTopicsAsync (via the AndroidX wrapper); the SDK records the assignment on the consent envelope. - Attribution Reporting API: source + trigger registration helpers in PrivacySandbox.kt. The OS dispatches aggregate reports to the platform's AR endpoint; no per-event data crosses domains. - Protected Audience API: outcome-only event "remarketing- auction-won" the host app fires when its mediation layer reports a winning bid. All three are runtime-feature-gated; a pre-Privacy-Sandbox device (or one whose user has opted out at the OS level) sees the conduit return null and the SDK ships its events without the Sandbox fields.
  • AAID deprecation: AAID is still readable on Android 13+ but the SDK prefers Privacy Sandbox primitives. AAID reads happen through a reflective AdvertisingIdClient lookup so apps that don't link play-services-ads-identifier still compile.
  • Jetpack Compose is the supported UI primitive. The optional consent dialog (enfinitos-mobile-consent-ui artifact) is the only Compose-dependent surface; the core SDK has no UI.
  • Wear OS 5 uses Compose for Wear OS + Tiles 2.0; the wearables variant of this SDK lives under packages/sdks/wearables/ (future tranche).

React Native

  • 0.76+ New Architecture only. Fabric + TurboModules + Codegen. The Old Architecture path (NativeModules.* via async bridging) is deprecated and slated for removal in 0.80; this SDK ships no Old-Arch code.
  • Codegen spec lives in src/NativeEnfinitOSMobileSpec.ts. The React Native CLI picks it up at build time and generates: - Objective-C++ protocol stubs (iOS). - Kotlin abstract-class stubs (Android). - JS bindings the SDK's TS code imports.
  • Hermes is the supported JS engine in 2026 — the SDK does not rely on JSC-specific features.

Flutter

  • Flutter 3.22+ with Material 3 + Impeller renderer. Older Flutter versions stick to v0.0.x of the plugin.
  • Dart 3.4+. The plugin uses sealed classes for the consent state machine and records for ad-hoc tuples; both are 3.0+ features.
  • Federated plugin convention via plugin_platform_interface. A future enfinitos_mobile_web sibling can be added for the browser case without breaking the existing API.

Attribution model

In 2026 the IDFA/AAID-as-primary-key world is mostly dead. The SDK defaults to server-side / cohort-based attribution:

  • iOS: SKAdNetwork v4. The ad network registers as the source; postbacks arrive at a backend the operator runs (NOT at the host app). The SDK conduit only manages the on-device conversion-value update.
  • Android: Privacy Sandbox Attribution Reporting. Source + trigger registration happens on-device; the OS dispatches aggregate reports to the platform's AR endpoint.
  • Universal Links / App Links are the click-through path. The link carries (campaignId, creativeId, surfaceId, nonce) — never a user identifier. The platform's server-side attribution joins the receipt to the proof-of-play stream by tuple.

ProofReporter ships the consent envelope on every event but the advertising id is zeroed by default; it's only attached when the user is authorized on iOS or has not enabled limit-ad-tracking on Android, and consent.deviceIdAllowed == true.

Endpoint surface

SDK callPlatform endpointStatus
fetchAdSlotPOST /runtime/resolve then POST /runtime/grantexisting
reportImpression/Click/Skip/CompletePOST /runtime/event-ingestexisting
reportConversionPOST /runtime/event-ingest + (iOS) SKAdNetwork postback to network endpointexisting
setConsentStateside-effect on next eventexisting
parseClickThroughLink (host call)POST /runtime/event-ingest with kind=click-through-receiptexisting
Privacy Sandbox Attribution Reporting registerOS-mediated; aggregate reports posted to POST /runtime/ar-aggregateneeds future API work

Test plan

  • iOS: XCTest under ios/Tests/EnfinitOSMobileTests/. Run via xcodebuild test or in Xcode. The tests inject a fake URLProtocol so production code paths execute without socket traffic.
  • Android: JUnit 4 + kotlinx-coroutines-test under android/src/test/. Run via ./gradlew :sdk-mobile:testDebugUnitTest.
  • React Native: Vitest under react-native/__tests__/. Run via pnpm --filter @enfinitos/sdk-mobile-react-native test.
  • Flutter: flutter_test under flutter/test/. Run via flutter test.
API reference

Hit the HTTP surface directly

The Mobile SDK is a thin client over the same governed HTTP API every other SDK calls. The full OpenAPI 3.1 reference lives on the docs site.

Sandbox

Run this SDK against a real tenant

The hosted sandbox is the fastest way to verify Mobile SDK against a real EnfinitOS tenant before committing to a pilot. Launching Q4 2026.