AdMob SDK integration
Track AdMob events on iOS, Android, Flutter, React Native, and Unity with the RevenueCat AdMob adapters
The RevenueCat AdMob adapters wrap the standard Google Mobile Ads load calls with automatic event tracking. There is one adapter per platform; they share the same concepts, so this guide shows every platform side by side.
This feature is currently in beta.
Before integrating, install the AdMob Adapter SDK for your platform. The installation guide lists the minimum RevenueCat SDK and Google Mobile Ads versions.
Once an ad is loaded through the adapter, the following events are tracked from AdMob's callbacks with no additional code:
- Ad Loaded — Ad successfully loads
- Ad Displayed — Ad impression is recorded
- Ad Opened — Customer clicks the ad
- Ad Revenue — Ad generates revenue (via AdMob's paid event handler)
- Ad Failed to Load — Ad fails to load
- Ad Reward Earned — The user earns a rewarded ad's reward. With reward verification it is reported as part of the verification funnel instead; without it, the Flutter, React Native and Unity adapters report it as unverified when the ad is shown through
showAndTrack
The adapters can't track Rewarded Ad Prompt Shown and Rewarded Ad Prompt Accepted, because only your app knows when it offers a rewarded ad. Call those two methods yourself; see Track rewarded ad prompts.
Choose an adapter
Pick the code tab that matches your platform and Google Mobile Ads SDK:
| Platform | Google Mobile Ads SDK | Code tab |
|---|---|---|
| iOS | Google Mobile Ads SDK | Swift |
| Android | Legacy Google Mobile Ads SDK (play-services-ads) | Kotlin (Legacy AdMob) |
| Android | Google Mobile Ads Next-Gen SDK (ads-mobile-sdk) | Kotlin (Next-Gen AdMob) |
| Flutter | google_mobile_ads plugin | Flutter |
| React Native | react-native-google-mobile-ads | React Native |
| Unity | Google Mobile Ads Unity plugin | Unity |
The two Android adapters are mutually exclusive. The Next-Gen adapter is Kotlin-only; Java apps can use the base AdTracker APIs directly.
Quick start
1. Configure the SDKs and import the adapter
Configure Purchases and initialize the Google Mobile Ads SDK as you normally would, before the first ad loads; the adapter only tracks while Purchases is configured. On Unity, also point the adapter at your configured Purchases component once with RevenueCatAdMob.Initialize(purchases); the Unity SDK has no shared singleton. The snippets on this page use Google's test ad unit IDs (TestIds on React Native, which picks the right ID per platform); replace them with your own. The IDs hardcoded in the Flutter snippets are the iOS test IDs; on Android use the matching Android test ad unit IDs.
- Swift
- Kotlin (Legacy AdMob)
- Kotlin (Next-Gen AdMob)
- Flutter
- React Native
- Unity
import RevenueCatAdMob
import com.revenuecat.purchases.Purchases
import com.revenuecat.purchases.admob.* // AdMob helper extensions
import com.revenuecat.purchases.Purchases
import com.revenuecat.purchases.admob.nextgen.* // AdMob Next-Gen helper extensions
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:purchases_admob_flutter/purchases_admob_flutter.dart';
import 'package:purchases_flutter/purchases_flutter.dart';
// Configure RevenueCat and initialize Google Mobile Ads before loading any ad.
await Purchases.configure(PurchasesConfiguration('<revenuecat_api_key>'));
await MobileAds.instance.initialize();
import Purchases from "react-native-purchases";
import mobileAds, { AdEventType, RewardedAdEventType, TestIds } from "react-native-google-mobile-ads";
import { RevenueCatAdMob } from "react-native-purchases-admob";
// Configure RevenueCat and initialize Google Mobile Ads before loading any ad.
await Purchases.configure({ apiKey: "<revenuecat_api_key>" });
await mobileAds().initialize();
using GoogleMobileAds.Api;
using RevenueCat.AdMob;
// Point the adapter at your configured Purchases component. Call once.
RevenueCatAdMob.Initialize(purchases);
MobileAds.Initialize(initStatus => { });
2. Replace load calls with the adapter
Replace the standard AdMob load call with the adapter's loadAndTrack equivalent and pass a placement. On React Native, create the ad through RevenueCatAdMob.create... instead of createForAdRequest; the returned object is the same ad class. Show full-screen ads through the adapter as well, so it can apply a show-time placement. On iOS, every method has an async/await and a completion-handler variant:
- Swift (async/await)
- Swift (completion handler)
- Kotlin (Legacy AdMob)
- Kotlin (Next-Gen AdMob)
- Flutter
- React Native
- Unity
// Before
let ad = try await InterstitialAd.load(with: adUnitID, request: Request())
ad.fullScreenContentDelegate = self
self.interstitialAd = ad
// After
self.interstitialAd = try await InterstitialAd.loadAndTrack(
withAdUnitID: adUnitID,
request: Request(),
placement: "level_complete",
fullScreenContentDelegate: self
)
// Before
InterstitialAd.load(with: adUnitID, request: Request()) { ad, error in
ad?.fullScreenContentDelegate = self
self.interstitialAd = ad
}
// After
InterstitialAd.loadAndTrack(
withAdUnitID: adUnitID,
request: Request(),
placement: "level_complete",
fullScreenContentDelegate: self
) { ad, error in
self.interstitialAd = ad
}
// Before
InterstitialAd.load(context, adUnitId, adRequest, callback)
// After
Purchases.sharedInstance.adTracker.loadAndTrackInterstitialAd(
context = context,
adUnitId = adUnitId,
adRequest = adRequest,
placement = "level_complete",
loadCallback = callback
)
val adRequest = AdRequest.Builder(adUnitId).build()
// Before
InterstitialAd.load(adRequest, callback)
// After
Purchases.sharedInstance.adTracker.loadAndTrackInterstitialAd(
adRequest = adRequest,
placement = "level_complete",
loadCallback = callback,
)
// Before
InterstitialAd.load(
adUnitId: adUnitId,
request: const AdRequest(),
adLoadCallback: InterstitialAdLoadCallback(
onAdLoaded: (ad) => _interstitialAd = ad,
onAdFailedToLoad: (error) => _interstitialAd = null,
),
);
// After
RevenueCatAdMob.loadAndTrackInterstitialAd(
adUnitId: adUnitId,
request: const AdRequest(),
placement: 'level_complete',
adLoadCallback: InterstitialAdLoadCallback(
onAdLoaded: (ad) => _interstitialAd = ad,
onAdFailedToLoad: (error) => _interstitialAd = null,
),
);
// Before
const interstitial = InterstitialAd.createForAdRequest(adUnitId);
interstitial.addAdEventListener(AdEventType.LOADED, () => setReady(true));
interstitial.load();
await interstitial.show();
// After
const interstitial = RevenueCatAdMob.createInterstitialAd(adUnitId, {
placement: "level_complete",
});
interstitial.addAdEventListener(AdEventType.LOADED, () => setReady(true));
interstitial.load();
await RevenueCatAdMob.showAndTrack(interstitial);
// Before
InterstitialAd.Load(adUnitId, new AdRequest(), (ad, error) =>
{
if (ad == null) return;
_interstitial = ad;
});
_interstitial.Show();
// After
RevenueCatAdMob.LoadAndTrackInterstitialAd(adUnitId, new AdRequest(), (ad, error) =>
{
if (ad == null) return;
_interstitial = ad;
}, placement: "level_complete");
_interstitial.ShowAndTrack();
The placement identifies where the ad appears and drives reporting in Ad Charts. Use stable, descriptive names like "home_banner" or "level_complete_interstitial", never dynamic values.
Implementation examples
Banner ads
On Flutter the banner listener is given at construction time, so create the banner with createBannerAd or wrap your listener with trackingBannerAdListener. On React Native BannerAd is a component, so the adapter provides the useTrackingBannerAdProps hook; class components call RevenueCatAdMob.trackingBannerAdProps once per banner instance instead. On Unity attach tracking with TrackBannerView right after constructing the BannerView. With these adapters a banner refresh reports a new loaded event with the new response ID.
- Swift
- Kotlin (Legacy AdMob)
- Kotlin (Next-Gen AdMob)
- Flutter
- React Native
- Unity
import RevenueCatAdMob
let bannerView = BannerView(adSize: AdSize(size: CGSize(width: 320, height: 50), flags: 0))
bannerView.adUnitID = "ca-app-pub-3940256099942544/2435281174"
bannerView.loadAndTrack(
request: Request(),
placement: "home_banner"
)
@Composable
fun BannerAdView() {
AndroidView(
factory = { context ->
AdView(context).apply {
adUnitId = "ca-app-pub-3940256099942544/9214589741"
setAdSize(AdSize.BANNER)
// Load with tracking
Purchases.sharedInstance.adTracker.loadAndTrackBannerAd(
adView = this,
adRequest = AdRequest.Builder().build(),
placement = "home_banner"
)
}
}
)
}
@Composable
fun BannerAdView() {
AndroidView(
factory = { context ->
AdView(context).apply {
val adRequest = BannerAdRequest.Builder(
"ca-app-pub-3940256099942544/9214589741",
AdSize.BANNER,
).build()
loadAndTrackAd(
adRequest = adRequest,
placement = "home_banner",
)
}
},
)
}
// Banner ads take their listener at construction time, so create the banner
// through the adapter and call load() as usual.
final banner = RevenueCatAdMob.createBannerAd(
adUnitId: 'ca-app-pub-3940256099942544/2435281174',
size: AdSize.banner,
request: const AdRequest(),
placement: 'home_banner',
listener: BannerAdListener(
onAdLoaded: (ad) => setState(() => _bannerLoaded = true),
onAdFailedToLoad: (ad, error) => ad.dispose(),
),
)..load();
// Render it with AdWidget once loaded.
AdWidget(ad: banner);
// Alternatively, wrap your own listener when constructing a BannerAd yourself.
final listener = RevenueCatAdMob.trackingBannerAdListener(
placement: 'home_banner',
listener: BannerAdListener(onAdLoaded: (ad) => setState(() {})),
);
import { BannerAd, BannerAdSize, TestIds } from "react-native-google-mobile-ads";
import { useTrackingBannerAdProps } from "react-native-purchases-admob";
function HomeBanner() {
// useTrackingBannerAdProps returns BannerAd props that report to RevenueCat and
// forward to any callbacks you pass in (onAdLoaded, onPaid, ...).
const bannerProps = useTrackingBannerAdProps({
unitId: TestIds.BANNER,
size: BannerAdSize.BANNER,
placement: "home_banner",
onAdLoaded: () => setBannerLoaded(true),
});
return <BannerAd {...bannerProps} />;
}
// Attach tracking right after constructing the banner and before LoadAd.
// Refreshes report a new loaded event with the new response id.
var adUnitId = "ca-app-pub-3940256099942544/2435281174";
var banner = new BannerView(adUnitId, AdSize.Banner, AdPosition.Bottom);
RevenueCatAdMob.TrackBannerView(banner, adUnitId, placement: "home_banner");
banner.LoadAd(new AdRequest());
Interstitial ads
- Swift (async/await)
- Swift (completion handler)
- Kotlin (Legacy AdMob)
- Kotlin (Next-Gen AdMob)
- Flutter
- React Native
- Unity
import RevenueCatAdMob
class MyViewController: UIViewController, FullScreenContentDelegate {
private var interstitialAd: InterstitialAd?
func loadInterstitial() async {
do {
self.interstitialAd = try await InterstitialAd.loadAndTrack(
withAdUnitID: "ca-app-pub-3940256099942544/4411468910",
request: Request(),
placement: "level_complete",
fullScreenContentDelegate: self
)
} catch {
// Handle error
}
}
func showInterstitial() {
interstitialAd?.present(from: self)
}
}
import RevenueCatAdMob
class MyViewController: UIViewController, FullScreenContentDelegate {
private var interstitialAd: InterstitialAd?
func loadInterstitial() {
InterstitialAd.loadAndTrack(
withAdUnitID: "ca-app-pub-3940256099942544/4411468910",
request: Request(),
placement: "level_complete",
fullScreenContentDelegate: self
) { [weak self] ad, error in
if let error { return }
self?.interstitialAd = ad
}
}
func showInterstitial() {
interstitialAd?.present(from: self)
}
}
class MyActivity : AppCompatActivity() {
private var interstitialAd: InterstitialAd? = null
fun loadInterstitial() {
Purchases.sharedInstance.adTracker.loadAndTrackInterstitialAd(
context = this,
adUnitId = "ca-app-pub-3940256099942544/1033173712",
adRequest = AdRequest.Builder().build(),
placement = "level_complete",
loadCallback = object : InterstitialAdLoadCallback() {
override fun onAdLoaded(ad: InterstitialAd) {
interstitialAd = ad
}
override fun onAdFailedToLoad(error: LoadAdError) {
interstitialAd = null
}
}
)
}
fun showInterstitial() {
interstitialAd?.show(this)
}
}
class MyActivity : AppCompatActivity() {
private var interstitialAd: InterstitialAd? = null
fun loadInterstitial() {
val adRequest = AdRequest.Builder(
"ca-app-pub-3940256099942544/1033173712",
).build()
Purchases.sharedInstance.adTracker.loadAndTrackInterstitialAd(
adRequest = adRequest,
placement = "level_complete",
loadCallback = object : AdLoadCallback<InterstitialAd> {
override fun onAdLoaded(ad: InterstitialAd) {
interstitialAd = ad
}
override fun onAdFailedToLoad(error: LoadAdError) {
interstitialAd = null
}
},
)
}
fun showInterstitial() {
interstitialAd?.show(this)
}
}
InterstitialAd? _interstitialAd;
Future<void> loadInterstitialAd() => RevenueCatAdMob.loadAndTrackInterstitialAd(
adUnitId: 'ca-app-pub-3940256099942544/4411468910',
request: const AdRequest(),
placement: 'level_complete',
adLoadCallback: InterstitialAdLoadCallback(
onAdLoaded: (ad) {
// Setting the callback here is fine: the adapter wraps whatever you set.
ad.fullScreenContentCallback = FullScreenContentCallback(
onAdDismissedFullScreenContent: (ad) {
ad.dispose();
_interstitialAd = null;
},
);
_interstitialAd = ad;
},
onAdFailedToLoad: (error) => _interstitialAd = null,
),
);
Future<void> showInterstitialAd() async {
await _interstitialAd?.showAndTrack();
}
import { AdEventType, TestIds } from "react-native-google-mobile-ads";
import { RevenueCatAdMob } from "react-native-purchases-admob";
// The returned object is a regular InterstitialAd, so load(), listeners and
// destroy() work as before.
const interstitial = RevenueCatAdMob.createInterstitialAd(
TestIds.INTERSTITIAL,
{ placement: "level_complete" }
);
interstitial.addAdEventListener(AdEventType.LOADED, () => setReady(true));
interstitial.addAdEventListener(AdEventType.CLOSED, () => setReady(false));
interstitial.load();
// Later
await RevenueCatAdMob.showAndTrack(interstitial);
private InterstitialAd _interstitial;
void LoadInterstitialAd()
{
RevenueCatAdMob.LoadAndTrackInterstitialAd(
"ca-app-pub-3940256099942544/4411468910", new AdRequest(), (ad, error) =>
{
if (ad == null) return;
ad.OnAdFullScreenContentClosed += () => _interstitial = null;
_interstitial = ad;
}, placement: "level_complete");
}
void ShowInterstitialAd()
{
if (_interstitial != null && _interstitial.CanShowAd())
{
_interstitial.ShowAndTrack();
}
}
Rewarded ads
To verify the earned reward with RevenueCat and grant it server-side, see Granting Ad Rewards.
- Swift (async/await)
- Swift (completion handler)
- Kotlin (Legacy AdMob)
- Kotlin (Next-Gen AdMob)
- Flutter
- React Native
- Unity
import RevenueCatAdMob
func loadRewardedAd() async {
do {
self.rewardedAd = try await RewardedAd.loadAndTrack(
withAdUnitID: "ca-app-pub-3940256099942544/1712485313",
request: Request(),
placement: "bonus_coins",
fullScreenContentDelegate: self
)
} catch {
// Handle error
}
}
func showRewardedAd() {
rewardedAd?.present(from: self) {
// User earned reward
}
}
import RevenueCatAdMob
func loadRewardedAd() {
RewardedAd.loadAndTrack(
withAdUnitID: "ca-app-pub-3940256099942544/1712485313",
request: Request(),
placement: "bonus_coins",
fullScreenContentDelegate: self
) { [weak self] ad, error in
if let error { return }
self?.rewardedAd = ad
}
}
func showRewardedAd() {
rewardedAd?.present(from: self) {
// User earned reward
}
}
fun loadRewardedAd(context: Context) {
Purchases.sharedInstance.adTracker.loadAndTrackRewardedAd(
context = context,
adUnitId = "ca-app-pub-3940256099942544/5224354917",
adRequest = AdRequest.Builder().build(),
placement = "bonus_coins",
loadCallback = object : RewardedAdLoadCallback() {
override fun onAdLoaded(ad: RewardedAd) {
// Ad loaded successfully
ad.show(activity) { reward ->
// User earned reward
grantCoins(reward.amount)
}
}
override fun onAdFailedToLoad(error: LoadAdError) {
// Handle error
}
}
)
}
fun loadRewardedAd() {
val adRequest = AdRequest.Builder(
"ca-app-pub-3940256099942544/5224354917",
).build()
Purchases.sharedInstance.adTracker.loadAndTrackRewardedAd(
adRequest = adRequest,
placement = "bonus_coins",
loadCallback = object : AdLoadCallback<RewardedAd> {
override fun onAdLoaded(ad: RewardedAd) {
ad.show(activity) { reward ->
grantCoins(reward.amount)
}
}
override fun onAdFailedToLoad(error: LoadAdError) {
// Handle the error.
}
},
)
}
RewardedAd? _rewardedAd;
Future<void> loadRewardedAd() => RevenueCatAdMob.loadAndTrackRewardedAd(
adUnitId: 'ca-app-pub-3940256099942544/1712485313',
request: const AdRequest(),
placement: 'bonus_coins',
rewardedAdLoadCallback: RewardedAdLoadCallback(
onAdLoaded: (ad) => _rewardedAd = ad,
onAdFailedToLoad: (error) => _rewardedAd = null,
),
);
Future<void> showRewardedAd() async {
await _rewardedAd?.showAndTrack(
onUserEarnedReward: (ad, reward) {
// User earned reward
},
);
}
import { RewardedAdEventType, TestIds } from "react-native-google-mobile-ads";
import { RevenueCatAdMob } from "react-native-purchases-admob";
// createRewardedAd is async so it can prepare reward verification when enabled.
const rewarded = await RevenueCatAdMob.createRewardedAd(
TestIds.REWARDED,
{ placement: "bonus_coins" }
);
rewarded.addAdEventListener(RewardedAdEventType.LOADED, () => setReady(true));
rewarded.addAdEventListener(RewardedAdEventType.EARNED_REWARD, (reward) => {
// User earned reward
});
rewarded.load();
// Later
await RevenueCatAdMob.showAndTrack(rewarded);
private RewardedAd _rewarded;
void LoadRewardedAd()
{
RevenueCatAdMob.LoadAndTrackRewardedAd(
"ca-app-pub-3940256099942544/1712485313", new AdRequest(), (ad, error) =>
{
if (ad == null) return;
_rewarded = ad;
}, placement: "bonus_coins");
}
void ShowRewardedAd()
{
if (_rewarded != null && _rewarded.CanShowAd())
{
_rewarded.ShowAndTrack(reward =>
{
// User earned reward
});
}
}
Rewarded interstitial ads
- Swift (async/await)
- Swift (completion handler)
- Kotlin (Legacy AdMob)
- Kotlin (Next-Gen AdMob)
- Flutter
- React Native
- Unity
import RevenueCatAdMob
func loadRewardedInterstitialAd() async {
do {
self.rewardedInterstitialAd = try await RewardedInterstitialAd.loadAndTrack(
withAdUnitID: "ca-app-pub-3940256099942544/6978759866",
request: Request(),
placement: "between_levels",
fullScreenContentDelegate: self
)
} catch {
// Handle error
}
}
func showRewardedInterstitialAd() {
rewardedInterstitialAd?.present(from: self) {
// User earned reward
}
}
import RevenueCatAdMob
func loadRewardedInterstitialAd() {
RewardedInterstitialAd.loadAndTrack(
withAdUnitID: "ca-app-pub-3940256099942544/6978759866",
request: Request(),
placement: "between_levels",
fullScreenContentDelegate: self
) { [weak self] ad, error in
if let error { return }
self?.rewardedInterstitialAd = ad
}
}
func showRewardedInterstitialAd() {
rewardedInterstitialAd?.present(from: self) {
// User earned reward
}
}
fun loadRewardedInterstitialAd(context: Context) {
Purchases.sharedInstance.adTracker.loadAndTrackRewardedInterstitialAd(
context = context,
adUnitId = "ca-app-pub-3940256099942544/5354046379",
adRequest = AdRequest.Builder().build(),
placement = "between_levels",
loadCallback = object : RewardedInterstitialAdLoadCallback() {
override fun onAdLoaded(ad: RewardedInterstitialAd) {
ad.show(activity) { reward ->
// User earned reward
}
}
override fun onAdFailedToLoad(error: LoadAdError) {
// Handle error
}
}
)
}
fun loadRewardedInterstitialAd() {
val adRequest = AdRequest.Builder(
"ca-app-pub-3940256099942544/5354046379",
).build()
Purchases.sharedInstance.adTracker.loadAndTrackRewardedInterstitialAd(
adRequest = adRequest,
placement = "between_levels",
loadCallback = object : AdLoadCallback<RewardedInterstitialAd> {
override fun onAdLoaded(ad: RewardedInterstitialAd) {
ad.show(activity) { reward ->
grantCoins(reward.amount)
}
}
override fun onAdFailedToLoad(error: LoadAdError) {
// Handle the error.
}
},
)
}
RewardedInterstitialAd? _rewardedInterstitialAd;
Future<void> loadRewardedInterstitialAd() =>
RevenueCatAdMob.loadAndTrackRewardedInterstitialAd(
adUnitId: 'ca-app-pub-3940256099942544/6978759866',
request: const AdRequest(),
placement: 'bonus_content',
rewardedInterstitialAdLoadCallback: RewardedInterstitialAdLoadCallback(
onAdLoaded: (ad) => _rewardedInterstitialAd = ad,
onAdFailedToLoad: (error) => _rewardedInterstitialAd = null,
),
);
Future<void> showRewardedInterstitialAd() async {
await _rewardedInterstitialAd?.showAndTrack(
onUserEarnedReward: (ad, reward) {
// User earned reward
},
);
}
import { RewardedAdEventType, TestIds } from "react-native-google-mobile-ads";
import { RevenueCatAdMob } from "react-native-purchases-admob";
const rewardedInterstitial = await RevenueCatAdMob.createRewardedInterstitialAd(
TestIds.REWARDED_INTERSTITIAL,
{ placement: "bonus_content" }
);
rewardedInterstitial.addAdEventListener(RewardedAdEventType.LOADED, () =>
setReady(true)
);
rewardedInterstitial.addAdEventListener(
RewardedAdEventType.EARNED_REWARD,
(reward) => {
// User earned reward
}
);
rewardedInterstitial.load();
// Later
await RevenueCatAdMob.showAndTrack(rewardedInterstitial);
private RewardedInterstitialAd _rewardedInterstitial;
void LoadRewardedInterstitialAd()
{
RevenueCatAdMob.LoadAndTrackRewardedInterstitialAd(
"ca-app-pub-3940256099942544/6978759866", new AdRequest(), (ad, error) =>
{
if (ad == null) return;
_rewardedInterstitial = ad;
}, placement: "bonus_content");
}
void ShowRewardedInterstitialAd()
{
if (_rewardedInterstitial != null && _rewardedInterstitial.CanShowAd())
{
_rewardedInterstitial.ShowAndTrack(reward =>
{
// User earned reward
});
}
}
App open ads
- Swift (async/await)
- Swift (completion handler)
- Kotlin (Legacy AdMob)
- Kotlin (Next-Gen AdMob)
- Flutter
- React Native
- Unity
import RevenueCatAdMob
func loadAppOpenAd() async {
do {
self.appOpenAd = try await AppOpenAd.loadAndTrack(
withAdUnitID: "ca-app-pub-3940256099942544/5575463023",
request: Request(),
placement: "app_launch",
fullScreenContentDelegate: self
)
} catch {
// Handle error
}
}
import RevenueCatAdMob
func loadAppOpenAd() {
AppOpenAd.loadAndTrack(
withAdUnitID: "ca-app-pub-3940256099942544/5575463023",
request: Request(),
placement: "app_launch",
fullScreenContentDelegate: self
) { [weak self] ad, error in
if let error { return }
self?.appOpenAd = ad
}
}
fun loadAppOpenAd(context: Context) {
Purchases.sharedInstance.adTracker.loadAndTrackAppOpenAd(
context = context,
adUnitId = "ca-app-pub-3940256099942544/9257395921",
adRequest = AdRequest.Builder().build(),
placement = "app_launch",
loadCallback = object : AppOpenAd.AppOpenAdLoadCallback() {
override fun onAdLoaded(ad: AppOpenAd) {
// Ad loaded successfully
}
override fun onAdFailedToLoad(error: LoadAdError) {
// Handle error
}
}
)
}
fun loadAppOpenAd() {
val adRequest = AdRequest.Builder(
"ca-app-pub-3940256099942544/9257395921",
).build()
Purchases.sharedInstance.adTracker.loadAndTrackAppOpenAd(
adRequest = adRequest,
placement = "app_launch",
loadCallback = object : AdLoadCallback<AppOpenAd> {
override fun onAdLoaded(ad: AppOpenAd) {
appOpenAd = ad
}
override fun onAdFailedToLoad(error: LoadAdError) {
appOpenAd = null
}
},
)
}
AppOpenAd? _appOpenAd;
Future<void> loadAppOpenAd() => RevenueCatAdMob.loadAndTrackAppOpenAd(
adUnitId: 'ca-app-pub-3940256099942544/5575463023',
request: const AdRequest(),
placement: 'app_launch',
adLoadCallback: AppOpenAdLoadCallback(
onAdLoaded: (ad) => _appOpenAd = ad,
onAdFailedToLoad: (error) => _appOpenAd = null,
),
);
Future<void> showAppOpenAd() async {
await _appOpenAd?.showAndTrack();
}
import { AdEventType, TestIds } from "react-native-google-mobile-ads";
import { RevenueCatAdMob } from "react-native-purchases-admob";
const appOpen = RevenueCatAdMob.createAppOpenAd(
TestIds.APP_OPEN,
{ placement: "app_launch" }
);
appOpen.addAdEventListener(AdEventType.LOADED, () => setReady(true));
appOpen.load();
// Later
await RevenueCatAdMob.showAndTrack(appOpen);
private AppOpenAd _appOpen;
void LoadAppOpenAd()
{
RevenueCatAdMob.LoadAndTrackAppOpenAd(
"ca-app-pub-3940256099942544/5575463023", new AdRequest(), (ad, error) =>
{
if (ad == null) return;
_appOpen = ad;
}, placement: "app_launch");
}
void ShowAppOpenAd()
{
if (_appOpen != null && _appOpen.CanShowAd())
{
_appOpen.ShowAndTrack();
}
}
Native ads
On Flutter wrap the native ad listener with trackingNativeAdListener and render the loaded ad in an AdWidget with a bounded size; displayed, opened and revenue are reported once it is on screen. On React Native call RevenueCatAdMob.trackNativeAd once NativeAd.createForAdRequest resolves; the loaded event is reported immediately and the rest once the ad is rendered in NativeAdView. On Unity call TrackNativeOverlayAd on the loaded NativeOverlayAd.
- Swift
- Kotlin (Legacy AdMob)
- Kotlin (Next-Gen AdMob)
- Flutter
- React Native
- Unity
import RevenueCatAdMob
let adLoader = AdLoader(
adUnitID: "ca-app-pub-3940256099942544/3986624511",
rootViewController: self,
adTypes: [.native],
options: nil
)
adLoader.delegate = self
adLoader.loadAndTrack(
Request(),
placement: "feed",
nativeAdDelegate: self
)
val adLoader = AdLoader.Builder(context, "ca-app-pub-3940256099942544/2247696110")
// Use forNativeAdWithTracking in place of the standard forNativeAd
.forNativeAdWithTracking(
adUnitId = "ca-app-pub-3940256099942544/2247696110",
placement = "feed",
) { nativeAd ->
// Use the loaded native ad
}
.build()
// Load as usual with the standard AdLoader.loadAd() call
adLoader.loadAd(AdRequest.Builder().build())
val adRequest = NativeAdRequest.Builder(
"ca-app-pub-3940256099942544/2247696110",
listOf(NativeAd.NativeAdType.NATIVE),
).build()
Purchases.sharedInstance.adTracker.loadAndTrackNativeAd(
adRequest = adRequest,
placement = "feed",
loadCallback = object : NativeAdLoaderCallback {
override fun onNativeAdLoaded(nativeAd: NativeAd) {
loadedNativeAd = nativeAd
}
override fun onAdFailedToLoad(error: LoadAdError) {
loadedNativeAd = null
}
},
)
// Native ads take their listener at construction time, so wrap it.
final nativeAd = NativeAd(
adUnitId: 'ca-app-pub-3940256099942544/3986624511',
request: const AdRequest(),
nativeTemplateStyle: NativeTemplateStyle(templateType: TemplateType.medium),
listener: RevenueCatAdMob.trackingNativeAdListener(
placement: 'feed',
listener: NativeAdListener(
onAdLoaded: (ad) => setState(() => _nativeLoaded = true),
onAdFailedToLoad: (ad, error) => ad.dispose(),
),
),
)..load();
// Render it once loaded. AdWidget needs a bounded size for native ads; displayed,
// opened and revenue are reported once the ad is on screen.
SizedBox(height: 320, child: AdWidget(ad: nativeAd));
import { NativeAd, TestIds } from "react-native-google-mobile-ads";
import { RevenueCatAdMob } from "react-native-purchases-admob";
// NativeAd.createForAdRequest resolves with a loaded ad, so the adapter reports
// the loaded event as soon as you attach tracking.
const nativeAd = await NativeAd.createForAdRequest(
TestIds.NATIVE
);
RevenueCatAdMob.trackNativeAd(nativeAd, { placement: "feed" });
// Render it in <NativeAdView nativeAd={nativeAd}> with NativeAsset / NativeMediaView
// children as usual; displayed, opened and revenue are reported once it is on screen.
// Attach tracking to the loaded native overlay ad.
var adUnitId = "ca-app-pub-3940256099942544/3986624511";
NativeOverlayAd.Load(adUnitId, new AdRequest(), new NativeAdOptions(), (ad, error) =>
{
if (ad == null) return;
RevenueCatAdMob.TrackNativeOverlayAd(ad, adUnitId, placement: "feed");
_nativeOverlay = ad;
});
Show-time placement
For full-screen ads you can set the placement when showing instead of when loading, which helps when you preload an ad before knowing where it will appear. The show-time placement applies to Ad Displayed, Ad Opened, and Ad Revenue events; Ad Loaded and Ad Failed to Load keep the load-time placement.
- Swift
- Kotlin (Legacy AdMob)
- Kotlin (Next-Gen AdMob)
- Flutter
- React Native
- Unity
// Load once, without a specific placement
InterstitialAd.loadAndTrack(
withAdUnitID: adUnitID,
request: Request(),
fullScreenContentDelegate: self
) { [weak self] ad, error in
self?.cachedAd = ad
}
// Show with placement depending on where the user is
cachedAd?.present(from: self, placement: "level_complete")
// Load once, without a specific placement
Purchases.sharedInstance.adTracker.loadAndTrackInterstitialAd(
context = context,
adUnitId = adUnitId,
adRequest = AdRequest.Builder().build(),
loadCallback = object : InterstitialAdLoadCallback() {
override fun onAdLoaded(ad: InterstitialAd) {
cachedAd = ad
}
}
)
// Show with placement depending on where the user is
cachedAd?.show(activity, "level_complete")
Purchases.sharedInstance.adTracker.loadAndTrackInterstitialAd(
adRequest = AdRequest.Builder(adUnitId).build(),
loadCallback = object : AdLoadCallback<InterstitialAd> {
override fun onAdLoaded(ad: InterstitialAd) {
cachedAd = ad
}
},
)
// Later, override the placement based on where the ad is shown.
cachedAd?.show(activity, placement = "level_complete")
// Load once, without a specific placement
await RevenueCatAdMob.loadAndTrackInterstitialAd(
adUnitId: adUnitId,
request: const AdRequest(),
adLoadCallback: InterstitialAdLoadCallback(
onAdLoaded: (ad) => _cachedAd = ad,
onAdFailedToLoad: (error) => _cachedAd = null,
),
);
// Show with placement depending on where the user is
await _cachedAd?.showAndTrack(placement: 'level_complete');
// Create and load once, without a specific placement
const cachedAd = RevenueCatAdMob.createInterstitialAd(adUnitId);
cachedAd.load();
// Show with placement depending on where the user is
await RevenueCatAdMob.showAndTrack(cachedAd, { placement: "level_complete" });
// Load once, without a specific placement
RevenueCatAdMob.LoadAndTrackInterstitialAd(adUnitId, new AdRequest(), (ad, error) =>
{
_cachedAd = ad;
});
// Show with placement depending on where the user is
_cachedAd?.ShowAndTrack("level_complete");
Your own callbacks
On iOS and Android the lifecycle callback parameter of every loadAndTrack method is optional; the adapter wraps the callback you pass and forwards every event to it after tracking. If you set or replace the callback after loading, use the tracking-aware setter: setTrackingFullScreenContentDelegate on iOS, setTrackingFullScreenContentCallback on legacy Android, setTrackingAdEventCallback on Android Next-Gen (plus setTrackingBannerAdRefreshCallback for banner refreshes). On Flutter, set fullScreenContentCallback and onPaidEvent inside onAdLoaded as usual and the adapter wraps them; to assign the callback later, use setTrackingFullScreenContentCallback. On React Native listeners are additive, and RevenueCatAdMob.trackAd attaches tracking to an ad you created yourself, before load(). On Unity the Google events are multicast, so your own += handlers keep working next to the adapter's.
- Swift
- Kotlin (Legacy AdMob)
- Kotlin (Next-Gen AdMob)
- Flutter
- React Native
- Unity
// Pass the full-screen content delegate when loading the ad.
InterstitialAd.loadAndTrack(
withAdUnitID: adUnitID,
request: Request(),
placement: "level_complete",
fullScreenContentDelegate: self
) { ad, error in }
// Or replace it later with the tracking-safe setter.
ad?.setTrackingFullScreenContentDelegate(myDelegate)
// Don't assign it directly. This replaces RevenueCat's tracking wrapper.
ad?.fullScreenContentDelegate = myDelegate
import com.revenuecat.purchases.admob.setTrackingFullScreenContentCallback
// Pass the full-screen content callback when loading the ad.
Purchases.sharedInstance.adTracker.loadAndTrackInterstitialAd(
context = context,
adUnitId = adUnitId,
adRequest = adRequest,
placement = "level_complete",
fullScreenContentCallback = myCallback
)
// Or replace it later with the tracking-safe setter.
ad.setTrackingFullScreenContentCallback(myCallback)
// Don't assign it directly. This replaces RevenueCat's tracking wrapper.
ad.fullScreenContentCallback = myCallback
import com.revenuecat.purchases.admob.nextgen.setTrackingAdEventCallback
// Pass the event callback when loading the ad.
Purchases.sharedInstance.adTracker.loadAndTrackInterstitialAd(
adRequest = adRequest,
placement = "level_complete",
loadCallback = loadCallback,
adEventCallback = myCallback,
)
// Or replace it later with the tracking-safe setter.
ad.setTrackingAdEventCallback(myCallback)
// Don't assign it directly. This replaces RevenueCat's tracking wrapper.
ad.adEventCallback = myCallback
// Setting the callback inside onAdLoaded is fine: the adapter wraps it.
RevenueCatAdMob.loadAndTrackInterstitialAd(
adUnitId: adUnitId,
request: const AdRequest(),
placement: 'level_complete',
adLoadCallback: InterstitialAdLoadCallback(
onAdLoaded: (ad) {
ad.fullScreenContentCallback = myCallback;
_interstitialAd = ad;
},
onAdFailedToLoad: (error) => _interstitialAd = null,
),
);
// To replace it later, use the tracking-safe setter.
_interstitialAd?.setTrackingFullScreenContentCallback(myCallback);
// Don't assign it directly after loading. This replaces RevenueCat's tracking wrapper.
_interstitialAd?.fullScreenContentCallback = myCallback;
// For a full-screen ad you created yourself, attach tracking before load().
const interstitial = InterstitialAd.createForAdRequest(adUnitId, {
keywords: ["game"],
});
RevenueCatAdMob.trackAd(interstitial, { placement: "level_complete" });
interstitial.load();
// Request options can also be passed through the adapter's create methods.
const tracked = RevenueCatAdMob.createInterstitialAd(adUnitId, {
placement: "level_complete",
requestOptions: { keywords: ["game"] },
});
// Google's ad events are multicast: add your own handlers with += and the
// adapter's tracking handlers keep running next to them.
RevenueCatAdMob.LoadAndTrackInterstitialAd(
"ca-app-pub-3940256099942544/4411468910", new AdRequest(), (ad, error) =>
{
if (ad == null) return;
ad.OnAdImpressionRecorded += () => Debug.Log("Interstitial shown");
ad.OnAdFullScreenContentClosed += () => _interstitial = null;
ad.OnAdPaid += adValue => Debug.Log($"Paid {adValue.Value} {adValue.CurrencyCode}");
_interstitial = ad;
}, placement: "level_complete");
Reward verification
Rewarded and rewarded interstitial ads can have their rewards verified by RevenueCat before you grant them: enable verification on the ad (an option when creating it on React Native) and show it with showWithRewardVerification. The dashboard setup and the code for every platform are in Granting Ad Rewards.
Android Next-Gen features
The Android Next-Gen adapter supports additional Google Mobile Ads Next-Gen loading workflows. These APIs are only available in the Kotlin (Next-Gen AdMob) integration.
Suspending loads
Interstitial, rewarded, rewarded interstitial, app open, and native helpers have suspending overloads. They return Google's original result with RevenueCat tracking installed on successful ads:
- Kotlin (Next-Gen AdMob)
lifecycleScope.launch {
when (
val result = Purchases.sharedInstance.adTracker.loadAndTrackInterstitialAd(
adRequest = AdRequest.Builder("AD_UNIT_ID").build(),
placement = "game_interstitial",
)
) {
is AdLoadResult.Success -> interstitialAd = result.ad
is AdLoadResult.Failure -> interstitialAd = null
}
}
Direct banner loading remains callback-based because Google doesn't provide a supported suspending AdView.loadAd API.
Preloading
All six supported formats work with Google's preloaders. Track preload success or failure with startAndTrack, then install lifecycle tracking on a buffered ad with pollAndTrackAd:
- Kotlin (Next-Gen AdMob)
val preloadConfiguration = PreloadConfiguration(
request = AdRequest.Builder("AD_UNIT_ID").build(),
bufferSize = 2,
)
InterstitialAdPreloader.startAndTrack(
preloadId = "game-interstitial-buffer",
preloadConfiguration = preloadConfiguration,
placement = "game_interstitial_preload",
preloadCallback = object : PreloadCallback {},
)
// Later, adopt a buffered ad. Polling doesn't emit another loaded event.
interstitialAd = InterstitialAdPreloader.pollAndTrackAd(
preloadId = "game-interstitial-buffer",
placement = "game_interstitial",
adEventCallback = interstitialAdEventCallback,
)
interstitialAd?.show(this)
The placements supplied to startAndTrack and pollAndTrackAd are independent. Polling doesn't emit another loaded event. Use the corresponding preloader extensions for banner, app open, native, rewarded, and rewarded interstitial ads.
Other loading workflows
The Next-Gen adapter also supports server-to-server ad responses for every format and single or batched native-ad loading. See the Android Next-Gen advanced loading guide for complete examples.
Migrate from the legacy Android adapter
The Next-Gen adapter isn't a drop-in binary replacement for the legacy adapter. Migrate the Google Mobile Ads SDK and RevenueCat adapter together:
- Replace the legacy dependencies with
purchases-admob-next-genandads-mobile-sdk. - Change RevenueCat imports from
com.revenuecat.purchases.admobtocom.revenuecat.purchases.admob.nextgen. - Update Google imports from
com.google.android.gms.adstocom.google.android.libraries.ads.mobile.sdk. - Update requests and callbacks to the Next-Gen types shown in this guide.
- Keep existing placement values when they identify the same ad slots so reporting remains consistent.
Next-Gen requests own the ad unit ID. BannerAdRequest also owns the banner size that the legacy SDK stored on AdView. Next-Gen callbacks may run on a background thread, so dispatch to the main thread before updating UI-confined state.
Review Google's Next-Gen migration guide for SDK and mediation changes.
Platform notes
- iOS availability: Ad tracking requires iOS 15.0+. On older versions calls are ignored and a warning is logged, including through the Flutter, React Native and Unity adapters.
- React Native: Requires
react-native-google-mobile-ads17.x and the New Architecture (17.0.0 needs React Native 0.76+, 17.1.0+ needs 0.86+). The library'suseInterstitialAd/useRewardedAdhooks, ad pools and multi-format requests create their own ads and are not tracked; use the class API shown above. Web is not supported. - Flutter: Requires
google_mobile_ads8.x; 9.x isn't supported yet. - Unity: Requires the Google Mobile Ads Unity plugin 10.x or newer, installed from OpenUPM or the
.unitypackage.Purchasescreates its native wrapper in its ownStart(), so with runtime setup callConfigureonly after that has run;RevenueCatAdMob.Initialize(purchases)just stores the reference and can be called earlier. The adapter tracks from whichever thread Google raises its callbacks on; enableMobileAds.RaiseAdEventsOnUnityMainThreadonly if your own callbacks touch Unity objects.
Troubleshooting
Testing your integration
- Run your app in debug mode
- Open the Ads page in your RevenueCat dashboard and toggle on "Sandbox data"
- Trigger some ads in your app and check that events appear in the sandbox events table
Events from debug builds are marked as sandbox, so you can verify the integration without affecting production data. Events are batched and sent periodically, not in real time. On iOS, backgrounding and foregrounding the app triggers a sync; Android flushes shortly after each event.
Events not appearing in charts
- Verify that "Impression-level ad revenue" is enabled in your AdMob account (instructions)
- Verify you've opted in via the Ads page in your RevenueCat dashboard
- Check that ads are loaded through the adapter and shown with its show methods, not the plain Google Mobile Ads calls
- Ensure a
placementis provided andPurchasesis configured before ads load - Use the sandbox data view (above) to confirm events are being received
Next steps
- Explore the example projects: iOS, legacy Android, Android Next-Gen, Flutter, React Native, Unity
- Grant ad rewards when users complete rewarded ads, verified server-side
- View your ad data in Ad Charts, and see the Ad Monetization overview for where it appears across the dashboard
- Connect your Google AdMob account to sync ad unit names for more readable charts — no code changes required
- Learn about Manual Integration for other mediation platforms