Skip to content

[SDK-789] fix(android): show in-app messages on the main thread and compile against native 3.10.1 - #918

Merged
joaodordio merged 2 commits into
masterfrom
fix/SDK-789-android-show-message-main-thread
Oct 9, 2026
Merged

joaodordio merged 2 commits into
masterfrom
fix/SDK-789-android-show-message-main-thread

Conversation

@joaodordio

@joaodordio joaodordio commented Oct 8, 2026 •

Copy link
Copy Markdown
Member

📝 Summary

Show in-app messages on the main thread on Android, and make the Android module compile against its pinned native SDK 3.10.1.

🎟️ Jira Ticket: SDK-789

📖 Description

1. The Android module does not compile in 3.2.0 (commit 1)

The published iterableapi:3.10.1 AAR is compiled with Kotlin 1.9 without -Xjvm-default, so IterableEmbeddedUpdateHandler.onEmbeddedMessagingSyncSucceeded and onEmbeddedMessagingSyncFailed are public abstract to Java (javap confirms, bodies live in $DefaultImpls). RNIterableAPIModuleImpl does not override them, so javac fails on pristine master and on the 3.2.0 npm tarball. This adds no-op overrides matching the native defaults.

This went unnoticed because ci.yml triggers on main while this repo's default branch is master; the workflow has 0 runs ever. Tracked separately.

2. showMessage crash (commit 2)

Iterable.inAppManager.showMessage called IterableInAppManager.showMessage on the RN native-modules thread. Native Android has no main-thread hop on that path.

  • Native 3.6.2 (RN 3.1.0 pin): a non-FragmentActivity host logged a warning and showed nothing.
  • Native 3.9.0+ (RN 3.2.0 pin is 3.10.1): that host now takes the Dialog path, and LifecycleRegistry.addObserver throws IllegalStateException off the main thread. The customer trace line numbers match 3.10.x exactly.
  • FragmentActivity hosts (stock ReactActivity) were not crashing: DialogFragment.show() goes through commit(), which only posts to the host handler. The main-thread assertion is in ensureExecReady (commitNow / executePendingTransactions). That is why this has been latent since 2020.

Changes in showMessage:

  • Post the native call with UiThreadUtil.runOnUiThread, same as setAutoDisplayPaused (fixed for the same reason in fdf29d54) and the native iOS SDK's show(message:).
  • Reject when the message id is no longer queued. Previously null went into native and NPE'd on message.isJsonOnly(), the same class as SDK-785 (fix: skip setRead when the in-app message is no longer queued #916).
  • Resolve null when the message is dismissed by tapping outside. Previously url.toString() NPE'd on the main thread.
  • messageId == "" used reference equality; now isEmpty().

Known parity gap: the iOS bridge logs and never settles the promise on a missing id, Android now rejects. Follow-up ticket to make iOS reject too.

Tests

First JVM unit tests for the Android bridge: Robolectric + Mockito, versions mirrored from iterable-android-sdk/iterableapi/build.gradle. android/src/test is excluded from the npm package.

Against the old showMessage, 3 of 5 fail (thread, missing message, null URL). With the fix 5/5 pass.

🧪 How to test?

  • cd example/android && ./gradlew :iterable_react-native-sdk:testDebugUnitTest (JDK 17)
  • ./gradlew :app:assembleDebug -PreactNativeArchitectures=arm64-v8a builds the example app
  • yarn test (585 passing), yarn typecheck, yarn lint
  • Manual repro: host RN in a ComponentActivity that is not a FragmentActivity, call Iterable.inAppManager.showMessage(message, true) from JS. Before: process dies with Method addObserver must be called on the main thread. After: message displays.

🧾 Changelog

Added under ## 3.2.1 (compile fix and showMessage crash fix).

📚 Docs PR if applicable

Follow-up: iterable-docs documents showMessage as Promise<string | undefined> with no rejection case. Ticket to follow.

Lands before #917 is published so both fixes ship in 3.2.1.

… compiles against native 3.10.1

The published iterableapi 3.10.1 AAR is compiled with Kotlin 1.9 without jvm-default,
so onEmbeddedMessagingSyncSucceeded and onEmbeddedMessagingSyncFailed are abstract
to Java implementers. RNIterableAPIModuleImpl did not override them, so the module
failed javac. Add no-op overrides that match the native defaults.
Iterable.inAppManager.showMessage called IterableInAppManager.showMessage on the React
Native native-modules thread. Since native Android 3.9.0, a host activity that is not a
FragmentActivity takes the Dialog path, whose LifecycleRegistry.addObserver call throws
IllegalStateException off the main thread and kills the process.

- Post the native call with UiThreadUtil.runOnUiThread, matching setAutoDisplayPaused
  and the native iOS SDK.
- Reject when the message id is missing instead of passing null into native (NPE).
- Resolve null when the message is dismissed without a URL instead of NPE on toString.
- Fix the empty messageId check, which used reference equality.

Adds the first JVM unit tests for the Android bridge (Robolectric + Mockito, versions
mirrored from iterable-android-sdk) and excludes them from the npm package.
@joaodordio
joaodordio requested a review from a team as a code owner October 8, 2026 17:09
@qltysh

qltysh Bot commented Oct 8, 2026

Copy link
Copy Markdown

1 new issue

Tool Category Rule Count
qlty Structure Function with high complexity (count = 6): showMessage 1

}
});
}
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function with high complexity (count = 6): showMessage [qlty:function-complexity]

@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Lines Statements Branches Functions
Coverage: 72%
71.92% (579/805) 61.22% (229/374) 67.18% (174/259)

@qltysh

qltysh Bot commented Oct 8, 2026

Copy link
Copy Markdown

Qlty


Coverage Impact

This PR will not change total coverage.

🚦 See full report on Qlty Cloud »

🛟 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

Comment on lines +63 to +79
public void showMessageFromBridgeThreadDisplaysOnMainThread() throws InterruptedException {
final AtomicReference<Thread> displayThread = new AtomicReference<>();
doAnswer(invocation -> {
displayThread.set(Thread.currentThread());
return null;
}).when(inAppManager).showMessage(eq(message), eq(true), any(IterableHelper.IterableUrlCallback.class));

Thread bridgeThread = new Thread(() -> module.showMessage(MESSAGE_ID, true, promise));
bridgeThread.start();
bridgeThread.join();

assertNull(displayThread.get());

shadowOf(Looper.getMainLooper()).idle();

assertSame(Looper.getMainLooper().getThread(), displayThread.get());
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What the spec says: SDK-789 is that hop. SDK-790 requires :iterable_react-native-sdk:testDebugUnitTest on pull requests to master. This PR’s GitHub Unit tests job is Jest and succeeded. ci.yml did not run.

Why it conflicts: The automated proof of the crash fix is the local Gradle command in the PR body. A later change can remove the hop and the checks that actually ran on #918 stay green. SDK-790 is the ticket that closes that gap; the test itself is the right check for this bridge.

Suggested action: Land SDK-790 so this suite runs on master pull requests before 3.2.1 is published. #918 does not need a CI change of its own.

@joaodordio
joaodordio merged commit 914f8c5 into master Oct 9, 2026
18 checks passed
@joaodordio
joaodordio deleted the fix/SDK-789-android-show-message-main-thread branch October 9, 2026 16:46
@joaodordio joaodordio mentioned this pull request Oct 9, 2026
8 tasks done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants