Google AdMob SDK for Flutter (google_admob_sdk)
A comprehensive, production-ready Flutter SDK wrapper for Google Mobile Ads (google_mobile_ads) with built-in Ad Unit ID management per platform, load time performance tracking, Firebase Analytics integration, customizable ProgressIndicators, and pre-built ad widgets & managers.
Features
- β‘ Easy Initialization: Initialize Google Mobile Ads with custom or test configuration in a single line, including COPPA / GDPR-K (
ageRestrictedTreatment) and content rating (maxAdContentRating). - π― Ad ID Management: Centralized
AdIdConfigfor Android & iOS supporting 6 ad types (Banner, Interstitial, Rewarded, Rewarded Interstitial, App Open, Native) with fallback to Google official test IDs. - π Performance & Event Analytics: Built-in
AdAnalyticsLoggersupportingFirebaseAdLogger,ConsoleAdLogger, or custom loggers to track ad load duration (ms), impressions, clicks, and load failures withadKeytracking (key_<ad_type>). - πΌοΈ Adaptive & Collapsible Banner Ads:
BannerAdWidget.adaptive(...)auto-calculates full device-width anchored adaptive size, pluscollapsiblePlacement: CollapsiblePlacement.bottom(yielding 2xβ4x eCPM boost with smart collapsible overlay). - π¬ Interstitial Ad Manager:
InterstitialAdManager.sharedsingleton, Frequency Capping (minShowInterval,canShow), andshowAdSafely(...)with countdown warning dialog to prevent accidental clicks (High CTR / Invalid Traffic penalty protection). - π Rewarded Ad Manager & SSV:
RewardedAdManager.sharedandshowAdSafely(...)with Server-Side Verification (ServerSideVerificationOptions) preventing reward fraud. - π Rewarded Interstitial Ad Manager:
RewardedInterstitialAdManager.sharedwith safe frequency capping, SSV, and preloaded lifecycle management. - πͺ App Open Ad & Automated Reactor:
AppOpenAdManager.sharedwith built-in ad expiration check (4 hours max age) andAppLifecycleReactorfor automated background-to-foreground ad triggers. - π¨ Native Ad Widget & Cache Pool:
NativeAdWidgetwithkeepAlive: trueandNativeAdPoolcache to prevent impression spam (24-34 imp/user) in ListView/TabBarView, plus Figma presets (cls,mediaView,interstitial), custom layouts, and fullscreen native dialogs. - π€ Bidding & Mediation Setup: Out-of-the-box support and native configuration guides for AppLovin, Unity Ads, Pangle, Meta Audience Network, and Mintegral.
Getting Started
1. Add Dependency
Add google_admob_sdk to your pubspec.yaml:
dependencies:
flutter:
sdk: flutter
google_admob_sdk:
path: ../google_admob_sdk # Or git / pub package path
2. Platform Configuration
Android Setup (AndroidManifest.xml)
Add your AdMob App ID inside the <application> tag:
<manifest>
<application>
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-3940256099942544~3347511713"/> <!-- Test App ID -->
</application>
</manifest>
iOS Setup (Info.plist)
Add GADApplicationIdentifier to ios/Runner/Info.plist:
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-3940256099942544~1458002511</string> <!-- Test App ID -->
<key>SKAdNetworkItems</key>
<array>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>cstr6suwn9.skadnetwork</string>
</dict>
</array>
Usage
1. SDK Initialization
Initialize GoogleAdmobSdk in your main() function:
import 'package:flutter/material.dart';
import 'package:google_admob_sdk/google_admob_sdk.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await GoogleAdmobSdk.initialize(
config: AdIdConfig(
android: const PlatformAdIds(
bannerId: 'ca-app-pub-xxx/android_banner',
interstitialId: 'ca-app-pub-xxx/android_interstitial',
rewardedId: 'ca-app-pub-xxx/android_rewarded',
rewardedInterstitialId: 'ca-app-pub-xxx/android_rewarded_interstitial',
appOpenId: 'ca-app-pub-xxx/android_app_open',
nativeId: 'ca-app-pub-xxx/android_native',
),
ios: const PlatformAdIds(
bannerId: 'ca-app-pub-xxx/ios_banner',
interstitialId: 'ca-app-pub-xxx/ios_interstitial',
rewardedId: 'ca-app-pub-xxx/ios_rewarded',
rewardedInterstitialId: 'ca-app-pub-xxx/ios_rewarded_interstitial',
appOpenId: 'ca-app-pub-xxx/ios_app_open',
nativeId: 'ca-app-pub-xxx/ios_native',
),
),
logger: FirebaseAdLogger(), // Log performance metrics to Firebase Analytics
enableConsoleLog: true, // Print ad load duration and events in debug console
maxAdContentRating: MaxAdContentRating.g, // Optional: Content rating (g, pg, t, ma)
ageRestrictedTreatment: TagForChildDirectedTreatment.no, // Optional: COPPA / Child-directed
);
runApp(const MyApp());
}
2. Banner Ad Widget (Adaptive & Collapsible)
2.1. Standard Banner with Custom ProgressIndicator:
BannerAdWidget(
adKey: 'home_bottom_banner',
screenName: 'scrHome',
adPlacement: 'scrHome',
adUnitName: 'Banner_Home',
adSize: AdSize.banner,
// Custom ProgressIndicator widget
progressIndicator: CircularProgressIndicator(
color: Colors.blueAccent,
strokeWidth: 3.0,
),
onAdLoaded: (ad) => print('Banner Loaded'),
onAdFailedToLoad: (error) => print('Banner Failed: $error'),
)
2.2. Anchored Adaptive & Collapsible Banner (2xβ4x eCPM Boost):
// Auto-calculates device width anchored adaptive size and requests a collapsible overlay
BannerAdWidget.adaptive(
adKey: 'home_bottom_collapsible_banner',
screenName: 'scrHome',
collapsiblePlacement: CollapsiblePlacement.bottom, // Expandable overlay on initial load
onAdLoaded: (ad) => print('High-eCPM Collapsible Banner Loaded'),
)
3. Native Ad Widget & Fullscreen Dialog
Embed NativeAdWidget with built-in presets (mediaView, cls, interstitial, small, dark) or custom styling:
// Using Preset (Blue CTA #1A90FF, 10px rounded corners)
NativeAdWidget(
adKey: 'feed_native_ad',
screenName: 'scrHome',
adPlacement: 'scrHome',
adUnitName: 'Native_Home',
templatePreset: NativeTemplatePreset.mediaView,
height: 320,
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10.0),
boxShadow: const [
BoxShadow(color: Color(0x0F000000), blurRadius: 8, offset: Offset(0, 2)),
],
),
)
```dart
// 1. Built-in Presets (Auto-links to native Android XML layouts matching Figma designs):
// - NativeTemplatePreset.cls: CTA button #1A90FF on the RIGHT, MediaView on TOP (Figma Node 70:6183)
// - NativeTemplatePreset.mediaView: CTA button #1A90FF on TOP, MediaView in MIDDLE (Figma Node 70:6182)
// - NativeTemplatePreset.interstitial: Large format layout (Figma Node 70:6181)
NativeAdWidget(
adKey: 'feed_native_ad',
templatePreset: NativeTemplatePreset.cls,
height: 320,
)
// 2. Custom Layout Builder in Flutter (rearrange surrounding elements freely):
NativeAdWidget(
adKey: 'feed_native_ad',
templatePreset: NativeTemplatePreset.cls,
height: 320,
header: const Text('Sponsored'),
showCloseButton: true,
onClose: () => print('Closed'),
layoutBuilder: (context, adView) {
return Column(
children: [
adView,
const Text('Tap to view details'),
],
);
},
)
// 3. Fullscreen Native Interstitial Dialog (Figma Ads Interstitial node 70:6181):
showNativeAdInterstitialDialog(
context: context,
adKey: 'native_interstitial_dialog',
screenName: 'scrInterstitialDialog',
);
// 4. In ListView / PageView (keepAlive: true prevents impression spam on scroll):
ListView.builder(
itemCount: 20,
itemBuilder: (context, index) {
if (index % 5 == 0) {
return NativeAdWidget(
adKey: 'feed_native_ad',
keepAlive: true, // Default: true. Retains state and prevents reload on scroll
cacheKey: 'feed_native_$index', // Optional: cache in NativeAdPool
templatePreset: NativeTemplatePreset.cls,
height: 320,
);
}
return ListTile(title: Text('Item $index'));
},
);
// 5. 100% Custom Native XML/XIB Layout via factoryId:
NativeAdWidget(
adKey: 'custom_native_factory',
factoryId: 'my_custom_factory', // Registered in Android MainActivity / iOS AppDelegate
height: 300,
)
Registering Custom Native Ad Factory in Android (MainActivity.kt):
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.googlemobileads.GoogleMobileAdsPlugin
import io.flutter.plugins.googlemobileads.NativeAdFactory // TOP-LEVEL INTERFACE in google_mobile_ads 9.x
class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
GoogleMobileAdsPlugin.registerNativeAdFactory(
flutterEngine,
"my_custom_factory",
MyCustomNativeAdFactory(layoutInflater)
)
}
override fun cleanUpFlutterEngine(flutterEngine: FlutterEngine) {
GoogleMobileAdsPlugin.unregisterNativeAdFactory(flutterEngine, "my_custom_factory")
super.cleanUpFlutterEngine(flutterEngine)
}
}
π Comprehensive Guide: For complete step-by-step XML layouts, drawables, and iOS Swift setup, see doc/CUSTOM_NATIVE_LAYOUTS.md.
4. Interstitial Ad (Shared Instance & Safe Show)
Use InterstitialAdManager.shared to load and present interstitial ads across your entire app, with built-in Frequency Capping (3-minute cooldown) and Countdown Notice to prevent accidental clicks and invalid traffic penalties:
// Use the shared instance across the whole app
final interstitialManager = InterstitialAdManager.shared;
// 1. Preload Interstitial Ad with Databuckets tracking parameters
await interstitialManager.loadAd(
adKey: 'level_complete_interstitial',
screenName: 'scrLevelComplete',
adPlacement: 'scrLevelComplete',
adUnitName: 'Inter_LevelPass',
sessionNumber: 1,
);
// 2. Safely present with cooldown check & warning countdown overlay:
// - Checks if cooldown elapsed (minShowInterval default 180s)
// - Shows brief countdown notice ("Ad starting shortly") before full-screen takeover
// - Always triggers onCompleted so game/app flow is never blocked
// - Fully customizable via countdownTitle, countdownSubtitle, or countdownConfig
await interstitialManager.showAdSafely(
context: context,
adKey: 'level_complete_interstitial',
checkCooldown: true,
showWarningCountdown: true,
countdownDuration: const Duration(milliseconds: 1200),
countdownTitle: 'Ad starting shortly',
countdownSubtitle: 'Please wait a moment...',
onCompleted: () {
// Proceed to next level or resume gameplay
},
);
5. Rewarded Ad & Rewarded Interstitial Ad (with Server-Side Verification)
Use RewardedAdManager.shared or RewardedInterstitialAdManager.shared to ensure preloaded rewarded ads are not lost when switching screens. Use ServerSideVerificationOptions to protect against reward fraud:
// Use the shared instance across screens
final rewardedManager = RewardedAdManager.shared;
// Optional: Set Server-Side Verification (SSV) callback metadata
rewardedManager.setServerSideVerificationOptions(
ServerSideVerificationOptions(
userId: 'user_12345',
customData: 'bonus_gold_pack',
),
);
await rewardedManager.loadAd(
adKey: 'extra_lives_rewarded',
screenName: 'scrShop',
adPlacement: 'scrShop',
adUnitName: 'Rewarded_ExtraLives',
);
// Present safely
await rewardedManager.showAdSafely(
context: context,
adKey: 'extra_lives_rewarded',
onUserEarnedReward: (ad, reward) {
print('User earned: ${reward.amount} ${reward.type}');
},
onCompleted: () {
print('Rewarded flow finished');
},
);
6. App Open Ad & Automatic Background-to-Foreground Reactor
Use AppOpenAdManager.shared for app startup or resume ads. Use AppLifecycleReactor to automate background-to-foreground display:
final appOpenManager = AppOpenAdManager.shared;
// Load ad on app launch
await appOpenManager.loadAd(
adKey: 'app_launch_open_ad',
screenName: 'scrSplash',
);
// Automate background-to-foreground ad presentation safely
final reactor = AppLifecycleReactor(
appOpenAdManager: appOpenManager,
minBackgroundDuration: const Duration(seconds: 15), // Prevent trigger on quick app switch
minShowInterval: const Duration(minutes: 5), // Cooldown between app open ads
canShowAd: () => !isNavigatingPayment, // Custom bypass condition
);
reactor.listenToAppStateChanges();
Databuckets Data Tracking Plan (Sheet "41")
google_admob_sdk automatically measures ad performance and logs events fully compliant with the Databuckets Tracking Plan (Sheet 41):
Universal IAA Events
| Event Name | Trigger | Main Parameters Logged |
|---|---|---|
iaa_request |
When ad load request starts | ad_request_id, ad_format, ad_unit_name, ad_unit_id, ad_mediation, screen_name, session_number |
iaa_loaded |
When ad load succeeds | ad_request_id, ad_format, ad_unit_name, ad_unit_id, ad_mediation, screen_name, is_load: 1, load_time |
iaa_fail_load |
When ad load fails | ad_request_id, ad_format, ad_unit_name, ad_unit_id, ad_mediation, screen_name, is_load: 0, error_code, error_message |
iaa_impression |
When ad impression occurs | ad_request_id, ad_format, ad_unit_name, ad_unit_id, ad_mediation, ad_placement, screen_name, currency, value |
iaa_fail_show |
When ad show fails | ad_request_id, ad_format, ad_unit_name, ad_unit_id, ad_placement, screen_name, error_code, error_message |
iaa_click |
When user clicks ad | ad_request_id, ad_format, ad_unit_name, ad_unit_id, ad_mediation, ad_placement, screen_name |
iaa_complete |
When full-screen ad ends | ad_request_id, ad_format, ad_unit_name, ad_unit_id, ad_placement, screen_name, end_type (done/quit), duration_ad |
Parameter Reference
Note
Format for screen_name & ad_placement: Values representing screen names must strictly follow lowerCamelCase starting with the scr prefix (e.g., scrHome, scrOnboard, scrSplash, scrLevelComplete, scrShop).
When you pass an adKey parameter (e.g. 'home_bottom_banner'), the SDK automatically passes both ad_key and dynamic key_<ad_type> parameters to Firebase Analytics:
| Parameter Name in Analytics | Value Example | Description |
|---|---|---|
ad_key |
'home_bottom_banner' |
Generic ad placement key |
key_banner |
'home_bottom_banner' |
Type-specific key for Banner ads |
key_interstitial |
'level_complete_interstitial' |
Type-specific key for Interstitial ads |
key_rewarded |
'extra_lives_rewarded' |
Type-specific key for Rewarded ads |
key_rewardedInterstitial |
'bonus_stage_rewarded_interstitial' |
Type-specific key for Rewarded Interstitial ads |
key_appOpen |
'app_launch_open_ad' |
Type-specific key for App Open ads |
key_native |
'feed_native_ad' |
Type-specific key for Native ads |
AdMob Quality, Show Rate & Match Rate Protection Architecture
To ensure your app never suffers from low Match Rates (Fill Rate), dropped Show Rates (< 50%), or account-level Invalid Traffic penalties, the SDK includes enterprise-grade guardrails:
1. Ad TTL & Expiration Auto-Invalidation (60 Minutes)
Google AdMob terms dictate that fullscreen ads expire within 60 minutes (4 hours for App Open). Displaying stale ads results in dropped impressions or policy penalties.
All managers (InterstitialAdManager, RewardedAdManager, RewardedInterstitialAdManager, AppOpenAdManager) automatically check isAdAvailable and dispose expired ads immediately:
// Check if ad is available and not expired (within 60m)
if (InterstitialAdManager.shared.isAdAvailable) {
// Safe to present
}
2. Exponential Backoff & Circuit Breaker (AdRetryPolicy)
Aggressive ad requests after No-Fill (ERROR_CODE_NO_FILL = 3) can ruin your Ad Unit's Quality Score. The SDK uses an exponential retry strategy (2s $\rightarrow$ 4s $\rightarrow$ 8s $\rightarrow$ 16s $\rightarrow$ 60s) and automatically trips a Circuit Breaker when consecutive failures occur:
// Managers automatically manage retryPolicy
final retryPolicy = InterstitialAdManager.shared.retryPolicy;
print('Is circuit breaker open: ${retryPolicy.isCircuitOpen}');
3. Smart Auto-Reload & Delay Capping
Eliminate Orphan Preloads (ads loaded upon dismissal that are abandoned when the user leaves the app):
// Globally configure in GoogleAdmobSdk.initialize:
await GoogleAdmobSdk.initialize(
enableAutoReload: true,
autoReloadDelay: const Duration(seconds: 5), // Gives user time before preloading next ad
);
4. Realtime AdMob Health Monitor (AdHealthMonitor)
Diagnose your app's live Match Rate, Show Rate, and CTR during testing and release:
// Access live diagnostic metrics
final health = GoogleAdmobSdk.instance.healthMonitor;
print(health.summary());
// Output:
// AdMob Health Summary:
// Requests: 12 | Loaded: 10 | Failed: 2
// Impressions: 8 | Clicks: 1
// Match Rate: 83.3% | Show Rate: 80.0% | CTR: 12.5%
AdMob Mediation & Bidding (AppLovin, Meta, Mintegral, Unity Ads, Pangle)
The SDK fully supports third-party ad networks (Mediation Adapters) via Google Mobile Ads Bidding & Waterfall mediation. No Dart code changes are requiredβall mediation adapters hook directly into the underlying Google Mobile Ads SDK lifecycle.
Caution
Why is Bidding Reporting 0.00% Bids in AdMob Dashboard?
If your Mediation report shows 0.00% Bids for Unity Ads, AppLovin, or Pangle:
The native mediation adapter binaries were not added to your host app's build.gradle.kts (Android) and Podfile (iOS).
In-app Bidding requires native SDK binaries in the compiled binary so Google AdMob can request real-time bids. Add the snippets below to enable Bidding immediately:
Android Configuration (android/app/build.gradle.kts):
dependencies {
// Google Mobile Ads Mediation Adapters
implementation("com.google.ads.mediation:applovin:13.1.0.0")
implementation("com.google.ads.mediation:facebook:6.19.0.0")
implementation("com.google.ads.mediation:mintegral:16.9.71.0")
implementation("com.google.ads.mediation:unity:4.13.2.0")
implementation("com.google.ads.mediation:pangle:6.4.0.6.0")
}
iOS Configuration (ios/Podfile):
target 'Runner' do
# Google Mobile Ads Mediation Adapters
pod 'GoogleMobileAdsMediationAppLovin'
pod 'GoogleMobileAdsMediationFacebook'
pod 'GoogleMobileAdsMediationMintegral'
pod 'GoogleMobileAdsMediationUnity'
pod 'GoogleMobileAdsMediationPangle'
end
π For complete step-by-step setup, Maven repositories, ProGuard rules, SKAdNetworkItems, and Ad Inspector verification, see:
doc/MEDIATION_SETUP_GUIDE.md