In this guide you'll integrate Google AdMob banner ads into a Flutter app from scratch — clean architecture, proper service layer, and real working code. I follow this exact setup in the video above, so read through once, then build along.
What You'll Build
A Flutter app with a singleton AdMobService that loads and manages a banner ad. The ad sits pinned at the bottom of the screen. The setup works on both Android and iOS.
Step 1 — Add the Dependency
Open pubspec.yaml and add the package:
dependencies:
flutter:
sdk: flutter
google_mobile_ads: ^5.1.0
Then run:
flutter pub get
Step 2 — Android Setup
Open android/app/src/main/AndroidManifest.xml and add the AdMob App ID inside the <application> tag:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="admobflutter"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<!-- AdMob App ID (test ID) -->
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-3940256099942544~3347511713"/>
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<meta-data
android:name="flutterEmbedding"
android:value="2"/>
</application>
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>
Step 3 — iOS Setup
Open ios/Runner/Info.plist and add the GADApplicationIdentifier key:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Admobflutter</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>admobflutter</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<!-- AdMob App ID (test ID) -->
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-3940256099942544~1458002754</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneConfigurationName</key>
<string>flutter</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
Step 4 — Create the AdMob Service
Create lib/services/admob_service.dart. This is a singleton — one instance across the whole app, handles loading, disposing, and exposing the banner ad.
import 'package:google_mobile_ads/google_mobile_ads.dart';
class AdMobService {
static final AdMobService _instance = AdMobService._internal();
factory AdMobService() {
return _instance;
}
AdMobService._internal();
// ✅ Google's official test banner ad unit ID
// Replace with your real ID before publishing to the store
static const String bannerAdUnitId = 'ca-app-pub-3940256099942544/6300978111';
BannerAd? _bannerAd;
bool _isBannerAdReady = false;
/// Initialize Mobile Ads SDK — call this once in main()
Future<void> initialize() async {
await MobileAds.instance.initialize();
}
/// Load the banner ad
void loadBannerAd({
required Function(BannerAd) onAdLoaded,
required Function(BannerAd, LoadAdError) onAdFailedToLoad,
}) {
_bannerAd = BannerAd(
adUnitId: bannerAdUnitId,
size: AdSize.banner,
request: const AdRequest(),
listener: BannerAdListener(
onAdLoaded: (ad) {
_isBannerAdReady = true;
onAdLoaded(ad as BannerAd);
},
onAdFailedToLoad: (ad, error) {
ad.dispose();
onAdFailedToLoad(ad as BannerAd, error);
},
),
)..load();
}
BannerAd? getBannerAd() => _bannerAd;
bool isBannerAdReady() => _isBannerAdReady;
void disposeBannerAd() {
_bannerAd?.dispose();
_isBannerAdReady = false;
}
void disposeAll() {
_bannerAd?.dispose();
}
}
Step 5 — Initialize in main.dart
Initialize the SDK before runApp. One call, no extra config needed.
import 'package:flutter/material.dart';
import 'services/admob_service.dart';
import 'pages/home_page.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize AdMob SDK once at startup
final adMobService = AdMobService();
await adMobService.initialize();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Monetized App',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
appBarTheme: const AppBarTheme(
centerTitle: false,
elevation: 0,
),
),
home: const HomePage(),
);
}
}
Step 6 — Display the Banner Ad
Create lib/pages/home_page.dart. The banner is pinned at the bottom using a Stack with Positioned. Content scrolls above it with bottom padding so nothing hides behind the ad.
import 'package:flutter/material.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import '../services/admob_service.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
late AdMobService _adMobService;
bool _isBannerAdReady = false;
@override
void initState() {
super.initState();
_adMobService = AdMobService();
_loadBannerAd();
}
void _loadBannerAd() {
_adMobService.loadBannerAd(
onAdLoaded: (_) {
setState(() {
_isBannerAdReady = true;
});
},
onAdFailedToLoad: (ad, error) {
debugPrint('Banner Ad failed to load: ${error.message}');
},
);
}
@override
void dispose() {
_adMobService.disposeBannerAd();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('Monetized App'),
elevation: 2,
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Stack(
children: [
SingleChildScrollView(
child: Column(
children: [
_buildHeaderSection(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 20),
_buildTitleSection(),
const SizedBox(height: 24),
_buildFeatureCards(),
const SizedBox(height: 100),
],
),
),
],
),
),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: _buildBannerAdWidget(),
),
],
),
),
);
}
Widget _buildHeaderSection() {
return Container(
width: double.infinity,
height: 250,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Colors.deepPurple.shade400, Colors.deepPurple.shade800],
),
),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.monetization_on, size: 80, color: Colors.white),
const SizedBox(height: 16),
const Text(
'Monetized App',
style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold, color: Colors.white),
),
const SizedBox(height: 8),
const Text(
'Powered by Google AdMob',
style: TextStyle(fontSize: 16, color: Colors.white70),
),
],
),
),
);
}
Widget _buildTitleSection() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Welcome to Your App',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
Text(
'A professional monetized app with seamless Google Mobile Ads integration.',
style: TextStyle(fontSize: 16, color: Colors.grey.shade600, height: 1.5),
),
],
);
}
Widget _buildFeatureCards() {
return Column(
children: [
_buildFeatureCard(icon: Icons.monetization_on, title: 'Ad Integration', description: 'Seamless Google Mobile Ads integration'),
const SizedBox(height: 12),
_buildFeatureCard(icon: Icons.design_services, title: 'Professional UI', description: 'Clean and modern interface design'),
const SizedBox(height: 12),
_buildFeatureCard(icon: Icons.trending_up, title: 'Revenue Ready', description: 'Start earning from day one'),
const SizedBox(height: 12),
_buildFeatureCard(icon: Icons.architecture, title: 'Clean Architecture', description: 'Abstracted AdMob service layer'),
],
);
}
Widget _buildFeatureCard({required IconData icon, required String title, required String description}) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.deepPurple.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(icon, size: 32, color: Colors.deepPurple),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 4),
Text(description, style: TextStyle(fontSize: 14, color: Colors.grey.shade600)),
],
),
),
],
),
),
);
}
Widget _buildBannerAdWidget() {
if (!_isBannerAdReady) return const SizedBox();
final bannerAd = _adMobService.getBannerAd();
if (bannerAd == null) return const SizedBox();
return Container(
color: Colors.grey.shade100,
child: SizedBox(
height: bannerAd.size.height.toDouble(),
width: bannerAd.size.width.toDouble(),
child: AdWidget(ad: bannerAd),
),
);
}
}
Project Structure
Your final file structure:
lib/
├── main.dart
├── services/
│ └── admob_service.dart
└── pages/
└── home_page.dart
Test IDs Reference
All IDs used in this post are Google's official test suite. Never use real ad unit IDs during development:
# Android App ID
ca-app-pub-3940256099942544~3347511713
# iOS App ID
ca-app-pub-3940256099942544~1458002754
# Banner Ad Unit ID (works on both platforms)
ca-app-pub-3940256099942544/6300978111