Skip to content

[SDK-791] Display in-app messages on the main thread when showMessage is called off it - #1102

Open
joaodordio wants to merge 3 commits into
masterfrom
fix/SDK-791-show-message-main-thread
Open

joaodordio wants to merge 3 commits into
masterfrom
fix/SDK-791-show-message-main-thread

Conversation

@joaodordio

@joaodordio joaodordio commented Oct 8, 2026 •

Copy link
Copy Markdown
Member

📝 Summary

IterableInAppManager.showMessage is now safe to call from any thread: off the main thread it posts the display to the main looper.

🎟️ Jira Ticket: SDK-791

📖 Description

Root cause behind SDK-789 (HelloFresh, RN 3.2.0 on native 3.10.1).

showMessage called IterableInAppDisplayer.showMessage on the caller thread. Since 3.9.0 (SDK-100), a host activity that is not a FragmentActivity takes the Dialog path, and IterableInAppDialogNotification.createInstance calls LifecycleRegistry.addObserver, which throws IllegalStateException: Method addObserver must be called on the main thread. Before 3.9.0 that host was a silent no-op, and the Fragment path only posts through FragmentTransaction.commit(), which is why calling from a background thread was latent for years.

iOS already guarantees this: InAppManager.show(message:consume:callback:) always dispatches to main ("This is public, so make sure we call from Main Thread").

Change:

  • The public 4-arg showMessage overload (all public overloads and processMessages funnel into it) posts itself to the main looper when called off it, using the same new Handler(Looper.getMainLooper()).post pattern as notifyOnChange.
  • Main-thread callers are unchanged: display, setRead and markForDeletion still run synchronously.
  • Javadoc on the documented overloads states the threading behaviour.

The React Native bridge also hops in react-native-sdk#918 so RN 3.2.1 is fixed without waiting on this release. This PR protects native and Flutter callers.

🧪 How to test?

New IterableInAppManagerThreadingTest (Robolectric, real ComponentActivity host, real displayer):

  • showMessage from a background HandlerThread (same shape as the RN native-modules thread) does not throw, does not display until the main looper runs, then displays the Dialog.
  • showMessage from the main thread displays synchronously.

With the hop removed, the background test fails with the production exception: IllegalStateException: Method addObserver must be called on the main thread.

Local results:

  • In-app suites (IterableInAppManagerTest, IterableInAppManagerSyncTest, IterableInAppDialogNotificationTest, IterableInAppHTMLNotificationTest, new threading test): 90 tests, 0 failures, 3 pre-existing @Ignore.
  • Full :iterableapi:testDebugUnitTest: 793 tests, 1 failure in IterableAsyncInitializationTest.testOperationQueuing_LargeNumberOfOperations. That test does not touch the in-app manager and passes 3/3 in isolation on this branch and on master; it is a pre-existing flake under full-suite load (5 s init wait).
  • checkstyle passes.

🧾 Changelog

Added under [Unreleased] > Fixed.

📚 Docs PR if applicable

No public docs describe showMessage threading today; the Javadoc now does.

… is called off it

Since 3.9.0 the Dialog renderer registers a LifecycleRegistry observer, which throws off
the main thread. Post the display to the main looper, matching the iOS SDK. Main-thread
callers are unchanged.
@joaodordio
joaodordio requested a review from a team as a code owner October 8, 2026 17:29
}

public void showMessage(final @NonNull IterableInAppMessage message, boolean consume, final @Nullable IterableHelper.IterableUrlCallback clickCallback, @NonNull IterableInAppLocation inAppLocation) {
public void showMessage(final @NonNull IterableInAppMessage message, final boolean consume, final @Nullable IterableHelper.IterableUrlCallback clickCallback, final @NonNull IterableInAppLocation inAppLocation) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

~L252–254 and ~L268: Threading Javadoc is missing on two public overloads

What the spec says: SDK-791 acceptance criteria: “Javadoc on showMessage states the threading behaviour.” The work item names the 4-argument overload as the method that changes.

Why it conflicts: Callers of showMessage(message, location) and showMessage(message, consume, clickCallback, location) do not see the new contract on the method they invoke. The documented overloads do state it, so the checkbox is met for those entry points.

Suggested action: Add the same threading sentence to the location overload and the 4-argument overload.

@franco-zalamena-iterable franco-zalamena-iterable left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I have one last "anti-flakiness" suggestion in a separate branch, will send you to you, for now there are only small suggestions

Comment thread iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppManager.java Outdated
…ppManager.java

Co-authored-by: Franco Zalamena <franco.zalamena@iterable.com>
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.

3 participants