Repository navigation
Conversation
… report unheard display messages firebase-inappmessaging-display removes all listeners on every activity pause, so the lifecycle streams went silent on Android after the app was backgrounded or a campaign opened another activity. The listeners are now added again when an activity resumes. A campaign sent to onMessageDisplay while nothing listened was dropped and never reported, which on iOS blocks every later campaign. It is now held for the first listener and reported as a display error if none subscribes within 5 seconds.
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. |
A message that arrives on `onMessageDisplay` while nothing listens is still held for the first listener for a few seconds. When none arrives: - iOS reports it as a display error, as before, so the SDK releases its display lock and can show the next campaign. `onMessageDisplayError` fires for it. - Android now only drops it. `displayErrorEncountered` stores a campaign impression there, and for app open campaigns uses up the one a day app open limit, for a message nobody saw. Android has no display lock, so the campaign stays eligible and triggers again. On Android, disabling custom display while it is already off, which `onDetachedFromEngine` also does, no longer pauses and resumes the default display on the current activity. That restarted a default message on screen and dropped the listeners of other engines. The plugin's SDK calls now go through an internal seam, so JUnit tests drive its activity lifecycle wiring against a fake `Application`.
Description
Fixes two event-delivery bugs in the lifecycle events (#18584) and custom display (#18586) that were added for 0.10.0.
1. Android: the lifecycle listeners were removed after the first activity pause.
firebase-inappmessaging-display'sFirebaseInAppMessagingDisplay.onActivityPausedcallsFirebaseInAppMessaging.removeAllListeners()on every pause, which clears all four listener maps inDeveloperListenerManager(checked in the 23.0.0 AARs from BoM 35). The plugin added its listeners once, and Dart callsaddEventListenersonly once, so after the app was backgrounded, or a campaign action opened a URL or another activity,onMessageClicked,onMessageImpression,onMessageDismissedandonMessageDisplayErrorstayed silent for the rest of the process.The plugin now tracks whether Dart wants the listeners separately from whether they are registered (
EventListenerRegistration), and adds them again inonActivityResumed. The activity lifecycle callbacks are now registered when either the listeners or custom display are in use (they were only registered for custom display).addEventListenersnow runs on the main thread, where the SDK removes the listeners; the SDK keeps them in plainHashMaps.Reproduced on an Android emulator (Pixel 9a) with a test-only harness in the example app (not committed): it reads the SDK's registered listeners by reflection, delivers an impression through
DeveloperListenerManager.impressionDetected, and starts an activity that finishes itself soMainActivitypauses and resumes.onMessageImpression.listenonMessageImpressionafter the pauseEvents the SDK dispatches while no activity is resumed (between one activity pausing and the next resuming) are still lost, since the SDK has no listeners then.
2. Messages sent to
onMessageDisplaywere dropped when nothing listened. AftersetCustomDisplayEnabled(true), native sends each campaign to a broadcastStreamController, which drops events that have no listener. Nothing then reported an impression, click, dismiss or error, and native kept waiting:FIRIAMDisplayExecutorsetsisMsgBeingDisplayed = YESwhen it hands a message to the display component, and resets it only in the click / dismiss / display error callbacks. Until then it shows no other campaign (I-IAM400002/I-IAM400008).pendingDisplayswas never removed.A message that arrives while nothing listens is now held and delivered to the first listener. If no listener subscribes within 5 seconds, or another campaign replaces it:
isMsgBeingDisplayedand records no impression, so native moves on. A dismissal would record one (recordValidImpression).onMessageDisplayErrorfires for it. For test messages, iOS shows its "fail to load a test message" alert with the reason.DisplayCallbacksImpl.displayErrorEncountered(23.0.0) runslogToImpressionStore()thenupdateWasImpressed(): it stores aCampaignImpression, so the campaign is filtered out as seen and reported to the server, and foron_foregroundcampaigns it incrementsAPP_FOREGROUND_ONE_PER_DAY_LIMITER_KEY, which would block every app open campaign for 24 h. Without a report, the campaign stays eligible and triggers again, as before this PR.A held message replaced by the same campaign is not reported, because native keeps one entry per campaign. A held message is dropped when custom display is disabled, since native dismisses it then. The docs now say to listen to
onMessageDisplaybefore enabling custom display, and which of this is iOS only.3. Android: disabling custom display when it was already off rebinds the default display. With the lifecycle callbacks now registered for listener-only apps, a redundant
setCustomDisplayEnabled(false), oronDetachedFromEngine, would pause and resumefirebase-inappmessaging-displayon the live activity: a flicker, restarted timers for a default message on screen, andremoveAllListeners()dropping other engines' listeners. It now returns early when custom display is already off.Tests
firebase_in_app_messaging_platform_interface,method_channel_firebase_in_app_messaging_test.dart), run for Android and iOS withdebugDefaultTargetPlatformOverride: a message sent before the first listener is delivered to it; one replaced by the same campaign is not reported; disabling custom display drops it. On iOS, one nobody listens to, or one another campaign replaces, is reported as a display error and not dismissed. On Android neither is reported (both Android tests fail if Android reports).FirebaseInAppMessagingPluginTestdrives the plugin's real lifecycle wiring against a fakeApplicationand a fake SDK (an internalInAppMessagingSdkseam overFirebaseInAppMessagingand the default display):addEventListenersadds the listeners, they come back after the SDK removes them on pause, detaching stops that.listenersAreAddedAgainWhenAnActivityResumesfails whenupdateLifecycleCallbacks()is removed fromaddEventListeners,onRemovedBySdk()fromonActivityPaused, orrestore()fromonActivityResumed. Two tests check a redundant disable and a detach leave the default display, and another engine's listeners, alone; both fail without the early return.EventListenerRegistrationTestcovers the state machine. No test needsunitTests.returnDefaultValues, so it is not set.CI
firebase_in_app_messagingis not a dependency of the aggregatetestsapp, soandroid_unit_tests.yamlgets a step that runs:firebase_in_app_messaging:testDebugUnitTestfrom the plugin's example app, and its reports are uploaded on failure.Notes
AUTOon native, which records an impression. This is native behaviour that predates this PR, kept as is.pendingDisplaysentry, as it did before this PR. It is replaced when the campaign triggers again, and cleared (with thatAUTOdismissal) when custom display is disabled. Releasing it would need a new Pigeon host method for an edge case that predates this PR, so it is left.Related Issues
Follow-up to #18584 and #18586.
Checklist
///).melos run analyze) does not report any problems on my PR.Breaking Change