Skip to main content
Supported on: Android SDK 1.0.33+
The AppDNA SDK is designed to work reliably in offline and low-connectivity environments. Configuration is cached locally, events are persisted to disk, and a bundled fallback config ensures the SDK functions even on first launch without network access.

Config Resolution Priority

The SDK resolves configuration using a three-tier priority system:
PrioritySourceDescription
1 (highest)Remote (Firestore)Firestore real-time listener for live configuration
2Cached (SharedPreferences)SharedPreferences-backed cache with TTL-based expiration
3 (lowest)Bundled (assets)Static appdna-config.json included in the app assets
The SDK uses a stale-while-revalidate pattern. Cached config is served immediately while a fresh copy is fetched from the remote source in the background.

Config TTL

The cached configuration has a time-to-live (TTL) that defaults to 3600 seconds (1 hour). You can customize this value in AppDNAOptions:
AppDNA.configure(
    context = this,
    apiKey = "adn_live_xxx",
    environment = Environment.PRODUCTION,
    options = AppDNAOptions(configTTL = 1800L) // 30 minutes
)
When the TTL expires, the SDK fetches a fresh config from Firestore on the next access. The stale config continues to be used until the fresh config is received.

Config Update Notifications

Listen for config updates by collecting the AppDNA.configUpdated coroutine flow:
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.flow.collect

lifecycleScope.launch {
    AppDNA.configUpdated.collect {
        Log.d("AppDNA", "Config updated — refreshing UI")
        // Re-read remote config values and update UI
    }
}
AppDNA.configUpdated is a SharedFlow<Unit> that emits whenever the SDK applies a new remote config bundle (initial load, TTL refresh, or live Firestore push). It mirrors iOS’s Notification.Name(AppDNA.configUpdated) and Flutter/RN bridge events.

Cache Keys

The SDK persists configuration data in SharedPreferences under the namespace ai.appdna.sdk. Per-module keys are isolated so partial failures (e.g., paywall config update mid-fetch) cannot leak state across modules:
KeyDescription
cache_flagsFeature flag configuration
cache_experimentsExperiment assignments and variants
cache_surveysSurvey configuration
cache_paywallsPaywall templates and products
cache_onboardingOnboarding flow definitions
cache_messagesIn-app message definitions

Bundled Config

For first-launch scenarios or environments with no network access, include a static configuration file in your app assets:
  1. In the AppDNA Console, navigate to SDK > Config Bundle.
  2. Download the appdna-config.json file.
  3. Place the file at src/main/assets/appdna-config.json.
app/
  src/main/assets/
    appdna-config.json    <-- Add this file
Access the current bundled config version programmatically:
val version = AppDNA.currentBundleVersion
The bundled config is a snapshot and will become stale over time. Always ensure the SDK can reach the remote config source for the latest configuration. The bundled config should be treated as a fallback only.

Event Queue

Events tracked with AppDNA.track(...) are not sent immediately. They are queued locally and flushed in batches.

Queue Behavior

SettingDefaultDescription
flushInterval30sTime interval between automatic flush cycles
batchSize20Number of events to batch before auto-flushing
Events are flushed when either condition is met (whichever occurs first). You can also call AppDNA.flush() to trigger an immediate flush.

Persistence

Events are persisted to disk immediately when tracked. This ensures that:
  • Events survive app crashes and force quits.
  • Events survive app restarts and device reboots.
  • Events are delivered on the next successful flush after connectivity is restored.
The event queue is stored in a local SQLite database under the SDK’s private namespace. Events remain queued until they are successfully delivered to the AppDNA backend or the user revokes analytics consent. Apps upgrading from older SDK versions automatically migrate legacy SharedPreferences-backed event queues on first launch.

Adaptive Batch Sizing

The SDK automatically adjusts batch size based on network conditions reported by ConnectivityManager.NetworkCapabilities:
  • Wi-Fi: 100 events per batch
  • Cellular: 50 events per batch
  • Metered cellular (roaming / data saver): 20 events per batch

