Supported on: iOS SDK
1.0.61+ · Android SDK 1.0.33+ · Flutter SDK 1.0.3+appdna_sdk Flutter package. The Flutter SDK delegates rendering, business logic, and storage to the native iOS and Android layers, so every method here resolves to native execution under the hood.
Core Methods
All core methods are static on theAppDNA class.
Config Methods
Session Data Methods
Cross-module key-value bag used by Console-configured template interpolation ({{session.*}}).
Consent + Traits
Experiment Methods
Push Methods (Static)
These static helpers mirrorAppDNA.push.* — use either style.
Presentation Methods
Web Entitlements
Deep Links
Module Namespaces
Access module-specific functionality through these static properties onAppDNA.
AppDNAPushModule
AppDNABilling
AppDNAOnboardingModule
AppDNAPaywallModule
AppDNARemoteConfigModule
AppDNAFeaturesModule
AppDNAExperimentsModule
AppDNAInAppMessagesModule
AppDNASurveysModule
AppDNADeepLinksModule
AppDNAScreenModule
Properties
Delegate Abstract Classes
Implement these abstract classes and register them with the corresponding module’ssetDelegate(...). Every setDelegate accepts a nullable delegate — pass null to clear.
AppDNAOnboardingDelegate
AppDNAPaywallDelegate
AppDNAPushDelegate
AppDNABillingDelegate
onPurchaseCompleted may fire later than the purchase, at least once: on Android at app start or at identify, for a purchase whose verification failed earlier or that was made outside the app (such a report has no paywall — an empty paywall_id, ""); on iOS for interrupted / Ask-to-Buy purchases, a purchase made on another device of the same Apple ID, or a late purchase made while another user was signed in (delivered when that user signs in). Late purchases are queued and delivered when you set a billing delegate, after identify or at app start. Grant idempotently by transaction['transactionId'].
AppDNAInAppMessageDelegate
shouldShowMessage is a veto — return false to suppress display before view construction and analytics.
AppDNASurveyDelegate
AppDNADeepLinkDelegate
shouldOpen is a veto — return false to suppress default deep-link handling (e.g., defer until the user has signed in).
AppDNAScreenDelegate
onScreenAction is a veto — return false to intercept the action and prevent default handling.
Key Types
AppDNAEnvironment
AppDNAOptions
vetoTimeout bounds the host hooks on the Flutter bridge (a non-positive value falls back to 5 s); sign-in actions in onBeforeStepAdvance always wait at least 120 s, or vetoTimeout if larger. Two native limits also apply: a refresh interaction always gets 8 s (a shorter vetoTimeout does not cut it short, and a larger one does not extend it), and an onBeforeStepRender reply is used only if it arrives within 3 s, whatever vetoTimeout is.
The iOS notification handler is configured with Info.plist keys, not runtime options (iOS only): AppDNADisableNotificationProxy (YES = do not install the launch-time notification proxy) and AppDNAForegroundPresentation (array of banner, list, sound, badge — how AppDNA pushes are presented in the foreground). See Push → How push works on iOS.
AppDNALogLevel
AppDNABillingProvider
AppDNABillingProvider is a class (not an enum): value-less providers are static consts, while adapty is a factory carrying the Adapty public SDK key.
onBeforeStepAdvance result (a Map)
onBeforeStepAdvance returns a plain Map<String, dynamic> — there is no StepAdvanceResult Dart class. {'type': 'proceed'} advances. {} advances every step except a sign-in action, which it blocks. The conceptual shapes:
onBeforeStepRender result (a Map)
onBeforeStepRender returns a Map<String, dynamic>? — there is no StepConfigOverride Dart class. Return null for no override, or a map of the fields to override:
PaywallAction values
There is noPaywallAction Dart class. The action argument passed to AppDNAPaywallDelegate.onPaywallAction(paywallId, action) is a plain String, one of:
All five cases are emitted from iOS SDK 1.0.84 and Android SDK 1.0.56. Before that,
cta_tapped was
sent by Android only — the iOS SDK never called onPaywallAction at all, so the same published
paywall reported a tap on one platform and nothing on the other — and the remaining four had no
emitter anywhere.🔴
plan_changed is not sent for the default selection. A paywall selects its default plan as
it appears; that is not a choice the user made, and reporting it would put one event at the top of
every paywall session that looks like engagement and is not.🔴 The action fires BEFORE the SDK decides whether the tap can do anything. A CTA tap that
cannot resolve a plan is reported and then does nothing else — no purchase_started, no purchase
callback. That ordering is what lets you tell “the SDK never received the tap” from “the purchase
was attempted and failed”, which is otherwise the same silence. custom behaves the same way: the
renderer still falls back to its default handling, and you are told it was an action you defined.Feature rows became tappable in iOS 1.0.84 / Android 1.0.56 so that feature_selected could exist. Nothing about them
looks or reads differently, and the paywall still scrolls — the row takes a tap, not a drag.DismissReason values
There is noDismissReason Dart class. The native SDK reports a dismiss reason in analytics events; on Flutter you receive the lifecycle via onPaywallDismissed. The reported string values are:
Screen dismissal result (a Map)
AppDNA.screen.show(...) resolves true when there was a host surface to present from — it is not the screen’s result. That is delivered to your AppDNAScreenDelegate.onScreenDismissed(screenId, result) as a Map<String, dynamic> with this shape:
Flow completion result (a Map)
AppDNA.screen.showFlow(...) resolves true when there was a host surface to present from — it is not the flow’s result. That is delivered to your AppDNAScreenDelegate.onFlowCompleted(flowId, result) as a Map<String, dynamic> with this shape:
LocationData
Result ofAppDNA.deepLinks.getLocationData(String fieldId) — the location a user picked for an onboarding location field:
A field the answer does not have is
null, never 0.0 or ''. A zone the lookup could not find leaves timezone and timezoneOffset both null — on every platform and surface; the SDK never makes up a 'UTC'. Code that read these fields as non-null must handle null (a Dart source change in Flutter 1.0.20). mapboxId is an opaque marker that the answer came from a permanent Mapbox geocode, not a stable place id to look up (for an interpolated address it encodes the request that produced it and runs to hundreds of characters). When a tapped suggestion cannot be resolved (with Mapbox, no permanent access on the account; with Google, the Geocoding API not enabled on the key; with either, any other provider error or a network failure), the answer is the typed text. On a resolved Mapbox answer, an address answer’s city is the place that contains the address, not the street name earlier releases stored; a place, locality, region or country answer keeps its own name as city; and a region answer also fills its own state / stateCode, a country answer its own country / countryCode, which earlier releases left empty. On a resolved Google answer, city is the first of the place’s locality, postal town, sublocality or second-level administrative area that Google reports — a city answer’s own name, an address answer’s town — so a region or country answer has no city: its own name is its state (a region) or its country (a country).
PaywallContext
OnboardingContext
Push notification payload (a Map)
There is no publicPushPayload Dart class. The notification argument delivered to the AppDNAPushDelegate callbacks (onPushReceived / onPushTapped) is a raw Map<String, dynamic> with camelCase keys:
Entitlement
PurchaseResult
ProductInfo
WebEntitlement
DeferredDeepLink
SurveyResult
SurveyAnswer
Notes
Architecture
The Flutter package exposes API facades, DTOs, and platform-channel bridges; rendering, business logic, network I/O, and storage execute in the native iOS (Swift) and Android (Kotlin) SDKs. New native behavior reaches Flutter via mechanical codegen — the Dart API surface stays continuously in sync with the native ones.Versioning
The Dart API surface stays continuously in sync with the bundled native renderer through mechanical codegen. CI enforces that the published Flutter package, the iOS SDK, and the Android SDK all advance together — every Flutter release ships with the matching renderer baked in. See the supported-versions box at the top of this page for the active versions.Platform-specific behavior
billingProviderapplies on both platforms.storeKit2(the default) is AppDNA’s own billing — StoreKit 2 on iOS, Google Play Billing on Android. UnderrevenueCatoradaptyAppDNA never finishes or acknowledges a transaction, and an SDK paywall tap fails withproviderNotAvailable— see Billing.requestPermission()onAppDNAPushModuletriggers the iOS APNs permission prompt or the Android 13+POST_NOTIFICATIONSruntime permission.shutdown()shuts the native SDK down on both platforms: it makes one last attempt to upload the queued events and releases native resources. Events that attempt cannot send stay on the device — Android hands them to a background upload once that attempt has finished, and both platforms send them after the nextconfigure. Callconfigureagain before using the SDK.offerTokenonpurchase(...)is required for Android subscription offers (base-plan tokens) and ignored on iOS.

