Monetizing a mobile app is one of the most important decisions an indie developer faces. After experimenting with several ad networks, I settled on Google AdMob for my Flutter apps — and I haven’t looked back. In this guide, I’ll walk you through the entire process of integrating AdMob into a Flutter app, from initial setup to going live with real ads.
Why AdMob for Flutter?
Google AdMob is the most widely used mobile advertising platform, and for good reason. It offers high fill rates, competitive eCPMs, and seamless integration with the Flutter ecosystem through the official google_mobile_ads package. As an indie developer building apps like Happy Balloon Pop, I appreciate the straightforward dashboard and reliable payouts.
Setting Up the google_mobile_ads Package
First, add the package to your pubspec.yaml:
dependencies:
google_mobile_ads: ^5.3.0
Run flutter pub get to install it. This package is maintained by Google and supports both Android and iOS platforms.
Android Configuration
Open your android/app/src/main/AndroidManifest.xml and add your AdMob App ID inside the <application> tag:
<manifest>
<application>
<!-- Your AdMob App ID -->
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-XXXXXXXXXXXXXXXX~YYYYYYYYYY"/>
</application>
</manifest>
Make sure your android/app/build.gradle has a minimum SDK version of 21 or higher:
android {
defaultConfig {
minSdkVersion 21
}
}
iOS Configuration
For iOS, open ios/Runner/Info.plist and add your AdMob App ID:
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-XXXXXXXXXXXXXXXX~YYYYYYYYYY</string>
You also need to add the SKAdNetworkItems for proper attribution. Google provides a list of SKAdNetwork identifiers that you should include. You can find the latest list in the AdMob documentation and add them to your Info.plist.
Initializing the Mobile Ads SDK
Before loading any ads, you need to initialize the SDK. I recommend doing this in your main.dart file:
import 'package:google_mobile_ads/google_mobile_ads.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await MobileAds.instance.initialize();
runApp(const MyApp());
}
The initialize() call should happen before you attempt to load any ads. It returns a Future<InitializationStatus> that you can use to check which adapters are ready, but in practice, simply awaiting it is sufficient.
Implementing Banner Ads
Banner ads are the simplest ad format and work well at the top or bottom of a screen. Here’s how I implement them:
class BannerAdWidget extends StatefulWidget {
const BannerAdWidget({super.key});
@override
State<BannerAdWidget> createState() => _BannerAdWidgetState();
}
class _BannerAdWidgetState extends State<BannerAdWidget> {
BannerAd? _bannerAd;
bool _isLoaded = false;
@override
void initState() {
super.initState();
_loadAd();
}
void _loadAd() {
_bannerAd = BannerAd(
adUnitId: 'ca-app-pub-3940256099942544/6300978111', // Test ID
size: AdSize.banner,
request: const AdRequest(),
listener: BannerAdListener(
onAdLoaded: (ad) {
setState(() => _isLoaded = true);
},
onAdFailedToLoad: (ad, error) {
ad.dispose();
debugPrint('Banner ad failed to load: $error');
},
),
)..load();
}
@override
Widget build(BuildContext context) {
if (!_isLoaded || _bannerAd == null) {
return const SizedBox.shrink();
}
return SizedBox(
width: _bannerAd!.size.width.toDouble(),
height: _bannerAd!.size.height.toDouble(),
child: AdWidget(ad: _bannerAd!),
);
}
@override
void dispose() {
_bannerAd?.dispose();
super.dispose();
}
}
One important lesson I learned: always dispose of your ads properly. Failing to do so can cause memory leaks, especially on screens that the user navigates to and from frequently.
Implementing Interstitial Ads
Interstitial ads are full-screen ads that cover the entire interface. They work best at natural transition points — for example, between game levels in my Happy Balloon Pop app.
class InterstitialAdManager {
InterstitialAd? _interstitialAd;
bool _isReady = false;
void loadAd() {
InterstitialAd.load(
adUnitId: 'ca-app-pub-3940256099942544/1033173712', // Test ID
request: const AdRequest(),
adLoadCallback: InterstitialAdLoadCallback(
onAdLoaded: (ad) {
_interstitialAd = ad;
_isReady = true;
_interstitialAd!.fullScreenContentCallback =
FullScreenContentCallback(
onAdDismissedFullScreenContent: (ad) {
ad.dispose();
_isReady = false;
loadAd(); // Pre-load the next one
},
onAdFailedToShowFullScreenContent: (ad, error) {
ad.dispose();
_isReady = false;
loadAd();
},
);
},
onAdFailedToLoad: (error) {
debugPrint('Interstitial ad failed to load: $error');
_isReady = false;
},
),
);
}
void showAd() {
if (_isReady && _interstitialAd != null) {
_interstitialAd!.show();
}
}
}
A key best practice: pre-load the next interstitial ad immediately after the current one is dismissed. This ensures an ad is ready whenever the user reaches the next transition point.
Implementing Rewarded Ads
Rewarded ads let users watch a video in exchange for an in-app reward. This is my favorite ad format because it creates a positive experience — users choose to watch the ad and get something in return.
class RewardedAdManager {
RewardedAd? _rewardedAd;
bool _isReady = false;
void loadAd() {
RewardedAd.load(
adUnitId: 'ca-app-pub-3940256099942544/5224354917', // Test ID
request: const AdRequest(),
rewardedAdLoadCallback: RewardedAdLoadCallback(
onAdLoaded: (ad) {
_rewardedAd = ad;
_isReady = true;
},
onAdFailedToLoad: (error) {
debugPrint('Rewarded ad failed to load: $error');
_isReady = false;
},
),
);
}
void showAd({required Function(int amount) onRewarded}) {
if (!_isReady || _rewardedAd == null) return;
_rewardedAd!.fullScreenContentCallback = FullScreenContentCallback(
onAdDismissedFullScreenContent: (ad) {
ad.dispose();
_isReady = false;
loadAd(); // Pre-load next ad
},
);
_rewardedAd!.show(
onUserEarnedReward: (ad, reward) {
onRewarded(reward.amount.toInt());
},
);
}
}
In Happy Balloon Pop, I use rewarded ads to give players extra lives or bonus items. The conversion rate is surprisingly high — players are happy to watch a 15-30 second video for a meaningful reward.
Ad Placement Best Practices
Through trial and error, I’ve developed some rules for ad placement:
- Never interrupt gameplay. Show interstitial ads only at natural breaks, like between levels or after a game over screen.
- Limit interstitial frequency. I show interstitial ads every 3-4 level transitions, not every single one. Over-showing ads drives users away.
- Place banners where they won’t interfere. The bottom of the screen works well for banner ads. Avoid placing them where users might accidentally tap them.
- Make rewarded ads optional. Always give users the choice to watch or skip. Never force a rewarded ad.
- Respect the user experience. If your app targets children (like some of my apps), be extra careful about ad placement and follow COPPA guidelines.
Testing with Test Ad Units
Google provides test ad unit IDs that you should always use during development:
| Ad Format | Test Ad Unit ID |
|---|---|
| Banner | ca-app-pub-3940256099942544/6300978111 |
| Interstitial | ca-app-pub-3940256099942544/1033173712 |
| Rewarded | ca-app-pub-3940256099942544/5224354917 |
Never use your real ad unit IDs during development. Google can detect invalid traffic and may suspend your AdMob account. I’ve heard horror stories from other developers who lost their accounts this way.
You can also enable test mode on specific devices by registering your device’s advertising ID:
MobileAds.instance.updateRequestConfiguration(
RequestConfiguration(testDeviceIds: ['YOUR_DEVICE_ID']),
);
Going Live Checklist
Before switching from test ads to real ads, go through this checklist:
- Replace all test ad unit IDs with your real ad unit IDs from the AdMob console.
- Set up your app-ads.txt file. This is an IAB standard that helps prevent ad fraud. Host it at your developer website’s root URL (e.g.,
https://yourdomain.com/app-ads.txt). - Verify your payment information in AdMob settings.
- Review ad policies. Make sure your app complies with AdMob policies, especially regarding content, placement, and user experience.
- Remove any test device configurations from your production build.
- Test the production build on a real device before submitting to the app stores.
Setting Up app-ads.txt
The app-ads.txt file is crucial for ad revenue. It tells ad networks that you’re an authorized seller of your app’s ad inventory. For AdMob, the file should contain:
google.com, pub-XXXXXXXXXXXXXXXX, DIRECT, f08c47fec0942fa0
Replace pub-XXXXXXXXXXXXXXXX with your AdMob publisher ID. Host this file at the root of the website URL you’ve registered in the app stores as your developer website.
Wrapping Up
Integrating AdMob into a Flutter app is straightforward once you understand the setup process. The key is to balance monetization with user experience. Respect your users, test thoroughly with test ad units, and follow Google’s policies to keep your account in good standing.
In my experience building and monetizing apps as an indie developer, the apps that treat ads as a complement to the experience — rather than the primary focus — tend to perform better in both user retention and ad revenue. Focus on building a great app first, then monetize thoughtfully.
If you’re just starting with Flutter app monetization, I recommend beginning with banner ads to get familiar with the integration process, then adding interstitial and rewarded ads as you refine your app’s user flow.