flutter_ads_orchestrator 0.1.0
flutter_ads_orchestrator: ^0.1.0 copied to clipboard
Smart AdMob orchestration for Flutter apps: preload, throttle, caps, placement rules, UMP consent. Supports interstitial and rewarded ads.
flutter_ads_orchestrator #
Smart AdMob orchestration for Flutter — preload, throttle, consent, never spam users.
flutter_ads_orchestrator wraps google_mobile_ads 8.x with the policy layer almost every monetised Flutter app eventually has to write by hand: preload, retry/backoff, cooldown, session/day caps, placement rules, app-open guard, UMP consent, and an iOS ATT hook. One declarative config, one entry point.
Why? #
Without orchestration, an AdMob integration usually looks like this:
- Two interstitials back-to-back because there's no global cooldown → uninstall trigger.
- "Get reward" button taps wait 5 s for a fresh load → drop-off.
- Forgotten preload → slow show → lost revenue.
- Missing UMP / ATT → zero fill in EU and on iOS 14.5+.
- Ad-show logic copy-pasted across every screen → untunable mess.
This package handles all of it through one AdsConfig.
Features #
- Smart preload with exponential-backoff retry and a fallback ad-unit chain.
- Throttling: cooldown, per-session cap, per-day cap (persisted).
- Declarative placement rules:
allowed,disabled,triggerEvery(N). - App-open guard for interstitials after cold start.
- UMP consent integrated (GDPR / CCPA via
google_mobile_ads). - iOS ATT callback hook — bring your own
app_tracking_transparencypackage. - Sealed result types for compile-time exhaustiveness.
- Event stream for analytics.
- Test mode auto-overrides production ad units with Google's official test IDs.
Quick start #
import 'package:flutter/material.dart';
import 'package:flutter_ads_orchestrator/flutter_ads_orchestrator.dart';
late final AdsOrchestrator orchestrator;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
orchestrator = AdsOrchestrator(
config: const AdsConfig(
testMode: kDebugMode,
interstitial: InterstitialRules(
adUnitIds: ['ca-app-pub-xxx/yyy', 'ca-app-pub-xxx/fallback'],
minInterval: Duration(seconds: 60),
maxPerSession: 8,
maxPerDay: 30,
appOpenGuard: Duration(seconds: 30),
placements: {
'level_complete': PlacementRule.triggerEvery(3),
'pause_menu': PlacementRule.disabled(),
'game_over': PlacementRule.allowed(),
},
),
rewarded: RewardedRules(
adUnitIds: ['ca-app-pub-xxx/zzz'],
minInterval: Duration(seconds: 15),
),
),
);
await orchestrator.initialize();
runApp(const MyApp());
}
// Anywhere in your UI:
final result = await orchestrator.showInterstitial(placement: 'level_complete');
result.when(
shown: (id) => analytics.log('ad_shown', {'unit': id}),
suppressed: (reason) => debugPrint('suppressed: ${reason.name}'),
failed: (error) => debugPrint('failed: ${error.message}'),
notReady: () => debugPrint('not preloaded'),
);
Setup #
AdMob App ID #
Add the App ID from your AdMob console (use Google's test App ID during development):
Android — android/app/src/main/AndroidManifest.xml inside <application>:
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-XXXXXXXXXXXXXXXX~YYYYYYYYYY" />
iOS — ios/Runner/Info.plist:
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-XXXXXXXXXXXXXXXX~YYYYYYYYYY</string>
iOS ATT (optional but recommended) #
This package does not bundle an ATT plugin so non-iOS apps stay lean. If you target iOS, add app_tracking_transparency and wire the callback:
# Your app's pubspec.yaml
dependencies:
app_tracking_transparency: ^2.0.6
import 'package:app_tracking_transparency/app_tracking_transparency.dart';
const consent = ConsentConfig(
attRequestCallback: _requestAtt,
);
Future<bool> _requestAtt() async {
final status = await AppTrackingTransparency.requestTrackingAuthorization();
return status == TrackingStatus.authorized;
}
Also add to ios/Runner/Info.plist:
<key>NSUserTrackingUsageDescription</key>
<string>This identifier will be used to deliver personalized ads.</string>
Placement rules #
placements: {
// Allow every call — global rules (cooldown, caps) still apply.
'game_over': PlacementRule.allowed(),
// Never show at this placement.
'pause_menu': PlacementRule.disabled(),
// Show on every Nth call. Calls 1..N-1 return Suppressed(triggerNotMet).
'level_complete': PlacementRule.triggerEvery(3),
},
// Unlisted placements fall back to this.
defaultPlacementBehavior: PlacementBehavior.allowed,
triggerEvery(N) is interstitial-only — rewarded ads silently ignore it since they're always user-initiated.
Event stream #
orchestrator.events.listen((event) {
switch (event) {
case AdLoadStartedEvent(): break;
case AdLoadedEvent(): break;
case AdLoadFailedEvent(): break;
case AdShownEvent(): break;
case AdDismissedEvent(): break;
case AdShowFailedEvent(): break;
case AdSuppressedEvent(): break;
case AdExpiredEvent(): break;
case UserEarnedRewardEvent(): break;
}
});
Global controls #
// Premium / IAP "remove ads" — persisted via SharedPreferences.
await orchestrator.setNoAdsFlag(true);
// Re-show the UMP privacy options form (from a settings screen).
await orchestrator.showPrivacyOptions();
// Testing only:
orchestrator.resetSessionCounters();
await orchestrator.resetConsent();
// App teardown.
await orchestrator.dispose();
Comparison #
| Feature | google_mobile_ads alone | easy_ads_flutter | flutter_ads_orchestrator |
|---|---|---|---|
| Preload | Manual | Auto | Auto |
| Cooldown | Manual | Manual | Built-in |
| Caps per session / day | Manual | Manual | Built-in |
| App-open guard | Manual | Manual | Built-in |
| Placement rules | Manual | Manual | Declarative |
| Fallback unit IDs | Manual | Manual | Built-in chain |
| UMP integration | Manual | Manual | Built-in |
| iOS ATT integration | Manual | Manual | Hook (you pick the package) |
| Mediation | Manual | Built-in (AdMob + FAN) | AdMob only |
Example app #
See example/ for a minimal three-screen demo (menu / game / settings) exercising every placement rule, rewarded ads, the no-ads toggle, and the privacy options form. Run on a real device with the test ad units already configured.
Roadmap #
- v0.2 — Banner widget, per-placement caps, debug overlay,
recordEventindependent trigger counter. - v0.3 — Native ads inline.
- v0.4 — App Open ads (full implementation).
- v1.0 — Remote config hook, Riverpod / GetIt companion package.
Stability #
v0.1 is a release candidate: the pure-Dart layer (rules, counters, preload, orchestrator) has 100% line coverage; the AdMob/UMP wrappers are smoke-tested against google_mobile_ads 8.x API docs and the example app but require device validation in your environment before production rollout.