By the end of this lesson, you will:
Advertising monetizes free users without requiring payment. Apps like Instagram, TikTok, and mobile games generate billions from ads. But bad advertising destroys user experience: intrusive popups, misleading buttons, endless interruptions.
Great advertising feels natural. Banner ads occupy unused space. Interstitials appear between levels. Rewarded ads give users choice: watch for bonus content. When done responsibly, ads fund free apps users love. Let's implement advertising that respects users.
AdMob offers three main formats:
One. Banner Ads
2. Interstitial Ads
3. Rewarded Ads
Install react-native-google-mobile-ads:
npm install react-native-google-mobile-ads
npx react-native-google-mobile-ads:install
Configure AdMob App ID in app.json:
{
"expo": {
"android": {
"googleServicesFile": "./google-services.json",
"config": {
"googleMobileAdsAppId": "ca-app-pub-XXXXX~YYYYY"
}
}
}
}
Initialize AdMob:
import mobileAds from 'react-native-google-mobile-ads';
import { useEffect } from 'react';
export default function AdMobSetup() {
useEffect(() => {
initializeAds();
}, []);
const initializeAds = async () => {
try {
await mobileAds().initialize();
console.log('AdMob initialized');
// Optional: Set request configuration
await mobileAds().setRequestConfiguration({
maxAdContentRating: 'T', // Teen
tagForChildDirectedTreatment: false,
tagForUnderAgeOfConsent: false,
});
} catch (error) {
console.error('AdMob init error:', error);
}
};
}
Display persistent banner ads:
import { BannerAd, BannerAdSize, TestIds } from 'react-native-google-mobile-ads';
import { View, StyleSheet } from 'react-native';
const BANNER_AD_UNIT_ID = __DEV__
? TestIds.BANNER
: 'ca-app-pub-XXXXX/YYYYY';
export default function BannerAdComponent() {
return (
<View style={styles.container}>
<BannerAd
unitId={BANNER_AD_UNIT_ID}
size={BannerAdSize.ANCHORED_ADAPTIVE_BANNER}
requestOptions={{
requestNonPersonalizedAdsOnly: false,
}}
onAdLoaded={() => {
console.log('Banner ad loaded');
}}
onAdFailedToLoad={(error) => {
console.error('Banner ad failed:', error);
}}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
alignItems: 'center',
marginVertical: 10,
},
});
Banner Sizes:
BANNER: 320x50 (standard)FULL_BANNER: 468x60 (tablets)LARGE_BANNER: 320x100 (larger)MEDIUM_RECTANGLE: 300x250 (inline content)ANCHORED_ADAPTIVE_BANNER: Responsive widthShow full-screen ads at transitions:
import {
InterstitialAd,
AdEventType,
TestIds,
} from 'react-native-google-mobile-ads';
import { useEffect, useState } from 'react';
const INTERSTITIAL_AD_UNIT_ID = __DEV__
? TestIds.INTERSTITIAL
: 'ca-app-pub-XXXXX/YYYYY';
export default function InterstitialAdManager() {
const [interstitialAd, setInterstitialAd] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
const ad = InterstitialAd.createForAdRequest(INTERSTITIAL_AD_UNIT_ID, {
requestNonPersonalizedAdsOnly: false,
});
// Load ad
const loadListener = ad.addAdEventListener(AdEventType.LOADED, () => {
console.log('Interstitial ad loaded');
setIsLoaded(true);
});
// Ad shown
const shownListener = ad.addAdEventListener(AdEventType.OPENED, () => {
console.log('Interstitial ad shown');
});
// Ad closed
const closedListener = ad.addAdEventListener(AdEventType.CLOSED, () => {
console.log('Interstitial ad closed');
setIsLoaded(false);
// Load next ad
ad.load();
});
// Load the ad
ad.load();
setInterstitialAd(ad);
return () => {
loadListener();
shownListener();
closedListener();
};
}, []);
const showAd = () => {
if (isLoaded && interstitialAd) {
interstitialAd.show();
} else {
console.log('Interstitial ad not ready');
}
};
return { showAd, isLoaded };
}
Best Practices for Interstitials:
Let users opt-in to watch for rewards:
import {
RewardedAd,
RewardedAdEventType,
TestIds,
} from 'react-native-google-mobile-ads';
import { useEffect, useState } from 'react';
import { Alert } from 'react-native';
const REWARDED_AD_UNIT_ID = __DEV__
? TestIds.REWARDED
: 'ca-app-pub-XXXXX/YYYYY';
export default function RewardedAdManager() {
const [rewardedAd, setRewardedAd] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
const ad = RewardedAd.createForAdRequest(REWARDED_AD_UNIT_ID, {
requestNonPersonalizedAdsOnly: false,
});
// Ad loaded
const loadListener = ad.addAdEventListener(RewardedAdEventType.LOADED, () => {
console.log('Rewarded ad loaded');
setIsLoaded(true);
});
// Ad earned reward
const rewardListener = ad.addAdEventListener(
RewardedAdEventType.EARNED_REWARD,
(reward) => {
console.log('User earned reward:', reward);
grantReward(reward);
}
);
// Ad closed
const closedListener = ad.addAdEventListener(RewardedAdEventType.CLOSED, () => {
console.log('Rewarded ad closed');
setIsLoaded(false);
// Load next ad
ad.load();
});
ad.load();
setRewardedAd(ad);
return () => {
loadListener();
rewardListener();
closedListener();
};
}, []);
const showAd = () => {
if (isLoaded && rewardedAd) {
rewardedAd.show();
} else {
Alert.alert('Ad Not Ready', 'Please try again in a moment');
}
};
const grantReward = (reward) => {
// Grant in-app currency, extra life, etc.
Alert.alert('Reward Earned!', `You received ${reward.amount} ${reward.type}`);
};
return { showAd, isLoaded };
}
Rewarded Ad UX:
Optimize placement for revenue and UX:
export const AdPlacementStrategy = {
// Banner ads
banners: {
topBanner: {
location: 'Top of screen',
bestFor: 'Content apps where scrolling is natural',
avoid: 'Near interactive elements',
},
bottomBanner: {
location: 'Bottom of screen',
bestFor: 'Games, apps with main action at top',
avoid: 'Apps with bottom navigation',
},
},
// Interstitial ads
interstitials: {
timing: [
'Between game levels',
'After completing task',
'When leaving section',
'After X minutes of use',
],
avoid: [
'During active gameplay',
'Mid-article reading',
'Every screen transition',
'More than once per 3 minutes',
],
implementation: () => {
let lastAdTime = 0;
const MIN_INTERVAL = 3 * 60 * 1000; // 3 minutes
const shouldShowAd = () => {
const now = Date.now();
if (now - lastAdTime < MIN_INTERVAL) {
return false;
}
lastAdTime = now;
return true;
};
return shouldShowAd;
},
},
// Rewarded ads
rewarded: {
opportunities: [
'Continue after failure (extra life)',
'Speed up timer',
'Unlock premium content temporarily',
'Earn in-game currency',
'Get hints/help',
],
presentation: () => {
Alert.alert(
'Watch Ad for Reward?',
'Watch a short video to earn 50 coins',
[
{ text: 'No Thanks', style: 'cancel' },
{ text: 'Watch Ad', onPress: showRewardedAd },
]
);
},
},
};
Monitor ad performance:
export const AdAnalytics = {
// Track ad impressions
trackImpression: (adType, adUnitId) => {
// Analytics.logEvent('ad_impression', {
// ad_type: adType,
// ad_unit_id: adUnitId,
// timestamp: Date.now(),
// });
},
// Track ad clicks
trackClick: (adType, adUnitId) => {
// Analytics.logEvent('ad_click', {
// ad_type: adType,
// ad_unit_id: adUnitId,
// });
},
// Track reward granted
trackReward: (amount, type) => {
// Analytics.logEvent('ad_reward_granted', {
// reward_amount: amount,
// reward_type: type,
// });
},
// Calculate eCPM (effective cost per mille)
calculateECPM: (revenue, impressions) => {
return (revenue / impressions) * 1000;
},
};
Follow advertising guidelines:
export const AdMobPolicies = {
// DO's
allowed: [
'Place ads in natural breaks',
'Clearly label ads as advertisements',
'Allow users to close ads easily',
'Use age-appropriate content ratings',
'Respect user privacy settings',
],
// DON'Ts
prohibited: [
'Click own ads',
'Encourage clicks ("Click this ad")',
'Place ads near interactive elements',
'Hide close button',
'Show ads on splash screen',
'Auto-refresh ads too frequently',
'Show ads on error/crash screens',
],
// Child-directed content
coppa: {
// If app targets children <13
configure: () => {
mobileAds().setRequestConfiguration({
tagForChildDirectedTreatment: true,
maxAdContentRating: 'G',
});
},
},
// GDPR compliance (Europe)
gdpr: {
// Show consent dialog
requireConsent: true,
consentOptions: ['Personalized ads', 'Non-personalized ads'],
},
};
Handle loading and errors gracefully:
import { useState } from 'react';
import { View, Text, ActivityIndicator, StyleSheet } from 'react-native';
export default function AdWithLoadingState({ adComponent, adType }) {
const [isLoading, setIsLoading] = useState(true);
const [hasError, setHasError] = useState(false);
const handleAdLoaded = () => {
setIsLoading(false);
setHasError(false);
};
const handleAdError = (error) => {
console.error(`${adType} ad error:`, error);
setIsLoading(false);
setHasError(true);
};
return (
<View style={styles.container}>
{isLoading && (
<View style={styles.loadingContainer}>
<ActivityIndicator size="small" color="#999" />
</View>
)}
{hasError && (
<View style={styles.errorContainer}>
<Text style={styles.errorText}>Ad unavailable</Text>
</View>
)}
{!hasError && adComponent}
</View>
);
}
const styles = StyleSheet.create({
container: {
minHeight: 50,
justifyContent: 'center',
alignItems: 'center',
},
loadingContainer: {
padding: 10,
},
errorContainer: {
padding: 10,
},
errorText: {
fontSize: 12,
color: '#999',
},
});
Complete ad integration with all formats:
import { useState, useEffect } from 'react';
import {
View,
Text,
TouchableOpacity,
StyleSheet,
ScrollView,
SafeAreaView,
} from 'react-native';
import { BannerAd, BannerAdSize, TestIds } from 'react-native-google-mobile-ads';
import mobileAds from 'react-native-google-mobile-ads';
export default function AdMonetizedApp() {
const [coins, setCoins] = useState(100);
const [isPremium, setIsPremium] = useState(false);
const interstitialAd = InterstitialAdManager();
const rewardedAd = RewardedAdManager();
useEffect(() => {
mobileAds().initialize();
}, []);
const handleLevelComplete = () => {
// Show interstitial after completing level
if (!isPremium && interstitialAd.isLoaded) {
interstitialAd.showAd();
}
};
const handleWatchAdForCoins = () => {
if (rewardedAd.isLoaded) {
rewardedAd.showAd();
setCoins((prev) => prev + 50);
}
};
return (
<SafeAreaView style={styles.container}>
{/* Top Banner Ad (only for free users) */}
{!isPremium && (
<View style={styles.topBanner}>
<BannerAd
unitId={TestIds.BANNER}
size={BannerAdSize.ANCHORED_ADAPTIVE_BANNER}
/>
</View>
)}
<ScrollView style={styles.content}>
<Text style={styles.title}>Game App</Text>
<View style={styles.coinsContainer}>
<Text style={styles.coinsText}>💰 Coins: {coins}</Text>
</View>
<TouchableOpacity
style={styles.button}
onPress={handleLevelComplete}
>
<Text style={styles.buttonText}>Complete Level</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.rewardButton]}
onPress={handleWatchAdForCoins}
>
<Text style={styles.buttonText}>
Watch Ad for 50 Coins 📺
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.premiumButton]}
onPress={() => setIsPremium(true)}
>
<Text style={styles.buttonText}>Remove Ads - $2.99</Text>
</TouchableOpacity>
</ScrollView>
{/* Bottom Banner Ad (only for free users) */}
{!isPremium && (
<View style={styles.bottomBanner}>
<BannerAd
unitId={TestIds.BANNER}
size={BannerAdSize.BANNER}
/>
</View>
)}
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f5f5f5',
},
topBanner: {
backgroundColor: '#fff',
alignItems: 'center',
},
bottomBanner: {
backgroundColor: '#fff',
alignItems: 'center',
},
content: {
flex: 1,
padding: 20,
},
title: {
fontSize: 28,
fontWeight: 'bold',
marginBottom: 20,
},
coinsContainer: {
backgroundColor: '#FFD700',
borderRadius: 12,
padding: 16,
marginBottom: 20,
alignItems: 'center',
},
coinsText: {
fontSize: 24,
fontWeight: 'bold',
},
button: {
backgroundColor: '#007AFF',
borderRadius: 12,
padding: 16,
alignItems: 'center',
marginBottom: 12,
},
rewardButton: {
backgroundColor: '#4CAF50',
},
premiumButton: {
backgroundColor: '#FFD700',
},
buttonText: {
color: '#fff',
fontSize: 16,
fontWeight: 'bold',
},
});
| Pitfall | Solution |
|---|---|
| Ads everywhere | Limit placement, respect user experience |
| Intrusive timing | Show ads at natural breaks, not mid-action |
| Ignoring test ads | Use TestIds during development, real ads in production |
| No frequency cap | Limit interstitials to 1 per 3-5 minutes |
| Violating policies | Read AdMob policies, avoid prohibited practices |
In the next lesson, we'll explore Viral App Patterns, learning psychological triggers and mechanics that make apps naturally shareable.