Skip to content

Releases: faizahmaddae/ad_flow

ad_flow 5.2.2

Choose a tag to compare

@faizahmaddae faizahmaddae released this 21 Jul 15:13
5b13183

A focused reliability patch completing the 5.2.1 cached-consent fast path. Upgrade: ad_flow: ^5.2.2.

No public API or default configuration changes; internal cached-consent reconciliation and reactive state accuracy were corrected.

What changed

  • 5.2.1 introduced safe cached-consent startup serving: an eligible returning user with valid cached UMP consent serves ads while this launch's consent-info update is still in flight.
  • 5.2.2 completes downgrade reconciliation when this launch's final consent result becomes false (consent lapsed and a now-required form was declined). The downgrade invalidates inventory through the existing consent generation before rechecking — so a stale ad whose SDK load was still in flight, and a full-screen show already waiting at an async pre-show check, are now rejected rather than served: the stale handle is disposed, never published as AdLoaded, no impression/show occurs, and the slot settles into an honest blocked state (no duplicate request or retry storm, balanced coordinator).
  • AdFlow.canRequestAds now reflects the accepted cached true result immediately while the fast path is actively serving, then reconciles to the final result. whenReady semantics are unchanged.

Preserved

Cached-false blocking, ATT exclusion, forwardConsent fail-closed, and request-configuration ordering are all unchanged; a final true settlement does not invalidate good inventory or duplicate loads.

Compatibility

No public API, dependency, configuration, or migration change. SemVer patch.

ad_flow 5.2.1

Choose a tag to compare

@faizahmaddae faizahmaddae released this 21 Jul 14:23
c2fc8c0

A focused reliability patch. Upgrade: ad_flow: ^5.2.1.

No public API or default configuration changes; internal correctness, weak-network recovery, and final dispatch safety were improved.

Cached-consent startup recovery

Returning users with valid cached UMP consent from a previous session no longer wait for this launch's (possibly slow) consent-info update before ads can serve. Per Google's UMP guidance, once this launch's update has been dispatched, a valid cached canRequestAds() lets first-frame banners/natives, full-screen preloads and cold App Open readiness serve immediately instead of stalling on slow or intermittent connections. The full flow still runs and publishes its final result; a downgrade drops the early inventory.

Protections remain intact: requestConsentInfoUpdate() runs every launch and no request precedes its dispatch; a cached-false (first-install) user stays blocked until settled; ATT ordering / a required ATT decision is honored (fast path disabled when a client-driven ATT primer is configured); forwardConsent stays fail-closed and forward-before-init (fast path disabled for forwarding adopters); request-configuration-before-load is unchanged; no duplicate consent flows or load storms.

Final full-screen dispatch guard

Full-screen ads are re-validated immediately before the SDK handle.show() dispatch: ads still enabled, the loaded ad's consent generation still current, not expired, not disposed. A disableAds() / consent mutation / expiry that lands during an async pre-show wait — previously skipped while the controller was AdShowing — no longer dispatches a disabled, stale, or expired ad. Revoked inventory is dropped (not left warm); stale/expired inventory is discarded and reloaded. Coordinator balance, exactly-once show(), reward semantics and the rewarded-interstitial intro are unchanged.

Documentation

Corrected App Open trigger-mode docs (README + AppOpenHandle: launchOnly / resumeOnly / launchAndResume, not "warm-start only") and the runtime-SSV helper doc (load-time attach fails the load closed for a configured SSV, not silent best-effort).

Compatibility

No public API, dependency, migration, or default-configuration change. SemVer patch.

ad_flow 5.2.0

Choose a tag to compare

@faizahmaddae faizahmaddae released this 21 Jul 12:53
26370ba

A focused reliability release. Backward-compatible — no migration.

Never load while Mobile Ads init is in flight

No ad request is ever sent while the real MobileAds.initialize() future is still running, on every path:

  • Ordinary first-frame fail-open / vacuous config — the request-config gate no longer honours fail-open while init is still running; it blocks with AdBlockReason.requestConfigNotApplied and fail-open applies only after init has genuinely completed (a config attempted and failed for real).
  • The forwardConsent-starts-init-later path — for a mediation forwardConsent adopter the SDK's init only starts after forwarding succeeds, i.e. after the first config check already ran. The gate now re-settles request configuration after the forwarding barrier, so a placement can no longer slip through on the forwarding-path init-wait timeout while init is still in flight. Forward-before-init is preserved.
  • Prompt recovery — when init completes late, request configuration is applied at once and blocked slots recover automatically (no app code, no long cooldown, no duplicate config calls or load storms). A never-completing init leaves slots in an honest blocked state, never AdLoading, never a request. Startup stays fully non-blocking.

