✍️ Issue Description
When a ghost/silent push notification cold-starts the app process while IterableApi.initializeInBackground() is still running, handleMessageReceived crashes with an unhandled RuntimeException:
IterableApi must be initialized before calling getInAppManager().
Make sure you call IterableApi#initialize() in Application#onCreate
at IterableApi.getInAppManager (IterableApi.java:1003)
at IterableFirebaseMessagingService.handleMessageReceived
Root cause
initializeInBackground() is async by design, it starts initialization on a background thread and returns immediately. Internally, initialize() sets _applicationContext as its very first statement but creates inAppManager much later:
sharedInstance._applicationContext = context.getApplicationContext(); // set immediately
sharedInstance._apiKey = apiKey;
sharedInstance.retrieveEmailAndUserId();
// ...
sharedInstance.inAppManager = new IterableInAppManager(...); // set much later
The ghost push branch in handleMessageReceived uses getMainActivityContext() != null as its initialization guard:
if (notificationType != null && IterableApi.getInstance().getMainActivityContext() != null) {
switch (notificationType) {
case "InAppUpdate":
IterableApi.getInstance().getInAppManager().syncInApp();
This guard is insufficient.
Since initializeInBackground() is async, there is a race window between _applicationContext being set (line 1 of initialize()) and inAppManager being created (much later). If Firebase delivers onMessageReceived on its background thread and lands inside that window, getMainActivityContext() returns non-null (guard passes) but inAppManager is still null resulting in a crash.
This does not affect normal (non-ghost) pushes, that path never calls getInAppManager().
Suggested fix
Replace the insufficient guard with isSDKInitialized(), which correctly returns false while background initialization is still in progress:
// Before
if (notificationType != null && IterableApi.getInstance().getMainActivityContext() != null) {
// After
if (notificationType != null && IterableApi.isSDKInitialized()) {
Behavioral change with this fix: In this edge case the ghost push sync is silently dropped instead of crashing. The in-app sync for that message is missed until the next ghost push or login. This is better than crashing.
Alternatively, if the SDK prefers to leave this responsibility to the caller, then handleMessageReceived should be documented with an explicit warning and a safe deferred pattern using isSDKInitialized() and onSDKInitialized().
This is also related to #727 which reports the same RuntimeException and requests graceful failure instead of throwing. This new issue provides a concrete race condition that explains exactly when and why this exception is triggered in practice, specifically when using initializeInBackground(), which is the suggested documented initialization path.
📋 Steps to Reproduce
- Initialize the Iterable SDK using
IterableApi.initializeInBackground()
- Ensure the app process is cold started.
- Send a ghost push with
notificationType = "InAppUpdate", "InAppRemove", or "UpdateEmbedded"
- Firebase delivers onMessageReceived on its background thread before initializeInBackground() completes
_applicationContext is already set, guard passes, inAppManager is still null resulting to a crash
📦 Iterable SDK version: 3.8.0
📲 Android OS version: Android 13, 14, 15, 16
✍️ Issue Description
When a ghost/silent push notification cold-starts the app process while
IterableApi.initializeInBackground()is still running,handleMessageReceivedcrashes with an unhandledRuntimeException:Root cause
initializeInBackground()is async by design, it starts initialization on a background thread and returns immediately. Internally,initialize()sets_applicationContextas its very first statement but createsinAppManagermuch later:The ghost push branch in
handleMessageReceivedusesgetMainActivityContext() != nullas its initialization guard:This guard is insufficient.
Since
initializeInBackground()is async, there is a race window between_applicationContextbeing set (line 1 of initialize()) andinAppManagerbeing created (much later). If Firebase deliversonMessageReceivedon its background thread and lands inside that window,getMainActivityContext()returns non-null (guard passes) butinAppManageris still null resulting in a crash.This does not affect normal (non-ghost) pushes, that path never calls
getInAppManager().Suggested fix
Replace the insufficient guard with
isSDKInitialized(), which correctly returnsfalsewhile background initialization is still in progress:Behavioral change with this fix: In this edge case the ghost push sync is silently dropped instead of crashing. The in-app sync for that message is missed until the next ghost push or login. This is better than crashing.
Alternatively, if the SDK prefers to leave this responsibility to the caller, then
handleMessageReceivedshould be documented with an explicit warning and a safe deferred pattern usingisSDKInitialized()andonSDKInitialized().This is also related to #727 which reports the same RuntimeException and requests graceful failure instead of throwing. This new issue provides a concrete race condition that explains exactly when and why this exception is triggered in practice, specifically when using
initializeInBackground(), which is the suggested documented initialization path.📋 Steps to Reproduce
IterableApi.initializeInBackground()notificationType = "InAppUpdate", "InAppRemove", or "UpdateEmbedded"_applicationContextis already set, guard passes,inAppManageris still null resulting to a crash📦 Iterable SDK version:
3.8.0📲 Android OS version: Android 13, 14, 15, 16