iOS · Android · SDK
Identity and data
How the SDK identifies app users, connects them to your accounts, and what it stores and sends.
Anonymous by default
On first configuration, the SDK creates an anonymous identity and stores it on the device. Later launches reuse it. Apps without their own accounts need nothing else and should not call identify. Feature plan votes belong to the identity; FAQs and alerts are the same for every identity.
Sign in
Pass your account ID during configuration when the user is already signed in at launch, or identify the user after sign-in:
| Platform | At configuration | After sign-in |
|---|---|---|
| iOS | try Joe.configure(withAPIKey:appUserID:) | try Joe.identify(appUserID:) |
| Android | Joe.configure(applicationContext, apiKey, appUserId = …) | Joe.identify(appUserId) |
The first identification keeps the anonymous ID, so votes cast before sign-in carry over to the account. Identifying the same account again changes nothing, and configuring without an app user ID keeps the stored identity, including a signed-in account. Devices identified with the same app user ID share their votes.
Choose an app user ID
- Stable and unique: an ID that belongs to exactly one account and never changes, such as your account's primary key.
- Not personal data: no email address or phone number.
- Not temporary: no placeholders, receipt IDs, or purchase SDK IDs that change after a restore, such as anonymous RevenueCat IDs.
- Not empty: a blank ID throws
MobileJoeError.invalidAppUserIDon iOS andIllegalArgumentExceptionon Android.
Sign out and switch accounts
After sign-out, call Joe.logout(). The SDK continues with new anonymous and device IDs; the account keeps its votes. Identifying a different account logs out first, so you do not need to call logout() before switching.
Identify requests
The SDK sends the identity to Mobile Joe in the background, once per app start and once per identity change. If that fails, the SDK stays usable and retries on the next app start.
Order of calls
Configure once at app start; further configure calls change nothing until the app relaunches. identify and logout() take effect immediately, in call order, and throw when the identity cannot be stored. identify also throws before configuration; logout() does too on Android, while on iOS it replaces the stored identity locally.
Identifiers
| Identifier | Format | Purpose |
|---|---|---|
| Anonymous ID | $MBJAnonymousID: followed by a random UUID as 32 hex characters |
Identifies the app user, for example for votes. Created without an account. |
| Device ID | $MBJDeviceID: followed by a random UUID as 32 hex characters |
Identifies the installation of an app user. It is generated by the SDK, not read from the hardware. |
| External ID | $MBJExternalID: followed by the first 32 hex characters of the unsalted SHA-256 hash of your app user ID |
Optional. Associates the app user with an account in your app across devices. Only set after configuring or identifying with an app user ID. |
The SDK never sends the app user ID itself. The hash is unsalted, so use an opaque account ID.
Local storage
Both SDKs store exactly one identity: the anonymous ID, the device ID, and the external ID if set.
| Platform | Location | Format |
|---|---|---|
| iOS | Application Support/MobileJoe/identity.mbj in the app container |
JSON file, written without a Data Protection class |
| Android | Private SharedPreferences file dev.mobilejoe.identity |
Keys anonymous_id, device_id, and external_id |
The SDKs store nothing else, apart from the iOS response cache.
Backups
The SDKs do not exclude the identity from backups. Platform rules apply:
- iOS: the Application Support directory is part of iCloud and computer backups. To exclude the identity, set
isExcludedFromBackupon theApplication Support/MobileJoedirectory. - Android: SharedPreferences are part of Auto Backup unless your app disables or limits it. To exclude the identity, add
dev.mobilejoe.identity.xmlin thesharedprefdomain to your backup rules.
<exclude domain="sharedpref" path="dev.mobilejoe.identity.xml" />
On Android 12 and higher, add the rule to cloud-backup and device-transfer in your data extraction rules. On earlier versions, add it to your full backup content rules.
Request metadata
Each request to the API contains these headers:
| Header | iOS | Android |
|---|---|---|
Authorization | Public SDK API key | Public SDK API key |
Identity-Anonymous-ID | Anonymous ID | Anonymous ID |
Identity-Device-ID | Device ID | Device ID |
Identity-External-ID | External ID, only when set | External ID, only when set |
Framework-Version | SDK version | SDK version |
App-Version | CFBundleShortVersionString | versionName |
App-Build-Version | CFBundleVersion | versionCode |
OS-Name | iOS | Android |
OS-Version | System version, for example 18.2.0 | API level, for example 35 |
Device-Model | Model identifier, for example iPhone16,2 | Build.MODEL, for example Pixel 8 |
Device-Brand | Not sent | Build.BRAND, for example google |
Preferred-Languages | Up to three languages of Locale.preferredLanguages at the first request | Up to three app locales at configuration |
Debug-Mode | debugMode passed to Joe.configure | isDebugMode passed to Joe.configure |
Requests also contain the parameters of the operation, such as a search term or a feature plan ID. See the REST API reference.
Response cache
The iOS SDK uses URLSession.shared, whose URLCache stores API responses in the Caches directory. The system can clear it at any time, and it is not backed up. The Android SDK does not cache responses.