Skip to content

fix(android): emit banner refresh events on the nextgen backend - #927

Open
GonzaloSoftonic wants to merge 2 commits into
invertase:mainfrom
GonzaloSoftonic:fix/nextgen-banner-refresh-events
Open

GonzaloSoftonic wants to merge 2 commits into
invertase:mainfrom
GonzaloSoftonic:fix/nextgen-banner-refresh-events

Conversation

@GonzaloSoftonic

Copy link
Copy Markdown

Description

On the nextgen Android backend, automatic banner refreshes emit no events. The classic backend's AdListener fires onAdLoaded / onAdFailedToLoad on every refresh, so apps that track refreshes (or watch for refresh failures) see them on classic and lose them after switching to androidSdk: 'nextgen'.

The cause: Next-Gen reports refreshes through a separate BannerAdRefreshCallback (Google docs). The banner view manager sets BannerAdEventCallback after load but never BannerAdRefreshCallback. It is still missing on main and in 17.4.0.

This PR sets it next to the event callback and maps:

  • onAdRefreshed → onAdLoaded, with the same payload as a first load (width, height, responseInfo).
  • onAdFailedToRefresh → onAdFailedToLoad, with the same error map as a failed load.

Details:

  • Like the classic onAdLoaded, a refresh re-blocks hardware-BACK focus on the AdView ([🐛] App Closes On Back Button in Nested Stack Page with BannerAd Loaded (Android) #813) and kicks refreshBannerPresentation() ([🐛] Only half of the add is loading #711), since a same-size refresh causes no size or visibility change to do it.
  • Refresh callbacks arrive off the main thread, so one can land after a prop-driven reload or an unmount. The callback is bound to its AdView and emits nothing once that AdView is no longer hosted or the view is torn down.
  • A multi-size request can refresh into another size. The RN parent never lays out the AdView, so a refresh measures and lays it out to the refreshed size, as the first load does; otherwise a taller ad is clipped and a shorter one leaves a gap.
  • It reads the AdView's current BannerAd, because the docs don't say whether a refresh swaps the instance. In 1.5.0 it doesn't (the public BannerAd wraps the refreshed ad and the SDK moves the event callback to it), but if a later SDK hands back a new BannerAd, the event callback is set on it so clicks and paid events keep arriving.
  • Collapsibility and dynamic-height layout are not re-applied on refresh: Google says "subsequent ad refreshes won't request collapsible banner ads", and a collapsible first ad keeps the dynamic-height path, which still measures the refreshed ad's real height.
  • The first-load payload and error map moved into two small seams (adLoadedPayload, loadAdErrorPayload), plus one for the current BannerAd (currentBannerAd), so the tests can stub them like paidEventPayload.

No JS or TypeScript change: the events and their payloads are the ones BannerAd already handles.

Related issues

None found.

Release Summary

Android (nextgen): banner auto-refreshes now emit onAdLoaded / onAdFailedToLoad, as on the classic backend.

Checklist

  • I read the Contributor Guide
    and followed the process outlined there for submitting PRs.
    • Yes
  • My change supports the following platforms;
    • Android
    • iOS (not applicable: the iOS SDK has no Next-Gen backend here)
  • My change includes tests;
    • e2e tests added or updated in __tests__e2e__
    • jest tests added or updated in __tests__
    • Robolectric unit test added in android/src/testNextgen (ReactNativeGoogleMobileAdsBannerAdRefreshCallbackTest)
  • I have updated TypeScript types that are affected by my change. (none affected)
  • This is a breaking change;
    • Yes
    • No

Test Plan

  • yarn tests:android:unit:nextgen: all pass, including 9 new cases: refresh → onAdLoaded with the refreshed size and response; refresh into a taller size → AdView resized; fluid refresh → the host view's size; a swapped BannerAd keeps click events, an unchanged one is left alone; refresh → presentation kick; failed refresh → onAdFailedToLoad; both callbacks queued before a reload or a teardown → nothing emitted. Removing the guard, the resize or the callback handoff each fails its test.
  • yarn tests:android:unit (classic): all pass.
  • ./gradlew ktlintCheck, git diff --check, yarn lint:spellcheck, yarn lint:docs-links: clean. Java formatted with google-java-format.
  • Appium smoke, slot 1, TestingAVD-1 (API 36 Play Store, arm64, local):
    • yarn tests:appium:android:nextgen: shards a and b 18/18 passing, every banner format included. Shard c: every request loaded, then the session idled 5 min with no command and Appium's idle timeout closed it; no assertion failed.
    • yarn tests:appium:android (classic, code untouched by this PR): 10/11 passing on two runs; Rewarded show-close never reached closed both times. The classic build doesn't compile this PR's src/nextgen code, and your x86_64 CI passed it on main (23 Oct), so I read it as an arm64 emulator issue.
  • Not yet checked on a device with live ads. I'll add the result here once we've run it in our app (we hit this moving to nextgen).

🔥

@CLAassistant

CLAassistant commented Oct 30, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@GonzaloSoftonic
GonzaloSoftonic force-pushed the fix/nextgen-banner-refresh-events branch from db9ae9d to 866e2b4 Compare October 30, 2026 07:47

This branch has not been deployed

No deployments
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

⌂ Proxy