Offline Behavior
Durable on-disk event queue, WorkManager retry, network-aware flush
The Convert Android SDK is built so tracking events are never lost, even on a flaky or absent network. This is an Android-specific capability with no equivalent in the request-scoped JS or PHP SDKs — it relies on on-device durable storage, WorkManager, and connectivity callbacks.
What happens when the network is down
flowchart TD
A[Decision / conversion call] --> B[Event enqueued in memory]
B --> C{Flush succeeds?}
C -->|Yes| D[Delivered to Convert]
C -->|No / offline| E[Persisted to app-private file]
E --> F[WorkManager job: convert-event-flush]
F -->|Network available| G[Retry with exponential backoff]
G --> D
H[Connectivity regained] -->|NetworkObserver| F
I[App returns to foreground] -->|restore| B
- Config fetch fails → the last successfully cached config is loaded from app-private storage (
filesDir/convert-sdk/config.json). If no cache exists either,runExperiencereturnsnulluntil the next successful fetch. One exception: a build configured with adebugTokennever reads or writes that cache, so a QA build has no offline cold-start fallback by design — see Configuration Options. - Tracking events generated while offline → persisted to a durable on-disk queue — a JSON file at
filesDir/convert-sdk/events.json, written atomically (tmp file + rename). The queue survives app restarts and device reboots. - Connectivity returns → a
NetworkObserver(usingConnectivityManager.registerDefaultNetworkCallback) drains the persisted queue and triggers a flush. - Backoff & retry → failed flushes reschedule via WorkManager with exponential backoff (10s base, doubling: 10s → 20s → 40s, capped). WorkManager survives app-kill and reboot, so an event enqueued today still ships after the user next launches the app.
Every outbound request the SDK makes — the foreground path and the background flush job alike — announces User-Agent: ConvertAgent/1.0. It is an SDK invariant applied after any caller-supplied headers, so it cannot be overridden; it is what exempts SDK traffic from the tracking server's bot filter. You do not need to configure anything for this.
Nothing is lost, and nothing is sent twice
Two properties are worth knowing when you reason about the queue:
- Claiming the queue is atomic. Every reader — the reconnect callback, the return-to-foreground restore, and the background flush job — takes the queue in a single locked read-and-remove step, so an event persisted at the same moment can never be silently dropped between the read and the delete. The lock is layered: one in-process lock shared by every queue instance over the same file (the flush worker usually runs in your app's process, so it is a second instance over the same file), plus a lock file for the multi-process case.
- A failed delivery puts the events back. If the background job claims the queue and the POST then fails or returns a non-2xx, it re-persists exactly what it claimed before asking WorkManager to retry. Restored events are matched against what is already queued in memory by visitor id and payload, so a duplicate is dropped while two different visitors' identical-looking events are both kept.
Storage locations
| What | Where |
|---|---|
| Offline event queue | filesDir/convert-sdk/events.json (app-private, mode 0700 parent dir) |
| Queue write lock | filesDir/convert-sdk/events.json.lock (sibling lock file; also a transient events.json.tmp during an atomic write) |
| Last-good config cache | filesDir/convert-sdk/config.json |
| Visitor id + sticky state | SharedPreferences file com.convert.sdk.visitor |
| Retry job | WorkManager unique work named convert-event-flush |
All paths are inside the app's private sandbox and are removed on app uninstall.
Foreground / background handoff
The SDK observes the process lifecycle:
- On background — it flushes the in-memory queue immediately, persists whatever survives the flush to
events.json, and enqueues aconvert-event-flushWorkManager job (with theREPLACEpolicy, so rapid background/foreground toggles collapse into one pending job). The job carries aNetworkType.CONNECTEDconstraint, so it stays idle until a network is available. - On foreground — it restores any events persisted on disk back onto the in-memory queue, then lets the normal batch timer ship them.
What you do not need to do
- No manual flush — the SDK batches and flushes on its own.
- No manual config refresh — the
dataRefreshIntervaltimer handles re-fetching while the app is in the foreground. - No WorkManager registration — the SDK registers itself via AndroidX App Startup.
What is not persistent
- Events for a visitor are stored in the app's private storage, so they are discarded on app uninstall or an explicit clear of app storage.
Permission note
The reconnect-triggered flush relies on ACCESS_NETWORK_STATE. Declare it for the best behavior:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />If the permission is absent, registerDefaultNetworkCallback throws a SecurityException that the SDK catches and swallows — it degrades gracefully. Queued events still ship on the next scheduled batch; only the immediate "flush on reconnect" push is skipped. See Installation.
Inspecting the queue during development
# See the pending flush job.
adb shell dumpsys jobscheduler | grep convert-event-flushYou can also use Android Studio's App Inspection → Background Task Inspector to watch the convert-event-flush worker. See Troubleshooting.
Related pages
- Tracking Control — disabling tracking interacts with the queue
- API Communication & Tracking — the shared batching/queue concept doc
- Persistent DataStore — how sticky decisions persist
Updated 28 days ago