Project: Memory Card Game
Ads Integrated: 5 out of 6 AdMob formats
Status: ✅ Production-ready (with test IDs)
Status: ✅ Implemented
Location: Top & Bottom của HomePage
Manager: banner_ad_manager.dart
Widget: banner_ad_widget.dart
Implementation:
// Reusable widget
const BannerAdWidget()
// Placed in:
- HomePage top
- HomePage bottomCharacteristics:
- ✅ Always visible during gameplay
- ✅ Non-intrusive, small size (50px height)
- ✅ Preloaded on page init
- ✅ Auto-refresh by AdMob
- 💰 eCPM: Low but consistent
User Experience:
- Minimal disruption
- Passive income stream
- Users quickly ignore them
Status: ✅ Implemented
Location: Between levels & replay
Manager: interstital_ad_manager.dart
Usage Points:
// 1. Next Level
void _nextLevel() {
_adManager.showAd(
onAdDismissed: () {
// Continue to next level
},
);
}
// 2. Play Again
void _onPlayAgain() {
_adManager.showAd(
onAdDismissed: () {
_resetGame();
},
);
}Characteristics:
- ✅ Full-screen ad
- ✅ Natural break points
- ✅ High engagement
- ✅ Cooldown logic to prevent spam
- 💰 eCPM: High
User Experience:
- Expected at level transitions
- Skipable after 5 seconds
- Not intrusive due to natural timing
Status: ✅ Implemented
Location: Help feature in HomePage
Manager: reward_ad_manager.dart
Usage:
void _useHelp() {
// Show dialog first
showDialog(...);
// User confirms
_rewardAdManager.showAd(
onUserEarnedReward: () {
// Give reward: auto-flip matching pair
_executeHelp(card1, card2);
},
onAdNotReady: () {
// Show error message
},
);
}Reward:
- Auto-flip 1 matching pair
- Doesn't count towards move limit
- Helps complete difficult levels
Characteristics:
- ✅ User opt-in (choice)
- ✅ Clear value proposition
- ✅ Must watch full ad
- ✅ Reward guaranteed after completion
- 💰 eCPM: Very High
User Experience:
- User controls when to watch
- Clear benefit explained upfront
- Fair exchange (ad for help)
Status: ✅ Implemented
Location: App foreground resume
Manager: app_open_ad_manager.dart (Global instance in main.dart)
Implementation:
// In main.dart
final appOpenAdManager = AppOpenAdManager(); // Global
class _MyAppState extends State<MyApp>
with WidgetsBindingObserver {
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
appOpenAdManager.showAdIfAvailable();
}
}
}Smart Cooldown Logic:
// Don't show if:
- Another ad was shown in last 4 hours
- User just watched rewarded/interstitial adCharacteristics:
- ✅ Monetizes returning users
- ✅ Shows when app resumes from background
- ✅ 4-hour cooldown
- ✅ Doesn't interrupt active sessions
- 💰 eCPM: Medium-High
User Experience:
- Only when returning to app
- Not shown after other ads (smart!)
- Feels natural, not spammy
Status: ✅ Implemented
Location: LevelSelectionPage
Manager: native_ad_manager.dart
Implementation:
// In LevelSelectionPage
Container(
height: 120, // Small, non-intrusive
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: AdWidget(ad: _nativeAd!),
),
)Placement:
- Between level grid and play button
- Center of screen
- Blends with UI design
Characteristics:
- ✅ Customizable appearance
- ✅ Blends into app UI
- ✅ Less disruptive than banner
- ✅ Better CTR than banner
- 💰 eCPM: Medium
User Experience:
- Looks like part of the app
- Natural placement
- Not jarring or annoying
Status: ❌ Not Implemented
Reason: Not necessary - already have both Interstitial & Rewarded
When to use:
- "Continue" feature after losing
- Unlock premium features
- Skip timers
Why we don't need it:
- Current monetization is sufficient
- Too many ad types can hurt UX
- Interstitial + Rewarded covers all use cases
1. App Launch
└─> (No ad - good first impression)
2. IntroPage
└─> (No ad - quick intro)
3. LevelSelectionPage
├─> 📱 Native Ad (120px, blends in)
└─> Select level
4. HomePage - Playing
├─> 📰 Banner Ad (Top)
├─> 📰 Banner Ad (Bottom)
├─> [Optional] 🎁 Rewarded Ad (Help button)
└─> Game over
5. Game Over
├─> Win → 📺 Interstitial Ad → Next Level
└─> Lose → 📺 Interstitial Ad → Retry
6. App Background → Foreground
└─> 🚪 App Open Ad (if cooldown passed)
| Ad Type | Frequency | Cooldown |
|---|---|---|
| Banner | Always visible | None (persistent) |
| Interstitial | Per level transition | After each ad shown |
| Rewarded | User-triggered | None (opt-in) |
| App Open | On app resume | 4 hours + after other ads |
| Native | On page load | None (static) |
| Ad Type | CPM Range | Priority | Use Case |
|---|---|---|---|
| Rewarded | $10-$40 | ⭐⭐⭐⭐⭐ | High value, opt-in |
| Interstitial | $5-$15 | ⭐⭐⭐⭐ | Natural breaks |
| App Open | $3-$10 | ⭐⭐⭐ | Re-engagement |
| Native | $2-$8 | ⭐⭐⭐ | Blend well |
| Banner | $0.50-$3 | ⭐⭐ | Low but consistent |
Note: Actual eCPM varies by:
- Geographic location
- User demographics
- App category
- Ad quality
- Fill rate
-
No Ad Spam
- Cooldown logic for App Open Ad
- Natural break points for Interstitial
- User choice for Rewarded
-
Proper Disposal
@override void dispose() { _adManager.dispose(); super.dispose(); }
-
Error Handling
- Graceful fallback when ads fail to load
- User feedback for ad not ready
-
Test IDs During Development
- All current IDs are Google test IDs
- Safe to test without policy violations
-
Preloading
- Ads load in
initState() - Ready when needed
- Minimal wait time for users
- Ads load in
-
Callbacks
- All ad managers support callbacks
- Actions continue after ads dismiss
- Proper lifecycle management
-
Get Real Ad Unit IDs from AdMob Console
- Create app in AdMob
- Create ad units for each format
- Copy Ad Unit IDs
-
Update constants.dart
// Replace test IDs static String get bannerAdUnitId => Platform.isAndroid ? 'ca-app-pub-YOUR_ID/BANNER_ID' : 'ca-app-pub-YOUR_ID/BANNER_ID_IOS';
-
Test on Real Devices
- Verify all ads load
- Check ad placement
- Test user flow
-
Monitor Performance
- Check AdMob dashboard
- Watch fill rates
- Optimize placement if needed
class AdConstants {
// Banner
static String get bannerAdUnitId => Platform.isAndroid
? 'ca-app-pub-3940256099942544/6300978111' // Test
: 'ca-app-pub-3940256099942544/2934735716'; // Test
// Interstitial
static String get interstitialAdUnitId => Platform.isAndroid
? 'ca-app-pub-3940256099942544/1033173712' // Test
: 'ca-app-pub-3940256099942544/4411468910'; // Test
// Rewarded
static String get rewardedAdUnitId => Platform.isAndroid
? 'ca-app-pub-3940256099942544/5224354917' // Test
: 'ca-app-pub-3940256099942544/1712485313'; // Test
// App Open
static String get appOpenAdUnitId => Platform.isAndroid
? 'ca-app-pub-3940256099942544/9257395921' // Test
: 'ca-app-pub-3940256099942544/5575463023'; // Test
// Native
static String get nativeAdUnitId => Platform.isAndroid
? 'ca-app-pub-3940256099942544/2247696110' // Test
: 'ca-app-pub-3940256099942544/3986624511'; // Test
}-
User-First Approach
- Ads don't interrupt core gameplay
- Rewarded ads offer real value
- Clear timing for interstitials
-
Technical Excellence
- Clean architecture (services folder)
- Proper lifecycle management
- Reusable components
-
Smart Monetization
- Multiple revenue streams
- High-value ad formats (Rewarded)
- Strategic placement
-
Scalable Code
- Easy to add more ad placements
- Centralized configuration
- Consistent patterns
- AdMob Docs: https://developers.google.com/admob/flutter
- Best Practices: https://support.google.com/admob/answer/6128543
- Policy: https://support.google.com/admob/answer/9999955
Summary: Professional AdMob integration with 5 ad formats, user-friendly implementation, and production-ready architecture! 🚀