> ## Documentation Index
> Fetch the complete documentation index at: https://docs.appdna.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# iOS Offline Support

> Config caching, event queue, and bundled configs

<Info>
  **Supported on:** iOS SDK `1.0.61+`
</Info>

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:

| Priority    | Source  | Description                                            |
| ----------- | ------- | ------------------------------------------------------ |
| 1 (highest) | Remote  | Firestore real-time listener for live configuration    |
| 2           | Cached  | UserDefaults-backed cache with TTL-based expiration    |
| 3 (lowest)  | Bundled | Static `appdna-config.json` included in the app bundle |

<Info>
  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.
</Info>

### 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`:

```swift theme={null}
AppDNA.configure(
    apiKey: "adn_live_xxx",
    environment: .production,
    options: AppDNAOptions(configTTL: 1800) // 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 using `NotificationCenter`:

```swift theme={null}
NotificationCenter.default.addObserver(
    forName: AppDNA.configUpdated,
    object: nil,
    queue: .main
) { notification in
    print("Config updated — refreshing UI")
    // Re-read remote config values and update UI
}
```

## Bundled Config

For first-launch scenarios or environments with no network access, include a static configuration file in your app bundle:

1. In the AppDNA Console, navigate to **SDK > Config Bundle**.
2. Download the `appdna-config.json` file.
3. Add the file to your Xcode project, ensuring it is included in your app target's **Copy Bundle Resources** build phase.

```
MyApp/
  Resources/
    appdna-config.json    <-- Add this file
```

<Warning>
  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.
</Warning>

## Event Queue

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

### Queue Behavior

| Setting         | Default | Description                                    |
| --------------- | ------- | ---------------------------------------------- |
| `flushInterval` | 30s     | Time interval between automatic flush cycles   |
| `batchSize`     | 20      | Number of events to batch before auto-flushing |

Events are flushed when either condition is met (whichever occurs first).

### 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.

<Note>
  The event queue is stored in the app's Application Support directory. Events remain queued until they are successfully delivered to the AppDNA backend or the user revokes analytics consent.
</Note>

### Adaptive Batch Sizing

The SDK automatically adjusts batch size based on network conditions:

* **Wi-Fi**: 100 events per batch
* **Cellular**: 50 events per batch
* **Expensive cellular (roaming)**: 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.

## Retry Policy

When a flush attempt fails, the SDK retries with exponential backoff:

| Attempt | Delay | Description                       |
| ------- | ----- | --------------------------------- |
| 1       | 1s    | First retry after initial failure |
| 2       | 2s    | Second retry                      |
| 3       | 4s    | Third and final retry             |

### Error Handling

| Status Code   | Behavior                                               |
| ------------- | ------------------------------------------------------ |
| 2xx           | Success. Events removed from queue.                    |
| 4xx           | Client error. Events discarded immediately (no retry). |
| 5xx           | Server error. Events kept in queue and retried.        |
| Network error | Events kept in queue and retried on next flush cycle.  |

<Warning>
  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.
</Warning>

### Circuit Breaker

After 5 consecutive upload failures, event uploads are paused until the next app session (a foreground event resets the failure count and resumes uploads). A single failure does not pause uploads. Every failed flush counts toward the consecutive-failure total — whether a 4xx batch was dropped or a 5xx/network error left the batch queued for retry — and only the 5th failure in a row trips the circuit breaker.

## Network Timeouts

| Operation          | Timeout |
| ------------------ | ------- |
| API requests       | 30s     |
| Resource downloads | 60s     |

## Consent and Offline Behavior

When analytics consent is set to `false`:

```swift theme={null}
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.

<Info>
  Consent state is persisted. 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.
</Info>

## Secure Storage

The following data is stored in the iOS Keychain for security:

| Data          | Storage  | Description                         |
| ------------- | -------- | ----------------------------------- |
| Push tokens   | Keychain | APNs device token                   |
| User IDs      | Keychain | Identified user ID                  |
| Anonymous IDs | Keychain | Auto-generated anonymous identifier |

Keychain storage ensures these values persist across app reinstalls and are protected by the device's hardware encryption.

## Summary

| Concern                | Behavior                                                   |
| ---------------------- | ---------------------------------------------------------- |
| Config on first launch | Bundled `appdna-config.json` used as fallback              |
| Config caching         | UserDefaults with configurable TTL (default 3600s)         |
| Config freshness       | Stale-while-revalidate; Firestore real-time listener       |
| Event persistence      | Written to disk immediately, survives crashes and restarts |
| Event flush            | Automatic on timer (30s) or batch threshold (20 events)    |
| Retry                  | Up to 3 retries with exponential backoff (1s, 2s, 4s)      |
| 4xx errors             | Events discarded, no retry                                 |
| 5xx / network errors   | Events retained, retried on next flush                     |
| Consent revoked        | Events dropped silently, not queued                        |
| Sensitive data         | Stored in Keychain (push tokens, user IDs, anonymous IDs)  |
