Braze Android SDK Version
42.3.1
Steps To Reproduce
The issue is probabilistic, so a deterministic minimal reproduction is not available. Below is the call pattern and how we measured it.
-
During application startup (an androidx.startup Initializer, once per process), request the device ID:
suspend fun getBrazeDeviceIdAwait(): String {
return suspendCancellableCoroutine { cont ->
Braze.getInstance(context).getDeviceIdAsync(object : IValueCallback<String> {
override fun onSuccess(value: String) { cont.resume(value) }
override fun onError() { /* logged; never invoked in these cases */ }
})
}
}
Braze.configure(...) always runs before this call — the initializer above is ordered after our Braze setup initializer via androidx.startup dependency ordering, so the SDK configuration is complete at the time of the call.
-
Start a timer at the call site. If neither onSuccess nor onError arrives within 5 seconds, record it.
-
If a callback arrives later, record it with the elapsed time (only when it exceeds 3 seconds).
-
Include the values of Braze.isSdkDisabledOrDelayed and Braze.isDisabled in each record.
-
Collect the records from production for 3 days.
Pattern: the issue is most prominent on cold starts where the process is launched in the background (e.g., triggered by an FCM message). Once a process fails to receive a callback, every subsequent call within that same process lifetime fails consistently.
Expected Behavior
Per the getDeviceIdAsync API documentation, the device ID is "a randomly generated, app specific ID that is stored on the device". We expect the callback to arrive within a short time.
Actual Incorrect Behavior
On affected launches, the callback never arrives within the lifetime of the process. Of the calls with no callback within 5 seconds, approximately 95% never received one at all.
Measured over 3 days in a production app with millions of DAU, by unique devices that launched the app:
| Result |
% of devices that launched the app |
| At least one launch where the callback never arrived |
27.1% |
| Callback arrived with a delay of 3+ seconds |
3.1% |
When the callback did arrive late, the elapsed time was p50 3.9s / p90 10.3s for foreground launches, and p50 7.7s / p90 68s including background launches (elapsed time includes periods of Doze / app freezing).
Every no-callback record had isSdkDisabledOrDelayed=false and isDisabled=false.
Verbose Logs
Braze verbose logs from an affected device are not available — the issue is
probabilistic and occurs on production devices where verbose logging is not
enabled.
Our own instrumentation records the following for each affected call:
getDeviceIdAsync watchdog: no callback within 5s
- elapsed = 5002ms
- isSdkDisabledOrDelayed = false
- isDisabled = false
(caller: app startup initializer, BackgroundMode=true, NetworkType=Wi-Fi)
If verbose logs would help, let us know what to capture and we will try to
obtain them.
Additional Information
As a mitigation, we plan to cache the first successfully received value locally in the app, apply a timeout to the wait, and fall back to the cached value on timeout. Are there any potential caveats or risks with this approach? The API documentation states the ID regenerates only when app data is cleared or the app is reinstalled — are there any other cases where the value changes (e.g., SDK upgrades)?
Braze Android SDK Version
42.3.1
Steps To Reproduce
The issue is probabilistic, so a deterministic minimal reproduction is not available. Below is the call pattern and how we measured it.
During application startup (an androidx.startup
Initializer, once per process), request the device ID:Braze.configure(...)always runs before this call — the initializer above is ordered after our Braze setup initializer via androidx.startup dependency ordering, so the SDK configuration is complete at the time of the call.Start a timer at the call site. If neither
onSuccessnoronErrorarrives within 5 seconds, record it.If a callback arrives later, record it with the elapsed time (only when it exceeds 3 seconds).
Include the values of
Braze.isSdkDisabledOrDelayedandBraze.isDisabledin each record.Collect the records from production for 3 days.
Pattern: the issue is most prominent on cold starts where the process is launched in the background (e.g., triggered by an FCM message). Once a process fails to receive a callback, every subsequent call within that same process lifetime fails consistently.
Expected Behavior
Per the
getDeviceIdAsyncAPI documentation, the device ID is "a randomly generated, app specific ID that is stored on the device". We expect the callback to arrive within a short time.Actual Incorrect Behavior
On affected launches, the callback never arrives within the lifetime of the process. Of the calls with no callback within 5 seconds, approximately 95% never received one at all.
Measured over 3 days in a production app with millions of DAU, by unique devices that launched the app:
When the callback did arrive late, the elapsed time was p50 3.9s / p90 10.3s for foreground launches, and p50 7.7s / p90 68s including background launches (elapsed time includes periods of Doze / app freezing).
Every no-callback record had
isSdkDisabledOrDelayed=falseandisDisabled=false.Verbose Logs
Additional Information
As a mitigation, we plan to cache the first successfully received value locally in the app, apply a timeout to the wait, and fall back to the cached value on timeout. Are there any potential caveats or risks with this approach? The API documentation states the ID regenerates only when app data is cleared or the app is reinstalled — are there any other cases where the value changes (e.g., SDK upgrades)?