In-flight disableAds() (Remove-Ads) protection

A disableAds() that lands after a load passed the gate but before the SDK returned its handle no longer publishes the late handle as loaded or keeps it warm. Banner, Native, Interstitial, Rewarded, Rewarded Interstitial, and App Open now re-check the current permission synchronously immediately before installing the handle, drop it if ads are disabled (AdBlocked(adsDisabled)), and re-warm on enableAds(). A transient indeterminate read never drops good inventory.

SSV backend-security guidance

Rewarded / rewarded-interstitial docs now explain, operationally: onReward is a client-side signal, not proof; attaching userId/customData does not prove backend verification; the backend must verify Google's callback signature and key_id, validate user / ad-unit / reward / custom data, and process transaction_id idempotently; the two fulfillment strategies; a no-double-grant warning; and a link to Google's official Flutter SSV guide.

Public API

  • +AdGate.isEnabled — one additive getter (mirrors AdGate.consentGeneration). This is the entire delta; no removals, no signature/enum/default changes; hence a correct minor. No migration.

Known limitation

The upstream google_mobile_ads 9.0.0 App Open failed-load leak remains (the plugin omits dispose() on that branch and its callback carries no ad reference, so the seam cannot fix it) — low-severity, process-bounded.

Verification

543 tests pass; analyze & format clean; pana 160/160; publish dry-run 0 warnings; Android and iOS simulator example builds pass. No physical-device run was performed — not required for this pure-Dart lifecycle change.

v5.1.1 — collapse disabled ad surfaces and correct adaptive placeholders

Choose a tag to compare

@faizahmaddae faizahmaddae released this 21 Jul 03:03
991b650

A focused, backward-compatible ad-surface layout bug fix prompted by real emulator screenshots.

  • Remove-Ads now immediately collapses Banner and Native surfaces to zero. Widget-first mode collapses synchronously on disableAds(); advanced controller mode on AdBlocked(adsDisabled).
  • The example also removes parent SafeArea / Card surfaces when ads are disabled (not just the ad inside them).
  • Anchored adaptive uses a 50dp pre-load floor and then the exact loaded dimensions.
  • Inline adaptive defaults to zero before load.
  • Fixed and loaded sizing remain exact.
  • placeholderHeight remains supported, but adsDisabled always overrides it (zero footprint).
  • Re-enabling ads reloads normally, with no duplicate requests.
  • No public API change and no migration required.

Verification: dart format/analyze clean, 537 tests green, coverage 87.3%, pana 160/160, publish dry-run 0 warnings. Android runtime was verified on an emulator with Google test ads (enabled / disabled / re-enabled); the iOS simulator build passed (iOS was not interactively runtime-tested).

v5.1.0 — App Open launch modes and focused reliability hardening

Choose a tag to compare

@faizahmaddae faizahmaddae released this 20 Jul 16:53
1dd6640

A focused reliability + App Open UX minor. Additive and backward-compatible — every 5.0 call site compiles unchanged, and the default App Open behavior is preserved.

App Open trigger modes

AppOpenConfig.triggerMode selects AppOpenTriggerMode:

  • resumeOnly — the default, exactly the v5.0 behavior (show on a genuine warm return only).
  • launchOnly — show only at cold launch, via showAtLaunchIfReady().
  • launchAndResume — both.

showAtLaunchIfReady()

Call it from your real loading screen, right before entering main content. It returns immediately and never waits for an ad, the SDK, consent, or the network — it shows only when an eligible ad is already ready, otherwise returns false at once and your app proceeds. It is one-shot per process launch, so a false result never turns into a surprise App Open later. Cold-launch App Open is best-effort, not guaranteed — it appears only if an ad happened to be ready.

