ad_flow 3.0.0
ad_flow: ^3.0.0 copied to clipboard
Policy-compliant AdMob integration for Flutter: banner, interstitial, rewarded, rewarded interstitial, native and app open ads with built-in GDPR/UMP consent.
3.0.0 #
Two releases in one (2.2.0 was never published): the production-hardening work from a deep 2026-07 multi-agent audit (25 confirmed findings, all fixed), plus the API cleanup that backward compatibility had forbidden. See MIGRATION.md for the short 2.x → 3.0 checklist.
BREAKING #
AdBlocked(reason)is a newAdLoadStatecase. A load refused by policy (consent pending, Remove-Ads, withdrawal, disposed graph) now reports itself as a state instead of anAdIdleindistinguishable from "nothing requested yet" — the model ADR-045 documented as correct but could not ship in 2.x. Exhaustive switches gain one case; the controller still re-checks its gate and proceeds toAdLoadingon its own.- Widget-first ad widgets.
AdFlowBanner(adFlow: ads)/AdFlowNativeAd(adFlow: ads)create AND own their controller, making the ADR-029 footgun (minting a controller insidebuild()→ permanently blank ad) unrepresentable.controller:is now optional (advanced use). FullScreenAdController.show()takes no reward callback — it was silently ignored by interstitial and app-open. The rewarded formats keepshow({onReward}).AdGateis a pure permission gate: the racy composedcanShow()query (review finding #6) and its caps/coordinator collaborators are removed. Show pacing lives in the controllers, where the atomictryEnter()is.AppOpenConfig.showOnColdStartremoved (deprecated + ignored since 2.1.0; it never could do anything). Banner/native slot constants renamedslot→slotNameto match the full-screen formats.
Added (3.0) #
AdFlow.canRequestAds— aValueListenable<bool>with the LIVE consent answer: follows a late consent grant (ADR-035 retry) and a privacy-options withdrawal, unlike the one-shotwhenReadysnapshot.
Fixed (correctness / revenue) #
- Banner refresh/resize races: a rotation during an in-flight opt-in
refresh could leak a live
BannerAd(a native view), destroy a fresher right-width ad, corrupt the recorded width, or cancel the slot's only recovery timer (wedging it blank).resize()now defers to an in-flight refresh; the refresh completion re-validates state and reconciles a mid-flight width change; the failure path backs off only while a current ad exists. - Refresh swaps now actually reach the screen: the plugin's
AdWidgetcannot re-point its platform view at a new ad, so an unkeyed rebuild after a swap kept hosting the DISPOSED ad — a permanently dead slot that still requested (and paid for) fresh ads.AdFlowBanner/AdFlowNativeAdnow key the hosted subtree by handle identity, forcing a correct remount. - Inline-adaptive auto-refresh: a failed post-refresh size query tore
down the LIVE mounted banner and silently ended its revenue reporting; it
now only fails the initial load. A refresh that resolves a different
height updates the widget via the new
BannerHandle.dimensionslistenable (inline adaptive creatives vary per refresh). - Preloaded full-screen ads expire (Google documents ~1 hour): new
maxAdAgeon interstitial/rewarded/rewarded-interstitial configs (default 55 min; null disables) — stale warm ads are proactively replaced and never shown. App-open's 4h expiry now runs through the same shared mechanism and also replaces proactively. - Ads come DOWN when no longer permitted:
disableAds()(Remove-Ads),dispose(), a re-initialize, and a consent withdrawal throughads.consentnow DROP live banner/native ads and warm full-screen inventory (previously only future loads were blocked — a mounted banner kept serving and auto-refreshing).enableAds()re-warms at once. NewAdController.recheckGate(). - show() no longer holds the shared coordinator across a consent settle:
a network-bound consent retry could freeze every full-screen format behind
one
show()call for up to 30s. The show path now uses cheap live checks only (AdGate.showBlockReason). - View-ad click latch can no longer be stranded: an iOS in-app overlay
click (open→close with no foreground event) used to eat the NEXT genuine
warm return's app-open ad.
onAdClosednow starts a 3s grace clock; Android's external-browser return ordering stays suppressed. - Raw platform-channel throws from the load dispatch are normalized to
AdFlowErrorand no longer leak the constructed ad + stream controllers. SharedPrefsKeyValueStorereads type-corrupt data as absent instead of throwing (a throwing cap read blocked every full-screen show, with no self-heal).- Consent/ATT primers wait (bounded) for the first frame, so a fast launch no longer silently drops the primer before the navigator mounts.
PrivacyOptionsButtonfailures default toFlutterError.reportErrorinstead of a silent swallow.
Added #
- Runtime SSV:
setServerSideVerification(ssv)on both rewarded controllers — setuserIdafter login and per-showcustomData; applies to the warm ad and future loads; throws if attaching fails. - Mediation observability:
AdResponseSummary(handle.response/controller.response) — winning ad source, adapter class, response ID.AdPaidEventgainsslotandadSourceNamefor analytics-ready impression logging. AdFlowConfig.validate()(run automatically): empty ad-unit strings and nonsensical durations fail fast at init.- Null-safe slot getters:
interstitialOrNull,rewardedOrNull,rewardedInterstitialOrNull,appOpenOrNull,appOpenControllerOrNull. - Testing surface:
FakeBannerHandle.simulateResize/responseSummary,FakeFullScreenAdHandle.simulateShowFailed/ssvUpdates/ssvUpdateError,FakeAdSdk.onPrivacyOptionsFormShown.
Changed #
AdFlow.consentnow returns a thin graph-aware wrapper: consent-mutating calls trigger a permission re-check across every controller (this is what makes withdrawal drop live ads). Read-only members delegate unchanged.- Docs:
doc/MEDIATION_SETUP.mdanddoc/NATIVE_ADS_SETUP.mdrewritten for v2 (they still described the removed v1 API); README documents the emergency kill-switch pattern, the Families app-open prohibition, and both-platform ad unit configuration.
For implementers of the seam interfaces (rare) #
BannerHandle gained dimensions; all handles gained response; the
rewarded handles gained updateServerSideVerification; AdController
gained recheckGate(). The in-package fakes implement all of these — custom
implementations must add them.
2.1.1 #
Docs: README updated to 2.1.x; documented the diagnostic surface
(AdBlockReason / onAdBlocked / lastBlockReason) and the rewarded
global-cap exemption; fixed a stale skill trap. No code changes.
2.1.0 #
Behaviour and default changes from the eight judgment calls raised by the 2.0.2 audit, all approved by the maintainer. No breaking API changes — every existing call site still compiles. But several DEFAULTS and BEHAVIOURS changed deliberately; read this section before upgrading. See MIGRATION.md for the upgrade checklist and ADR-039 … ADR-045 for the reasoning.
Revenue #
- The global frequency cap no longer blocks user-initiated rewarded ads
(ADR-039). A user who tapped "watch an ad for 100 coins" 10s after an
interstitial fired got no ad, no reward and no explanation — the shipped
default global gap (15s) silently refused the highest-eCPM format in the
package. The global cap now paces involuntary ads only (interstitial,
app-open). Rewarded impressions are still recorded globally, so an
interstitial cannot fire straight after one.
New:
RewardedConfig.cap/RewardedInterstitialConfig.cap(unlimited by default) if you do want a per-format limit. - App-open ads now show on the FIRST genuine warm return of a session (ADR-043). The manager was consuming that return as a "cold start" the platform never actually emits — costing one impression in every single session, on both platforms. A true cold start still cannot show an ad: nothing is loaded yet.
- A banner refresh no longer blanks the slot (ADR-041). It used to destroy the live ad and reload from empty, so the slot went blank for the whole load — multi-second on a weak network, every cycle — and a refresh that merely failed (no-fill, routine) left it empty, having destroyed a perfectly good ad to get there. The replacement now loads in the background and swaps in only on success.
Defaults changed #
BannerConfig.minRefreshnow defaults tonull= no client-side refresh at all (ADR-041). AdMob already auto-refreshes banner ad units server-side, from the console, on by default; the client timer was a second, unsynchronised refresh loop on the same placement — up to 2x the ad requests for no extra revenue. Set the refresh rate in the AdMob console. PassminRefresh:explicitly to opt back in.- The frequency gap is now measured from the previous ad's DISMISS, not its SHOW (ADR-040). Stamped at show time, the gap ran down while the user was still watching: a 30s rewarded ad under a 15s global gap used the gap up on screen, so an interstitial could fire the instant the user closed it — two full-screen ads back to back.
AppOpenConfig.showOnColdStartis deprecated and ignored (ADR-043). It could never do what its name promised, and its only real effect is now the default. Remove it.
Policy #
- An app-open ad no longer stacks on a banner/native ad (ADR-042). Returning
from a banner or native ad the user clicked no longer shows one — they were
being handed a second ad the moment they closed the first. And the new
AdFlow.setBlockingViewAdVisible(bool)lets the app declare that a blocking banner occupies the screen, so no app-open ad covers it. ad_flow cannot judge that itself — whether a banner is "blocking" is a question about your layout — so placement remains partly the integrator's job.
Robustness #
AdFlow.initialize()is now idempotent (ADR-044). A second call used to build a whole new graph and leave the previous one fully alive — still listening to the foreground stream, still preloading, still able to show ads, and coordinating through its own separate coordinator, so it could not even see the new graph's ads. Two app-open reactors, each blind to the other. It now disposes the previous graph.
New #
AdBlockReason+AdFlow.onAdBlocked+controller.lastBlockReason(ADR-045) — the answer to "why aren't my ads showing?". A refused load reported plainAdIdle, which is also what "nothing requested yet" looks like, so consent-not-gathered, Remove-Ads and a frequency cap all looked identical, and the package logged nothing. Deliberately not a newAdLoadStatecase:AdLoadStateis sealed, and adding one would break every exhaustiveswitchin every app.AdFlow.setBlockingViewAdVisible(bool);BannerAdController.revision,.resize(),.loadedWidth;StoredFrequencyCapPolicy.globalCapExemptSlots;AdGate.loadBlockReason();FullScreenAdCoordinator.noteViewAdOpened()/.consumeViewAdOpened()/.blockingViewAdVisible.
2.0.2 #
Bug-fix release from a full adversarial audit. Every fix below is guarded by a test that was verified failing first. No breaking API changes.
Revenue — weak/slow networks (ADR-035)
- FIXED: an offline or very slow launch served ZERO ads for the entire
session, even after the network returned seconds later. The consent flow ran
exactly once per launch; if its info update failed,
canRequestAds()stayed false and nothing ever re-asked. A failed consent flow is now retried (rate-limited). A user who simply declined is still never re-prompted. - FIXED: banner and native slots mounted on the first frame stayed blank for 5 minutes on every new install. The consent gate resolves after the config gate, so the first-frame load failed fast and re-armed only after the 5-minute failure cooldown. Loads now wait for consent to settle, and a gate-blocked slot re-checks with a short exponential backoff instead.
- FIXED: a failed banner auto-refresh destroyed the live banner. The same
BannerAd'sonAdFailedToLoadalso fires on a failed AdMob-driven refresh — routine on a weak network. The seam disposed the mounted ad, closed its paid-event stream (silently ending revenue reporting for that placement) and raised "Bad state: Future already completed". - FIXED: adaptive banners never reloaded on rotation/fold (ADR-036) — the ad kept the old orientation's width for the rest of the session, including every refresh.
Policy
- FIXED: the mandatory rewarded-interstitial SKIP button was rendered off-screen at large accessibility text scales (ADR-038) — an AdMob-required opt-out became unreachable. The consent/ATT primers became un-escapable dead ends the same way. All three screens now scroll.
- FIXED: a raw platform error from ATT aborted the whole consent flow (ADR-034) — no info update, no GDPR form, no privacy-options entry point. ATT and GDPR are independent regimes: ADR-031 established that an ATT denial must not suppress a required form; an ATT crash must not either.
- FIXED: a failed/timed-out consent flow hid the privacy-options entry point while ads kept serving from cached consent (invariant 2 / GDPR).
- FIXED: an inline adaptive banner whose height could not be resolved was rendered in a zero-height box — a loaded, billable, unviewable impression.
Robustness (ADR-034, ADR-037)
- FIXED: a throwing frequency-cap store or gate inside
show()left the full-screen coordinator claimed forever, permanently blocking every full-screen format for the session. - FIXED: a
PlatformException/MissingPluginExceptionfrom anyload()pinned that slot atAdLoadingforever with no retry armed. - FIXED: a device clock that was ahead when an ad showed blocked every full-screen ad forever, across restarts. Future-dated timestamps are now ignored and pruned.
Docs
- Documented the required iOS
SKAdNetworkItems(missing entries cost iOS revenue silently) and clarified that client-driven ATT and the AdMob console IDFA message are mutually exclusive. - Fixed the §7 testing snippet, which crashed verbatim under non-blocking init.
Testing
FakeAdSdkgainsonConsentInfoUpdatefor modelling an offline launch that later recovers.
2.0.1 #
- Docs: added a "Set up with AI" README section with copy-paste new-setup and v1→v2 migration prompts. No code changes.
2.0.0 #
Ground-up rewrite targeting google_mobile_ads ^9.0.0. Breaking — see
MIGRATION for the field-by-field and
symbol-by-symbol mapping.
- NEW: Rewarded interstitial format with the policy-mandated intro/skip
screen enforced by construction (
RewardedIntroScreen+ injected presenter). - NEW: Frequency capping — per-format time/count caps AND a global cross-format cap, persisted across restarts.
- NEW: Interstitial user-action pacing (
recordUserAction+minActionsBetween), opt-in by first use. - NEW: Server-side verification options for rewarded formats.
- NEW:
onPaidEventimpression-level revenue callback for every format. - NEW:
package:ad_flow/ad_flow_testing.dartshipsFakeAdSdkso apps can unit-test their ad integration. - NEW: Experimental Next-Gen GMA SDK opt-in on Android via
--dart-define=USE_NEXT_GEN_SDK=true(no Dart changes). - NEW: Non-blocking
AdFlow.initialize()— builds the graph synchronously and returns immediately; consent/ATT/SDK-init run in the background. Render your first frame at once (noFutureBuilder<AdFlow>spinner). OptionalFuture<bool> ads.whenReadyawaits the consent gate. Nothing loads before the gate opens (ADR-032). - NEW: Opt-in consent & ATT priming screens — the v2 equivalent of v1's
initializeWithExplainer, now decoupled fromBuildContextvia presenters (attExplainer/consentExplaineroninitialize, ready-madeAttExplainerScreen/ConsentExplainerScreen). SupplyingattExplainerenables client-driven ATT (iOS). Additive — pass nothing for today's UMP-driven behaviour (ADR-030). - IMPROVED: Architecture — dependency injection everywhere, no static
global config; one
AdSdkseam is the only door to the plugin; state isValueListenable<AdLoadState>. - IMPROVED: Consent — UMP wrapped once into
ConsentGatewayFutures; ATT handled by UMP (dependency onapp_tracking_transparencyremoved); consent failures degrade gracefully with a typedlastError. - IMPROVED: Retries — exponential backoff with jitter, cooldown, then automatic re-arm (v1 never re-armed banner/native loads).
- FIXED: App-open ads no longer fire after Control Center / permission
dialogs / app switcher (v1 treated iOS
inactiveas backgrounding); foreground detection now usesAppStateEventNotifier; 4-hour expiry enforced with discard-and-reload. - FIXED:
isUsingTestAdsfalse positives — test mode is an explicit config flag, never derived from resolved IDs. - BREAKING: Requires Flutter ≥ 3.38.1, Dart ≥ 3.10, iOS 13+, Android minSdk 24 / compileSdk 36 (from google_mobile_ads 9.x).
- BREAKING: All v1 managers, mixins, Easy* widgets and the broad
google_mobile_adsre-export are gone — see MIGRATION §7.
1.3.18 #
- NEW:
EasyBannerAdnow supports optionalSafeAreawrapping (#6)- Added
useSafeAreaparameter (default:true) to prevent extra black space - Set to
falsewhen the banner is already inside aSafeAreaorScaffoldthat handles insets - Works for fixed-size, adaptive, and collapsible banners
- Added
- IMPROVED: Extracted
_wrapWithSafeArea()helper inEasyBannerAdfor cleaner SafeArea logic - IMPROVED: Test suite expanded to 1035 tests
- IMPROVED: Branch protection enabled on
main(requires PR review before merge)
1.3.17 #
- Re-release of v1.3.16 (no code changes)
1.3.16 #
- FIX: App Open ad no longer shows immediately after closing an interstitial or rewarded ad
- OS lifecycle (
paused → resumed) from fullscreen ad overlays was mistaken for a real foreground event - Added fullscreen-ad suppression with 5-second grace period in
AppLifecycleReactor InterstitialAdManagerandRewardedAdManagernow signal showing/dismiss to the reactor
- OS lifecycle (
- IMPROVED: Example launcher now distinguishes "initialized" from "can request ads"
- Shows 3 states: Initializing, Initialized (No Consent), AdFlow Ready
- No longer shows "Initializing…" forever when consent is denied
- IMPROVED: Test suite expanded to 1031 tests
1.3.15 #
- IMPROVED: Comprehensive README rewrite with step-by-step integration guide
- Added callbacks reference tables for all 5 ad types
- Added status listeners documentation
- Added
ignoreCooldowninterstitial example
- IMPROVED: Restructured example app with focused per-ad-type demos
- Launcher menu with navigation to Banner, Interstitial, Rewarded, Native, App Open examples
- All-in-one demo page retained for quick overview
- IMPROVED: Added CI/CD with GitHub Actions
- Automated format, analyze, and test on push/PR
- Auto-publish to pub.dev on version tag push
- IMPROVED: Expanded test suite to 1015 tests
- INTERNAL: Added
AdSdkabstraction andAdManagerMixinfor testability - INTERNAL: Added
PrivacyRequirementMixinfor consent checks
1.3.14 #
- NEW: Non-blocking initialization for instant app startup
- App can start immediately without waiting for AdFlow to initialize
- Ads load in background while users interact with the app
- Dramatically improves user experience on slow networks
- NEW:
waitForInit()method - waits for initialization to complete- Returns
Future<bool>indicating if ads can be requested - Returns immediately if already initialized
- Use for fullscreen ads (interstitial, rewarded) before showing
- Returns
- NEW:
initStream- broadcast stream that emits when initialization completes- Widgets can subscribe and react when AdFlow becomes ready
- Useful for complex scenarios requiring custom ad loading
- IMPROVED:
EasyBannerAdandEasyNativeAdare now fully reactive- Automatically subscribe to
initStreamon mount - Auto-load ads when AdFlow initialization completes
- No code changes required - existing widgets work seamlessly
- Automatically subscribe to
- IMPROVED: Test coverage expanded from 309 to 328 tests
- Added tests for
waitForInit()behavior - Added tests for reactive widget initialization
- Added tests for stream subscription cleanup
- Added tests for
1.3.13 #
- FIX: Ad managers now properly guard against dispose-during-retry crashes
- Added
_isDisposedflag toInterstitialAdManager,RewardedAdManager,AppOpenAdManager,NativeAdManager - Retry loops now exit early if manager is disposed mid-operation
- Prevents
setState() called after dispose()errors in edge cases
- Added
- FIX: Status listener iteration is now safe from concurrent modification
- All ad managers now use
List.of()when notifying listeners - Prevents
ConcurrentModificationErrorif listener removes itself during callback
- All ad managers now use
- FIX: Removed unnecessary
metaimport inad_service.dart - IMPROVED: Test coverage expanded from 227 to 309 tests
- Added comprehensive tests for
EasyPrivacySettingsButtonandPrivacySettingsListTile - Added dispose guard tests for all ad managers
- Added listener safety tests for concurrent modification scenarios
- Added comprehensive tests for
1.3.12 #
- FIX: Splash screen remains too long when AdMob initialization is slow (#4)
- Added smart timeouts with sensible defaults to prevent indefinite blocking
consentNetworkTimeout(default: 10s) - Timeout for consent info network request, falls back to cached statussdkInitTimeout(default: 8s) - Timeout for Mobile Ads SDK initialization, retries in backgroundcoldStartAdTimeout(default: 3s) - Timeout for cold-start app open ad loading- Consent dialogs are NOT affected - they always wait for user interaction (compliance)
- Zero code changes required - existing apps get faster initialization automatically
- IMPROVED: Background retry for SDK initialization if timeout occurs
- IMPROVED: Cold-start app open ads now use bounded timeout instead of blocking indefinitely
1.3.11 #
- FIX:
AppOpenAdManager.addStatusListenercallback now fires correctly (#3)- Status listeners were not notified when using
showAdIfAvailable() - Now properly calls
_notifyStatusListeners()on show/dismiss/fail events
- Status listeners were not notified when using
- FIX: iOS App Store rejection for GDPR shown after ATT denial (#2)
- Added
skipGdprConsentIfAttDeniedconfig option (default:true) - When user selects "Ask App Not to Track", GDPR consent UI is skipped
- Prevents Apple Guideline 5.1.1 rejections
- Set to
falseif you legally require showing GDPR consent regardless of ATT
- Added
- NEW:
ConsentManager.lastAttStatusandisAttDeniedgetters- Access the iOS ATT authorization status after consent gathering
1.3.10 #
- NEW:
EasyBannerAdnow supports custom ad sizes- Use
EasyBannerAd(adSize: AdSize.mediumRectangle)for fixed-size banners - Supports all standard sizes:
banner,largeBanner,mediumRectangle,leaderboard, etc. - Fixed-size banners skip orientation handling for better performance
- Priority:
adSize>collapsible> adaptive (default)
- Use
1.3.9 #
- FIX: Export
BannerAdListenerfromgoogle_mobile_ads(fixes #1)- Allows users to create custom-sized
BannerAdinstances directly
- Allows users to create custom-sized
- NEW: Added
BannerAdManager.loadBanner()method for custom ad sizes- Load banners with specific sizes like
AdSize.mediumRectangle(300x250) for dialogs - Same consent/disabled checks and callbacks as
loadAdaptiveBanner()
- Load banners with specific sizes like
1.3.8 #
- NEW: Mediation support for third-party ad networks
- Added
MediationHelperclass for forwarding consent to mediation networks - Built-in support for Unity Ads and AppLovin with convenience methods
- Register custom adapters for any mediation network
- Consent auto-forwarded during
initialize()/initializeWithExplainer() - See
doc/MEDIATION_SETUP.mdfor complete integration guide
- Added
- DOCS: Added comprehensive mediation documentation
- IMPROVED: Updated copilot-instructions.md with mediation patterns
1.3.7 #
- FIX:
NativeAdWidgetnow respectsAdsEnabledManager.isDisabledon initial build - IMPROVED: Added comprehensive tests for
EasyNativeAdandNativeAdWidgetads-disabled behavior
1.3.6 #
- NEW:
EasyNativeAdnow collapses when ads fail to load (no more empty white space)- Added
hideOnLoadingparameter (default:true) - collapses while loading - Added
hideOnErrorparameter (default:true) - collapses on load failure (e.g., no fill) - Set to
falseto show loading/error widgets with reserved height
- Added
- FIX: Removed double semicolon in
BannerAdManagercausing static analysis warning - IMPROVED: Better UX for fixed-height layouts like
bottomNavigationBar
1.3.5 #
- FIX: All ad managers now respect
AdsEnabledManager.isDisabledstateloadAd()andshowAd()check disabled state before proceeding- Fixes race condition where
disableAds()inonCompletewas too late - Affected managers:
BannerAdManager,InterstitialAdManager,RewardedAdManager,AppOpenAdManager,NativeAdManager
- DOCS: Updated copilot-instructions.md with timing warning for disabling ads
1.3.4 #
- FIX: Applied
dart formatto all files for pub.dev static analysis compliance
1.3.3 #
- IMPROVED: Code quality improvements across all ad managers
- Extracted magic numbers to named constants for better maintainability
- Added explicit types for improved type safety in
AdFlowConfig - Fixed potential memory leaks in dispose methods (banner, interstitial, app open)
- IMPROVED: Selective ad type preloading
preloadAds()now only preloads ad types that have real IDs configured- Added
hasBannerConfigured,hasInterstitialConfigured, etc. getters - Use only the ad types you need without loading unnecessary ads
- FIX:
reset()now properly callsAdFlowConfig.resetCurrent()- Previously config state persisted after reset, now fully resets
- FIX: Status listeners properly cleaned up in dispose methods
- IMPROVED: Simplified example files
- Replaced complex demo pages with two clean, reactive examples
example_with_explainer.dart- GDPR-friendly with explainer dialogexample_without_explainer.dart- Direct initialization- Both examples demonstrate reactive UI with status listeners
1.3.2 #
- NEW: Centralized error handling with
AdFlowErroranderrorStream- Subscribe to
AdFlow.instance.errorStreamfor all ad-related errors - Use
AdFlow.instance.setErrorCallback()for simpler callback-based handling - Errors include type, code, message, ad unit ID, and timestamp
- Supports logging to analytics, crash reporting, or custom UI
- Subscribe to
- NEW: Comprehensive native ad factory documentation
- Added
doc/NATIVE_ADS_SETUP.mdwith platform code examples - Android (Kotlin) and iOS (Swift) factory implementations
- Layout XML and XIB templates
- Added
- BREAKING: Removed deprecated
AdConfigclass- Use
AdFlowConfig.currentfor static access to config values - Use
AdFlow.instance.configfor instance-based access - Cleaner API with no deprecation warnings
- Use
- IMPROVED: Simplified consent flow to match Google's official samples
- Sequential popup handling prevents stacking
- Explainer dialogs only shown when consent is actually needed
1.3.1 #
- NEW: Added
AdFlow.instance.reset()for testing- Enables proper unit testing of singleton state
- Clears all managers and resets initialization
- FIX: Fixed barrel export to use correct file (
ad_service.dart) - FIX: Fixed
use_build_context_synchronouslywarnings inBannerAdManager - IMPROVED: Added lazy initialization for ad managers
- Managers only created when first accessed
- Better memory efficiency for apps using subset of ad types
- IMPROVED: Expanded test coverage from 140 to 185 tests
- Added
AdFlowsingleton tests - Added
EasyBannerAdwidget tests - Added
ConsentManagertests
- Added
- Removed duplicate
ad_flow_service.dartfile
1.3.0 #
- NEW: Added
EasyPrivacySettingsButtonwidget for GDPR compliance- Auto shows/hides based on privacy options requirement
- Opens official Google privacy options form
- Customizable text, icon, and style
- NEW: Added
PrivacySettingsListTilefor settings screens - FIX:
initializeWithExplainer()now properly checks AdsEnabledManager- Previously skipped "Remove Ads" check, now matches
initialize()behavior
- Previously skipped "Remove Ads" check, now matches
- FIX:
isPrivacyOptionsRequired()now returns correct cached value- Was incorrectly returning
canRequestAdsinstead of privacy options status
- Was incorrectly returning
- Added production example with complete implementation guide
- Updated documentation with privacy button usage examples
1.2.0 #
- NEW: Added
RewardedAdManagerfor rewarded video ads- Watch ads to earn in-app rewards (coins, lives, etc.)
- Automatic preloading and retry logic
- Reward callbacks with type and amount
- Status listeners for UI updates
- Added
androidRewardedAdUnitIdandiosRewardedAdUnitIdtoAdFlowConfig - Added
TestAdUnitIds.rewardedfor testing - Re-exported
RewardedAdandRewardItemfrom google_mobile_ads - Updated example app with rewarded ads demo page
1.1.0 #
- BREAKING: Added
AdFlowConfigfor runtime configuration of ad unit IDs - Users can now configure ad unit IDs without modifying package source code
- Added
AdFlowConfig.testMode()factory for easy development/testing setup - Added
TestAdUnitIdsclass with Google's official test ad unit IDs AdConfigis now a proxy that reads fromAdFlowConfig- Updated example app to demonstrate new configuration pattern
1.0.2 #
- Added explicit platform support declaration for Android and iOS
1.0.1+1 #
- Code formatting fixes for pub.dev static analysis compliance
1.0.1 #
- Initial release
- Banner ads (adaptive and collapsible)
- Interstitial ads with cooldown management
- App open ads with lifecycle handling
- Native ads with factory support
- GDPR/ATT consent management via UMP SDK
- iOS App Tracking Transparency support
- Remove Ads feature with persistence
- Multi-language consent dialogs (English, Spanish, Persian)