Skip to main content
Supported on: iOS SDK 1.0.61+ · Android SDK 1.0.33+ · Flutter SDK 1.0.3+
This guide walks you through configuring the AppDNA SDK, identifying users, and tracking your first event in a Flutter application.

1. Configure the SDK

Initialize AppDNA as early as possible in your app lifecycle, typically in your main() function before runApp():
import 'package:firebase_core/firebase_core.dart';
import 'package:appdna_sdk/appdna_sdk.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await Firebase.initializeApp();

  await AppDNA.configure(
    apiKey: "adn_live_xxx",
    env: AppDNAEnvironment.production,
    options: AppDNAOptions(logLevel: AppDNALogLevel.debug),
  );

  runApp(const MyApp());
}
If you don’t have your own Firebase: Keep Firebase.initializeApp() as shown above.If you already use Firebase in your Flutter app: Remove the Firebase.initializeApp() call and just call AppDNA.configure(...). The SDK automatically initializes its own named Firebase instance from GoogleService-Info-AppDNA.plist (iOS) and google-services-appdna.json (Android). Your existing Firebase setup is not affected.
Call AppDNA.configure(...) exactly once before using any other SDK methods. Calling it multiple times will result in undefined behavior.

Configuration Options

The AppDNAOptions class lets you customize SDK behavior:
ParameterTypeDefaultDescription
flushIntervalint?30Seconds between automatic event flushes
batchSizeint?20Number of events to batch before flushing
configTTLint?3600Seconds before cached config is considered stale
logLevelAppDNALogLevel?AppDNALogLevel.warningVerbosity of SDK console logs
billingProviderAppDNABillingProvider?AppDNABillingProvider.storeKit2Billing integration to use

Environment

The AppDNAEnvironment enum controls which backend environment the SDK targets:
ValueDescription
AppDNAEnvironment.productionProduction API and configuration
AppDNAEnvironment.stagingStaging API for testing

Log Level

The AppDNALogLevel enum controls console log verbosity:
ValueDescription
AppDNALogLevel.noneNo logging
AppDNALogLevel.errorErrors only
AppDNALogLevel.warningErrors and warnings
AppDNALogLevel.infoErrors, warnings, and info
AppDNALogLevel.debugAll messages including debug
Use AppDNALogLevel.debug during development to see all SDK activity. Switch to AppDNALogLevel.warning or AppDNALogLevel.none for production builds.

Billing Provider

The AppDNABillingProvider enum specifies which billing system to use:
ValueDescription
AppDNABillingProvider.storeKit2Native StoreKit 2 on iOS, Play Billing on Android (default)
AppDNABillingProvider.revenueCatRevenueCat integration
AppDNABillingProvider.adaptyAdapty integration (pass API key via adaptyApiKey: option)
AppDNABillingProvider.noneDisable the billing module
On Android the SDK uses Google Play Billing under the StoreKit2 alias. RevenueCat is supported across both platforms when the native bridges are configured.

2. Wait for Ready State

The SDK fetches remote configuration asynchronously. Use onReady to know when the SDK is fully initialized:
await AppDNA.onReady(() {
  print("SDK ready -- remote config loaded");
});
onReady() accepts a callback that runs once the remote configuration has been fetched and applied.

3. Identify Users

Once a user signs in, call identify to associate events with their user ID:
await AppDNA.identify(
  "user-123",
  traits: {
    "plan": "premium",
    "signup_date": "2025-01-15",
  },
);
Traits are merged with any previously set traits. You do not need to pass all traits on every call — only the ones that have changed.

4. Track Events

Track user actions with track:
await AppDNA.track(
  "workout_completed",
  properties: {
    "duration": 45,
    "type": "strength",
  },
);
Events are batched and flushed automatically based on your flushInterval and batchSize settings.

5. Flush Events Manually

Force an immediate flush of all queued events:
await AppDNA.flush();
This is useful before the app enters the background or when you need to ensure events are sent immediately. Control whether the SDK collects and sends analytics data:
await AppDNA.setConsent(analytics: true);
When analytics is set to false, events are silently dropped and not queued. No data is sent to AppDNA servers until consent is granted.

7. Change Log Level at Runtime

Adjust the log level without reconfiguring the SDK:
AppDNA.setLogLevel("debug");
Valid levels: "none", "error", "warning", "info", "debug".

8. Remote Config and Feature Flags

Retrieve server-side configuration values:
final welcomeMessage = await AppDNA.getRemoteConfig("welcome_message");
Check whether a feature flag is enabled:
final darkModeEnabled = await AppDNA.isFeatureEnabled("dark_mode");

9. Experiments

Get the variant assigned to a user for an experiment:
final variant = await AppDNA.getExperimentVariant("paywall_test");
Check if the user is in a specific variant:
final isInVariantB = await AppDNA.isInVariant("paywall_test", "b");
Inspect all experiment exposures collected during the current session:
final exposures = await AppDNA.experiments.getExposures();
for (final exposure in exposures) {
  print("${exposure['experimentId']}${exposure['variant']}");
}

10. Session Data

Store cross-module session data that can be used in template interpolation across onboarding flows, paywalls, and in-app messages:
// Store session data
await AppDNA.setSessionData(key: 'selected_plan', value: 'premium');
await AppDNA.setSessionData(key: 'referral_code', value: 'FRIEND2026');

// Retrieve session data
final plan = await AppDNA.getSessionData(key: 'selected_plan'); // "premium"

// Clear all session data
await AppDNA.clearSessionData();
Session data values are accessible in Console-configured content using template syntax: {{session.selected_plan}}. See the Rich Media guide for the full template namespace ({{user.*}}, {{session.*}}, {{device.*}}, {{config.*}}).

11. Entitlements

Check whether the user has any active subscription (StoreKit, Play Billing, RevenueCat, or web entitlement):
final isSubscribed = await AppDNA.billing.hasActiveSubscription();
Subscribe to live entitlement updates so your UI reflects renewals, cancellations, and restored purchases without polling:
AppDNA.billing.onEntitlementsChanged.listen((entitlements) {
  final active = entitlements.where((e) => e.status == "active").toList();
  print("Active entitlements: ${active.length}");
});
See the Billing guide for the full purchase and restore flow.

12. Web Entitlement

Retrieve the current web entitlement (subscriptions granted outside the app store, e.g. via Stripe):
final ent = await AppDNA.webEntitlement;
Listen for web entitlement changes in real time:
AppDNA.onWebEntitlementChanged.listen((ent) {
  print("Web entitlement changed: $ent");
});
Check for a deferred deep link that brought the user to your app on first launch:
final link = await AppDNA.checkDeferredDeepLink();
if (link != null) {
  // Navigate to the linked content
}

14. Get SDK Version

Print the underlying native SDK version (useful for support tickets):
final version = await AppDNA.getSdkVersion();
print("Native SDK version: $version");

15. Reset on Logout

When a user signs out, call reset to clear the user identity and flush any remaining events:
await AppDNA.reset();
This clears the identified user, generates a new anonymous ID, and flushes queued events.

16. Shutdown

When the app is terminating, shut down the SDK to ensure all events are flushed and resources are released:
await AppDNA.shutdown();
On Android this releases native resources. On iOS this is a no-op because the SDK shuts down automatically when the process exits.
You now have the SDK configured with user identification, event tracking, remote config, experiments, entitlements, and deep links working. Continue to the module-specific guides for Push Notifications, Billing, Onboarding, and Paywalls.