Also in this release

  • launch-only inventory retirement — after its single launch, launchOnly stops maintaining an ad it can never show again.
  • Native-ad expiryNativeConfig.maxAdAge (safe default 55 min; null disables); stale native inventory is dropped and reloaded through the normal gate.
  • Runtime SSV readiness fix — a rewarded / rewarded-interstitial ad is never ready or showable until the latest SSV payload has settled (finalized before AdLoaded; concurrent updates generation-serialized).
  • Callback / lifecycle containmentAppStateEventNotifier.startListening() rejection contained; enableAds()/disableAds() and the App Open manager are inert after dispose().
  • Backward compatibilityFullScreenAdControllerBase.onLoaded() unchanged; resumeOnly default unchanged.
  • Docs & example — README/MIGRATION/CHANGELOG reconciled; the example gains a real launch/loading screen demonstrating the launch path without an artificial delay.

See the CHANGELOG and MIGRATION. Live on pub.dev: https://pub.dev/packages/ad_flow/versions/5.1.0

v5.0.0 — consent-safe mediation and production hardening

Choose a tag to compare

@faizahmaddae faizahmaddae released this 20 Jul 13:17
78802a1

A post-release adversarial audit of 4.0.0 plus a mediation-consent lifecycle redesign. The major is driven by mediation-privacy correctness: consent forwarding now runs before MobileAds.initialize() and fails closed by default, and the conceptually-invalid deferMediationInit is removed.

📦 pub.dev: https://pub.dev/packages/ad_flow/versions/5.0.0

Breaking changes

  • Removed AdFlowConfig.deferMediationInit and AdSdk.disableMediationInitialization(). Both drove the plugin's session-wide disable of Google mediation (an A/B-testing tool), not a defer/resume — conceptually invalid and revenue-harming. AdSdk removal affects only direct seam implementers; FakeAdSdk/GmaAdSdk are updated.
  • forwardConsent runs BEFORE MobileAds.initialize() and fails CLOSED by default. A failed/timed-out forward means the GMA SDK is not initialized and mediation-capable loads block with AdBlockReason.consentNotForwarded (retried in the background); init + serving recover when forwarding succeeds. UI is never blocked — initialize() returns immediately. Opt out only via MediationConsentFailurePolicy.unsafeFailOpen.
  • AdBlockReason gained consentNotForwarded — exhaustive switches need the new case (or a wildcard).

Added

  • AdFlow.initialize(forwardConsent:) — the fail-closed, before-init consent-forwarding barrier for networks that don't read the IAB TCF string themselves. Serialized (never invoked concurrently, even across the 15s wait bound — Future.timeout does not cancel its source) and generation-guarded.
  • AdFlowConfig.mediationConsentPolicy + MediationConsentFailurePolicy (failClosed default, unsafeFailOpen = explicit unsafe opt-out).

Fixed (correctness / reward integrity / reliability)

  • Frequency-cap late-hydration overwrite (late store read now merges, never rolls memory back / double-counts).
  • Runtime SSV in-flight race + fail-drop (re-apply on install; drop a warm ad on attach failure).
  • Rewarded-interstitial: re-validate live permission + expiry after the unbounded intro.
  • Async-callback + refreshed-banner-paid isolation; safe teardown (safeUnawaited).
  • validate() mirrors every constructor assert (release builds strip asserts).
  • Forwarder serialization across the timeout boundary (at most one invocation, strictly ordered).
  • Stale-consent ad invalidation folded into recheckGate() via an internal consent-generation stamp (no new public method).
  • MediationNetworkExtras asserts against an empty class name.

Verification

Static/tests: flutter analyze clean · 494 tests · coverage 85.2% · pana 160/160 · publish dry-run 0 warnings.

Runtime (Google-demand only, AdFlowConfig.test(), no Unity/overrides): Android emulator — all six formats shown; iOS simulator — banner, native, interstitial, rewarded, rewarded-interstitial shown, app-open loaded/ready. Physical-device testing intentionally deferred.

Unity mediation — known limitation (not a v5 blocker)

Unity mediation via gma_mediation_unity is temporarily unavailable with 5.0.0: the latest published adapter (1.8.1) requires google_mobile_ads: ^8.0.0 while 5.0.0 requires ^9.0.0. Google's main has an in-progress 1.9.0 for ^9.0.0 but it is unpublished; this release intentionally uses no git dependency, override, or vendored patch. Documented as an optional-integration limitation in doc/MEDIATION_SETUP.md. Unity mediation itself was not runtime-validated.

Upgrading

Most apps compile unchanged — see MIGRATION.md. Delete deferMediationInit if set; add the consentNotForwarded case to exhaustive AdBlockReason switches; if you use forwardConsent, note it now runs before init and fails closed.

Full notes: CHANGELOG.md.