Skip to content

v4.4.5 — Blank banner slot under Compose interop

Choose a tag to compare

@i2hammad i2hammad released this 27 Aug 11:38
· 12 commits to main since this release

Patch release. No API changed.

Fixes a BannerAdView bug that renders a blank banner slot inside Jetpack Compose — the ad loads, reports success and logs a billed impression, but nothing is drawn. Also picks up the Next-Gen GMA SDK 1.4.0, AGP 9.3.2 and Firebase BOM 34.18.0.

Upgrading is a drop-in from 4.4.x.

Fixed

A banner loaded successfully but rendered blank under Compose interop

BannerAdView swaps its shimmer placeholder for the real AdView on load, which raises an ordinary requestLayout() — enough in a plain View hierarchy, but not inside Compose's AndroidView. View.requestLayout() only propagates while each ancestor reports isLayoutRequested == false, and for an interop subtree the request has to reach AndroidViewsHandler, the only place that turns a View layout request into a LayoutNode.requestRemeasure(). If any ancestor on the way up (the ViewFactoryHolder included) already carried a pending layout flag, the request stopped there and Compose never re-measured the subtree.

The banner then kept the measurement it had while the shimmer was mounted, and the freshly attached AdView was never measured at all:

  • the ad loads successfully, onAdLoaded and onAdImpression both fire
  • the impression is logged and billed
  • the slot renders blank — the container is still sized for the shimmer, the ad view is 0×0
  • it stays blank until something unrelated forces a full traversal (a rotation, a keyboard, a resize)

Recomposition does not rescue it either: a semantics or testTag change does not invalidate measurement.

Both success paths — the AdMob path (handleAdLoadSuccess) and the multi-provider BannerWaterfall path (onBannerLoaded) — now call a new private forceRelayoutAfterAdSwap(), which flags the ancestor chain with forceLayout(), requests the layout from above Compose's interop holder where AndroidViewsHandler will see the pending flag, and repeats once on the next frame in case the request was raised during an in-flight layout pass (ViewRootImpl.requestLayoutDuringLayout).

XML-hosted banners are unaffected — the extra pass is a no-op when the layout is already correct.

Changed

  • Dependency updates: Google Mobile Ads Next-Gen SDK 1.3.1 → 1.4.0, Android Gradle Plugin 9.3.0 → 9.3.2, Firebase BOM 34.17.0 → 34.18.0, org.json (test only) 20260719 → 20260814. Play Billing stays at 9.1.0, Kotlin at 2.2.10, Yandex Mobile Ads at 8.3.0, Compose BOM at 2026.08.00

Notes

  • The fix is not test-covered — it depends on a real Compose interop measure/layout traversal that the Robolectric harness cannot drive faithfully; verified on-device against a Compose-hosted banner slot. The existing 176 tests are unaffected
  • No billing changes in this release

Upgrading

implementation 'com.github.i2hammad.AdManageKit:ad-manage-kit:v4.4.5'
implementation 'com.github.i2hammad.AdManageKit:ad-manage-kit-billing:v4.4.5'
implementation 'com.github.i2hammad.AdManageKit:ad-manage-kit-core:v4.4.5'

// For Jetpack Compose support
implementation 'com.github.i2hammad.AdManageKit:ad-manage-kit-compose:v4.4.5'

// For Yandex Ads multi-provider support
implementation 'com.github.i2hammad.AdManageKit:ad-manage-kit-yandex:v4.4.5'

No code changes are required.

Full release notes: docs/release-notes/RELEASE_NOTES_v4.4.5.md