Skip to content

handleMessageReceived crashes with RuntimeException when using initializeInBackground() #1067

Description

@Thanasis17m

✍️ 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

  1. Initialize the Iterable SDK using IterableApi.initializeInBackground()
  2. Ensure the app process is cold started.
  3. Send a ghost push with notificationType = "InAppUpdate", "InAppRemove", or "UpdateEmbedded"
  4. Firebase delivers onMessageReceived on its background thread before initializeInBackground() completes
  5. _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


Metadata

Metadata

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions