AdmobKit-flutter
AdmobKit is a production-tested Google Mobile Ads (AdMob) orchestration engine for Flutter. It eliminates startup lag, layout shifts, and ad timing guesswork with an instant 0ms display contract, deterministic settlement, and pre-sized native ad templates.
The Real Story: Why AdmobKit Exists
In our company, junior and mid-level developers routinely got mobile ad implementations wrong:
- Splashes hung indefinitely on slow connections or failed network requests.
- Full-screen ads fired directly on top of Google UMP consent forms on fresh installs.
- Native ads suffered from nasty Cumulative Layout Shift (CLS) because container pixel heights were guessed.
- Users escaped paywall exit interstitials simply by tapping Android's hardware back button or edge-swiping.
When our teams started utilizing AI coding agents (Cursor, Claude Code, Copilot, Antigravity), these edge cases compounded. Agents generated ad code that appeared correct on the surface, but broke in production when users experienced poor network connectivity or offline conditions.
The 3 Monetization Architecture Experiments
To solve this once and for all, we benchmarked and field-tested three distinct monetization architectures across native iOS (Swift), native Android (Kotlin), and Flutter:
| Architecture | Strategy | Pros | Honest Engineering Trade-offs (Cons) |
|---|---|---|---|
| 1. Eager Immediate-Display (Standardized in this package) |
Always keep ads primed and ready in memory. If the user arrives at an ad touchpoint, show it with 0ms wait. | • Maximum impression volume. • Zero user wait time (no spinners or stalled navigation). • Higher overall ad revenue. • Deterministic UX. |
• Lower show rate (impressions / requests) because cached ads may expire if unused. • Slightly higher baseline RAM footprint from buffer priming. |
| 2. High-Probability Preloading (Testing in separate plugin) |
Preload ads only when user telemetry indicates a 70–90% likelihood of reaching an ad touchpoint. | • Balanced show rate and ad freshness. • Moderate request efficiency. |
• Can still introduce subtle latency if user navigates faster than the ad downloads. |
| 3. Lossless / Predictive (Testing in separate plugin) |
Load ads strictly on demand with a 50% probability heuristic, waiting for completion before proceeding. | • Highest fill-rate conservation. • Lowest memory footprint. |
• Stalls user navigation with loading delays, fallback timeouts, and network checks. |
AdmobKit is our company-standardized solution for the Eager Immediate-Display strategy. We built it into an unshakeable Flutter plugin so our engineers and AI agents literally cannot mess up mobile ad integrations.
We have open-sourced it for any team whose product architecture prioritizes 0ms user wait times, maximum impression volume, and rock-solid UI stability.
The Core Mental Model (The 4 Foundational Laws)
- The 0ms Show Contract:
AdmobKit.show(placement, onDismissed: ...)is strictly non-blocking. If an ad is ready in memory, it displays instantly. If unready, expired, or offline, it invokesonDismissedimmediately with 0ms delay. Navigation is never stalled. - Deterministic Settlement (
waitFor): Network responses, UMP consent, and remote configs are asynchronous. Never guess completion times withTimer()orFuture.delayed(). Always useawait AdmobKit.waitFor(placement)composed inFuture.wait. - Immediate-Display Priority: At boot or screen mount, allocate 100% of network bandwidth to the ad the user is currently looking at. Background preloads (e.g. downstream click interstitials) wait until the immediate ad resolves.
- Template-Bound Native Dimensions: Native ad dimensions are strictly owned by native layouts (
small,medium,big). Flutter code never guesses container heights—always bind totemplate.height.
Quick Comparison: Stock GMA vs. AdmobKit
| Feature | Standard google_mobile_ads |
AdmobKit (admob_kit_flutter) |
|---|---|---|
| Fullscreen Presentation | Manual state tracking, risk of showing stale/empty ads | Instant 0ms display or immediate pass-through |
| Splash Screen Navigation | Guesswork with Future.delayed(3s) or timers |
Deterministic waitFor composed with Auth/Config |
| Native Ad Container Sizing | Manual height guessing (causes layout shifts) | Pre-sized templates with zero Cumulative Layout Shift |
| Android Hardware Back | Ignored by default (users bypass exit ads) | Built-in AdPaywallGuard with PopScope interception |
| VIP / In-App Purchase | Manual conditional checks across every widget | Universal entitlement callback (isPremium) |
| Consent (GDPR / ATT) | Boilerplate UMP and ATT orchestration | Automated two-stage boot handling UMP & ATT |
| Network Adaptation | Uniform timeouts regardless of connectivity | Adaptive timeouts (Wi-Fi vs Cellular vs Offline) |
Platform Setup
1. Android Setup
Open android/app/src/main/AndroidManifest.xml and add your Google AdMob App ID inside the <application> tag:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="your_app"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<!-- Google AdMob Application ID -->
<!-- Use test ID during development: ca-app-pub-3940256099942544~3347511713 -->
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-3940256099942544~3347511713"/>
<!-- Required for Google User Messaging Platform (UMP) -->
<meta-data
android:name="com.google.android.gms.ads.flag.OPTIMIZE_INITIALIZATION"
android:value="true"/>
<meta-data
android:name="com.google.android.gms.ads.flag.OPTIMIZE_AT_STARTUP"
android:value="true"/>
</application>
</manifest>
2. iOS Setup
Open ios/Runner/Info.plist and add your AdMob App ID, SKAdNetwork tracking identifiers, and App Tracking Transparency (ATT) description:
<key>GADApplicationIdentifier</key>
<!-- Use test ID during development: ca-app-pub-3940256099942544~1458002511 -->
<string>ca-app-pub-3940256099942544~1458002511</string>
<!-- App Tracking Transparency permission prompt description -->
<key>NSUserTrackingUsageDescription</key>
<string>This identifier will be used to deliver personalized ads to you.</string>
<!-- Google AdMob SKAdNetwork Identifier -->
<key>SKAdNetworkItems</key>
<array>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>cstr6suwn9.skadnetwork</string>
</dict>
</array>
Production Canonical Recipes
Recipe 1: Two-Stage Boot & Remote Config Orchestration
Call Stage 1 in main() so GDPR/UMP consent and the GMA SDK initialize immediately at cold boot without waiting on network configs. Call Stage 2 as soon as placement IDs are known.
import 'package:flutter/material.dart';
import 'package:admob_kit_flutter/admob_kit_flutter.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Stage 1: Initialize Consent (UMP + ATT) and GMA SDK at cold boot
await AdmobKit.initialize(
config: AdmobKitConfig(
initialConcurrency: 1, // Single-thread radio to optimize slow networks
subsequentConcurrency: 1,
isPremium: () => UserStore.isVip, // Universal ad suppression gate
),
);
runApp(const MyApp());
}
// In your app bootstrap / remote config service:
void onRemoteConfigLoaded() {
// Stage 2: Prime placements once IDs are resolved from network
AdmobKit.registerPlacements([
AppAds.splashInterstitial,
AppAds.feedNative,
AppAds.mainBanner,
]);
}
Recipe 2: Composable Deterministic Splash Gate
Never use arbitrary Timer() or Future.delayed(). Compose Remote Config, Auth token restoration, and ad preloading in parallel. If the ad is ready, show it; if it fails or times out, proceed immediately with 0ms user wait.
Future<void> handleSplashSequence(BuildContext context) async {
// Parallel deterministic wait for startup dependencies
await Future.wait([
FirebaseRemoteConfig.instance.fetchAndActivate(),
AuthService.instance.restoreSession(),
AdmobKit.waitFor(AppAds.splashInterstitial),
]);
if (!context.mounted) return;
// 0ms Non-blocking show contract
AdmobKit.show(
AppAds.splashInterstitial,
onDismissed: () {
Navigator.pushReplacementNamed(
context,
AuthService.instance.isLoggedIn ? '/home' : '/onboarding',
);
},
);
}
Recipe 3: Feed / List Native Ad with Zero Layout Shift
Always bind the container height directly to template.height. Pre-sized native templates (big, medium, small) map directly to native Android XML and iOS Auto Layout views.
Widget buildFeedNativeAd() {
final template = NativeAdTemplate.medium;
return Container(
height: template.height, // Concrete template height (130dp) — NEVER guess
width: double.infinity,
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFFE2E8F0)),
),
clipBehavior: Clip.antiAlias,
child: AdNativeView.templated(
placement: AppAds.feedNative,
template: template,
),
);
}
Recipe 4: Navigation Shell Sticky Banner
Use AdBannerView at the bottom of your main navigation shell. It automatically collapses to SizedBox.shrink() when a user upgrades to VIP.
Scaffold(
body: currentTabBody,
bottomNavigationBar: Column(
mainAxisSize: MainAxisSize.min,
children: [
const AdBannerView(
placement: AppAds.mainBottomBanner,
),
BottomNavigationBar(...),
],
),
);
Recipe 5: Paywall Exit Guard with Hardware Back Interception
Intercepts both the top visual close button and Android hardware back / edge-swipe navigation to trigger an exit interstitial on free tier. VIP users bypass with 0ms delay.
AdPaywallGuard(
placement: AppAds.paywallExitInterstitial,
onDismiss: () => Navigator.of(context).pop(),
builder: (context, triggerDismiss) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.close),
onPressed: triggerDismiss, // Triggers exit ad or passes through if VIP
),
),
body: PaywallContent(...),
);
},
);
Recipe 6: Interval Interstitials & Rewarded Features
// Interval trigger (e.g. every 3rd completed action)
void onTaskCompleted() {
_actionCounter++;
if (_actionCounter % 3 == 0) {
AdmobKit.show(AppAds.intervalInterstitial);
}
}
// Rewarded gate (e.g. unlocking a feature or bonus export)
void onUnlockFeature() {
AdmobKit.show(
AppAds.rewardedBooster,
onRewardGranted: (amount, type) {
applyUnlockedBenefit();
},
onDismissed: () {
// Refresh UI state
},
);
}
Recipe 7: Privacy & UMP Consent Updates in Settings
Provide users with the option to change their privacy and tracking preferences at any time as required by GDPR and App Store guidelines:
ListTile(
title: const Text('Privacy & Cookie Settings'),
leading: const Icon(Icons.privacy_tip_outlined),
onTap: () async {
final updated = await AdmobKit.showPrivacyOptionsForm();
if (updated) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Privacy preferences updated.')),
);
}
},
);
Recipe 8: Google Ad Inspector for QA
Open the native on-device Ad Inspector to verify ad mediation, fill status, and SDK adapters during QA builds:
void openInspector() {
AdmobKit.openAdInspector((error) {
if (error != null) {
debugPrint('Ad Inspector error: $error');
}
});
}
Observability & Extensibility
AdmobKit is designed with clean, decoupled contracts for logging, analytics, and diagnostics:
AdmobKit.initialize(
config: AdmobKitConfig(
// Pipe impression-level revenue (ILRD) to Firebase / AppsFlyer / Adjust
analytics: MyAnalyticsTracker(),
// Pipe slow network timeouts and no-fills to Sentry / Crashlytics
diagnostics: MyDiagnosticsTracker(),
// Custom logger or quiet in production
logLevel: kReleaseMode ? AdLogLevel.none : AdLogLevel.info,
// Network timeout configuration (Wi-Fi vs Cellular)
timeouts: AdTimeoutConfig.standard,
),
);
AI Agent Integration Runbook (skills.sh)
This package includes a specialized AI Agent skill located at skills/flutter-ads/SKILL.md.
Automatic Skill Installation (Any Agent)
Install the skill directly into your project using the skills.sh CLI:
# Install to current project (Claude Code, Cursor, Antigravity, Copilot, etc.)
npx skills add shahid0/AdmobKit-flutter
# Or install globally across all projects
npx skills add shahid0/AdmobKit-flutter -g
Golden Developer Prompts for AI Coding Assistants
When directing AI agents (Cursor, Claude Code, Antigravity, Copilot), paste these exact formulas:
-
Initial Setup:
"Integrate
admob_kit_flutterusing theflutter-adsskill. Configure two-stage initialization with Stage 1 inmain.dartand Stage 2 inRemoteConfigService. Set startup concurrency to 1." -
Splash Integration:
"Implement the splash screen using the AdmobKit deterministic settlement pattern. Compose
AdmobKit.waitFor(splashInterstitial)with app bootstrap futures, and useAdmobKit.showon proceed. Zero arbitrary timers and no display over UMP consent." -
Feed / Native Card:
"Add a medium native ad card at index 3 in the main feed using
NativeAdTemplate.medium.heightandAdNativeView.templated. Ensure zero cumulative layout shift." -
Paywall Guard:
"Wrap the paywall screen in
AdPaywallGuardusingpaywallExitInterstitialto intercept both hardware back navigation and visual close button clicks."
License & Credits
Free and open source under the MIT License.
Created and maintained by Shahid. You are free to use this package in commercial products and live applications without needing in-app attribution. Documentation, forks, and source distributions must preserve the original copyright notice.
Libraries
- admob_kit_flutter
- Production-grade Google AdMob plugin for Flutter with instant 0ms display, UMP GDPR consent, zero-CLS native ad templates, banners, interstitials, and rewarded ads.
- flutter_ads
- Legacy library entrypoint for backwards compatibility.