AdMob Ads in Flutter with google_mobile_ads
google_mobile_ads 9.2.0 walkthrough: MobileAds.instance.initialize(), BannerAd sizing, InterstitialAd.load with FullScreenContentCallback, UMP consent and policy rules.
Published on • November 5, 2026
AI Assistant

AdMob integration is mostly plumbing: an app ID in two native config files, one SDK initialization, and a load/show/dispose cycle per format. google_mobile_ads version 9.2.0 (published October 2026, Apache-2.0, verified publisher google.dev) wraps all of it — and supports only Android and iOS, with no web or desktop implementation.
Get the plumbing right first; the revenue mistakes — real ad units in dev builds, accidental clicks, missing consent — are what get accounts suspended.
Platform setup: app IDs, manifest, Info.plist
Every AdMob app has an App ID (ca-app-pub-...~...) which identifies the app, and separate Ad unit IDs (ca-app-pub-.../...) which identify each placement. On Android, put the App ID in android/app/src/main/AndroidManifest.xml:
<application>
<!-- Sample AdMob app ID: ca-app-pub-3940256099942544~3347511713 -->
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy"/>
</application>
Missing or malformed entries crash at startup with Missing application ID, per the Android setup guide, which also sets the floor at minSdkVersion 24 and compile SDK 35. That same guide notes the com.google.android.gms.permission.AD_ID permission: SDK versions 20.4.0 and higher declare it automatically, while 20.3.0 or lower targeting Android 13 must declare it manually.
On iOS, add the equivalent keys to ios/Runner/Info.plist — GADApplicationIdentifier with your App ID, plus SKAdNetworkItems containing at least Google’s cstr6suwn9.skadnetwork key. The iOS setup guide targets iOS 13.0+ with Xcode 16+. Expect a possible DexArchiveMergerException (64K method limit) on Android right after adding the plugin; running flutter run from the terminal lets the tool enable multidex for you.
Initialize, then load a banner
void main() {
WidgetsFlutterBinding.ensureInitialized();
unawaited(MobileAds.instance.initialize());
runApp(const MyApp());
}
The Flutter ads cookbook is explicit that you do not have to await this: the future completes after initialization or a 30-second timeout, and ad requests issued earlier simply wait for it. Initializing at startup gives the SDK the most runway.
A banner is three objects — the ad, its listener, and the widget:
final bannerAd = BannerAd(
size: AdSize.banner,
adUnitId: Platform.isAndroid
? 'ca-app-pub-3940256099942544/6300978111' // Google's test unit
: 'ca-app-pub-3940256099942544/2934735716',
request: const AdRequest(),
listener: BannerAdListener(
onAdLoaded: (ad) => setState(() => _bannerAd = ad as BannerAd),
onAdFailedToLoad: (ad, error) => ad.dispose(),
),
)..load();
// In build():
SafeArea(
child: SizedBox(
width: AdSize.banner.width.toDouble(),
height: AdSize.banner.height.toDouble(),
child: _bannerAd == null ? const SizedBox() : AdWidget(ad: _bannerAd!),
),
)
The SizedBox reserves the banner’s exact footprint before and after loading so the layout does not jump, and SafeArea keeps it clear of notches. Dispose the ad when it leaves the tree or fails to load.
Interstitials and rewarded ads
Interstitials are loaded imperatively and shown once:
InterstitialAd.load(
adUnitId: adUnitId,
request: const AdRequest(),
adLoadCallback: InterstitialAdLoadCallback(
onAdLoaded: (ad) {
ad.fullScreenContentCallback = FullScreenContentCallback(
onAdShowedFullScreenContent: (ad) => debugPrint('shown'),
onAdDismissedFullScreenContent: (ad) => ad.dispose(),
onAdFailedToShowFullScreenContent: (ad, err) => ad.dispose(),
);
_interstitialAd = ad;
},
onAdFailedToLoad: (error) => debugPrint('load failed: $error'),
),
);
Per the interstitial guide, the ad renders as an Overlay above the app rather than in the widget tree, show() can only be called successfully once (later calls trigger onAdFailedToShowFullScreenContent), and disposal belongs in the dismissed/failed callbacks. Rewarded ads follow the same shape with RewardedAd.load, a RewardedAdLoadCallback, and an OnUserEarnedRewardCallback that hands you a RewardItem — grant the reward only in that callback, after the user has actually earned it.
Google’s test units for this format are ca-app-pub-3940256099942544/1033173712 (Android) and .../4411468910 (iOS).
Consent: UMP and App Tracking Transparency
The package now ships Google’s User Messaging Platform classes directly — ConsentInformation, ConsentRequestParameters, ConsentForm, ConsentDebugSettings, ConsentStatus, DebugGeography and PrivacyOptionsRequirementStatus. The flow mirrors the UMP reference: call requestConsentInfoUpdate(...) on every app launch, then loadAndShowConsentFormIfRequired(...) when the update says a form is needed, then check canRequestAds() before requesting ads — it always returns false until requestConsentInfoUpdate has run. If getPrivacyOptionsRequirementStatus() reports REQUIRED, add a visible settings entry point that opens showPrivacyOptionsForm. For testing, ConsentDebugSettings with DebugGeography.DEBUG_GEOGRAPHY_EEA forces the form, and reset() exists for simulating a first install — never call it in production.
On iOS, Apple’s App Tracking Transparency prompt gates access to the IDFA. Google’s guidance is to decide deliberately whether ATT is right for your app, consider a pre-prompt explainer (which you can manage through AdMob’s Privacy & messaging IDFA messages), remember that the EU user consent policy applies to anything shown before the ATT alert, and configure SKAdNetworkItems with Google’s network key so install attribution keeps working without an IDFA.
Traffic quality and policy
Always test with test ads: Google’s interstitial documentation states plainly that using live ads during development can lead to suspension of your account. Beyond that, the interstitial ad guidance says placement must follow the natural flow of the app — surprising users, showing too many ads, or obstructing use leads to poor experience, more accidental clicks, and ultimately invalid activity that can result in Google disabling ad serving. Note the timing constraints too: close buttons can be delayed up to 5 seconds (12 for high-engagement video, 30 for some third-party bidding sources), so never overlap your own controls with that region.
Before serving real ads, your app must pass AdMob’s readiness review and be listed on a supported store, and you must publish an app-ads.txt file on your developer website.
Takeaway: Wire the App ID into both native config files, initialize MobileAds.instance.initialize() at startup, keep test ad unit IDs in development, gather UMP consent before the first ad request, and place ads at genuine transition points — that combination is what keeps an AdMob account alive.