The AppDNA Android SDK is designed to work reliably in offline and degraded network conditions. It uses a layered configuration strategy and persistent event queue to ensure your app functions correctly regardless of connectivity.
Configuration Priority
The SDK resolves configuration using the following priority order:
| Priority | Source | Description |
|---|
| 1 | Remote (Firestore) | Live configuration fetched from Firebase Firestore |
| 2 | Cached (SharedPreferences) | Previously fetched config stored locally |
| 3 | Bundled (assets) | Static config file bundled with the app |
When the SDK initializes, it attempts to fetch remote configuration. If the network is unavailable, it falls back to cached config. If no cache exists (first launch with no connectivity), it uses the bundled config.
SharedPreferences Cache
The SDK persists configuration data in SharedPreferences under the namespace ai.appdna.sdk.
Cache Keys
| Key | Description |
|---|
cache_flags | Feature flag configuration |
cache_experiments | Experiment assignments and variants |
cache_surveys | Survey configuration |
cache_paywalls | Paywall templates and products |
cache_onboarding | Onboarding flow definitions |
Cache TTL
The configTTL parameter in AppDNAOptions controls how long cached config is considered fresh. The default is 300 seconds (5 minutes). After this period, the SDK will attempt to fetch updated configuration from the remote source.
AppDNA.configure(
context = this,
apiKey = "adn_live_xxx",
environment = Environment.PRODUCTION,
options = AppDNAOptions(
configTTL = 600L // Cache for 10 minutes
)
)
Setting a longer configTTL reduces network requests but delays propagation of configuration changes. For most apps, the default of 300 seconds provides a good balance.
Bundled Configuration
For the best first-launch experience, bundle a configuration file with your app. This ensures the SDK has valid configuration even when the user has no network connectivity on first launch.
Setup
- Export your current configuration from the AppDNA console
- Place the file at
src/main/assets/appdna-config.json
The SDK automatically reads this file as the fallback when no cached or remote configuration is available.
Bundle Version
Access the current bundled config version:
val version = AppDNA.currentBundleVersion
// Returns Int
Update the bundled config file with each app release to minimize the gap between bundled and remote configuration. This ensures users always have a reasonable baseline on first launch.
Event Queue
The SDK queues events locally when they cannot be sent immediately. Events are persisted to SharedPreferences to survive app restarts.
Queue Behavior
| Setting | Value | Description |
|---|
| Max queue size | 10,000 | Maximum number of events stored locally |
| Persistence | SharedPreferences | Events survive app restarts |
| Auto-flush | Timer + batch | Events flush on timer interval or batch threshold |
Events are flushed automatically based on the flushInterval and batchSize values configured in AppDNAOptions. You can also call AppDNA.flush() to trigger an immediate flush.
When the event queue reaches 10,000 events, the oldest events are dropped to make room for new ones. Under normal conditions with regular connectivity, the queue should never reach this limit.
Networking
OkHttp Timeouts
The SDK uses OkHttp3 for all HTTP requests with the following timeout configuration:
| Timeout | Duration | Description |
|---|
| Connect | 10s | Maximum time to establish a connection |
| Read | 30s | Maximum time to read a response |
| Write | 30s | Maximum time to write a request |
Retry Strategy
Failed requests due to server errors (5xx) or network errors are retried with exponential backoff:
| Attempt | Delay |
|---|
| 1st | 1s |
| 2nd | 2s |
| 3rd | 4s |
After 3 failed attempts, the request is abandoned and events remain in the queue for the next flush cycle.
All requests include the following headers:
| Header | Value |
|---|
Authorization | Bearer {apiKey} |
Content-Type | application/json |
Device Identification
The SDK uses Settings.Secure.ANDROID_ID as the device identifier. This value 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:
| Scope | Dispatcher | Used For |
|---|
| IO | Dispatchers.IO | Networking, file I/O, SharedPreferences |
| Main | Dispatchers.Main | UI operations, billing callbacks |
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.
Next Steps