Event Storage Limits

Events are stored locally with the following limits:
  • Maximum 10,000 events in queue
  • Maximum 5MB disk usage
Oldest events are dropped when limits are exceeded.

Networking

OkHttp Timeouts

The SDK uses OkHttp3 for all HTTP requests with the following timeout configuration:
TimeoutDurationDescription
Connect30sMaximum time to establish a connection (10s for background uploads)
Read30sMaximum time to read a response
Write30sMaximum time to write a request

Retry Strategy

When a flush attempt fails, the SDK retries with exponential backoff:
AttemptDelayDescription
11sFirst retry after initial failure
22sSecond retry
34sThird and final retry
After 3 failed attempts, the request is abandoned and events remain in the queue for the next flush cycle.

Error Handling

Status CodeBehavior
2xxSuccess. Events removed from queue.
4xxClient error. Events discarded immediately (no retry).
5xxServer error. Events kept in queue and retried.
Network errorEvents kept in queue and retried on next flush cycle.
Events that receive a 4xx response are discarded permanently. This prevents malformed events from blocking the queue. Check your event schemas if you see 4xx errors in the SDK logs.

Circuit Breaker

After 5 consecutive upload failures, event uploads are paused until the next app session. Server errors (4xx) immediately pause uploads for the current session.

Request Headers

All requests include the following headers:
HeaderValue
AuthorizationBearer {apiKey}
Content-Typeapplication/json
User-AgentAppDNA-Android/{sdk_version}
When analytics consent is set to false:
AppDNA.setConsent(analytics = false)
Events are silently dropped and are not added to the queue. No data is stored or transmitted. When consent is later granted, only events tracked after that point are queued and sent.
Consent state is persisted in SharedPreferences. If a user revokes consent and restarts the app, the SDK remembers the revoked state and continues to drop events until consent is explicitly granted again.

Secure Storage

The following data is stored using Android’s EncryptedSharedPreferences (Android Keystore-backed) for security:
DataStorageDescription
Push tokensEncryptedSharedPreferencesFCM device token
User IDsEncryptedSharedPreferencesIdentified user ID
Anonymous IDsEncryptedSharedPreferencesAuto-generated anonymous identifier
EncryptedSharedPreferences uses AES-256-GCM via the Android Keystore, so these values are protected by hardware encryption on supported devices.

Device Identification

The SDK derives a stable per-install identifier using Settings.Secure.ANDROID_ID combined with the SDK-generated anonymous ID. The anonymous ID is regenerated on AppDNA.reset(); ANDROID_ID is unique per app per device and persists across app reinstalls on the same device (unless the device is factory reset).

Coroutine Scopes

The SDK uses Kotlin coroutines with the following dispatcher assignments:
ScopeDispatcherUsed For
IODispatchers.IONetworking, file I/O, SharedPreferences
MainDispatchers.MainUI operations, billing callbacks
DefaultDispatchers.DefaultCPU-bound work (e.g., experiment bucketing, hash)
All SDK operations are non-blocking. Network requests and persistence operations run on the IO dispatcher, while UI-related callbacks (billing flows, paywall/onboarding rendering) are dispatched on the Main thread.

Summary

ConcernBehavior
Config on first launchBundled appdna-config.json used as fallback
Config cachingSharedPreferences with configurable TTL (default 3600s / 1 hour)
Config freshnessStale-while-revalidate; Firestore real-time listener
Event persistenceWritten to disk immediately, survives crashes and restarts
Event flushAutomatic on timer (30s) or batch threshold (20 events)
RetryUp to 3 retries with exponential backoff (1s, 2s, 4s)
4xx errorsEvents discarded, no retry
5xx / network errorsEvents retained, retried on next flush
Circuit breakerAfter 5 consecutive failures, uploads paused until next session
Consent revokedEvents dropped silently, not queued
Sensitive dataStored in EncryptedSharedPreferences (push tokens, user IDs, anon IDs)

Next Steps