Releases: faizahmaddae/ad_flow
Release list
ad_flow 5.2.2
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 asAdLoaded, no impression/show occurs, and the slot settles into an honest blocked state (no duplicate request or retry storm, balanced coordinator). AdFlow.canRequestAdsnow reflects the accepted cachedtrueresult immediately while the fast path is actively serving, then reconciles to the final result.whenReadysemantics 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
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
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.requestConfigNotAppliedand fail-open applies only after init has genuinely completed (a config attempted and failed for real). - The
forwardConsent-starts-init-later path — for a mediationforwardConsentadopter 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 (mirrorsAdGate.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
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 onAdBlocked(adsDisabled). - The example also removes parent
SafeArea/Cardsurfaces 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.
placeholderHeightremains supported, butadsDisabledalways 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
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, viashowAtLaunchIfReady().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,
launchOnlystops maintaining an ad it can never show again. - Native-ad expiry —
NativeConfig.maxAdAge(safe default 55 min;nulldisables); 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 containment —
AppStateEventNotifier.startListening()rejection contained;enableAds()/disableAds()and the App Open manager are inert afterdispose(). - Backward compatibility —
FullScreenAdControllerBase.onLoaded()unchanged;resumeOnlydefault 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
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.deferMediationInitandAdSdk.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.AdSdkremoval affects only direct seam implementers;FakeAdSdk/GmaAdSdkare updated. forwardConsentruns BEFOREMobileAds.initialize()and fails CLOSED by default. A failed/timed-out forward means the GMA SDK is not initialized and mediation-capable loads block withAdBlockReason.consentNotForwarded(retried in the background); init + serving recover when forwarding succeeds. UI is never blocked —initialize()returns immediately. Opt out only viaMediationConsentFailurePolicy.unsafeFailOpen.AdBlockReasongainedconsentNotForwarded— 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.timeoutdoes not cancel its source) and generation-guarded.AdFlowConfig.mediationConsentPolicy+MediationConsentFailurePolicy(failCloseddefault,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). MediationNetworkExtrasasserts 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.