applovin_admob_sdk 1.0.5
applovin_admob_sdk: ^1.0.5 copied to clipboard
Dual-provider ad SDK for Flutter (AdMob + AppLovin MAX) with built-in safety throttle, VIP bypass, RouteAware banner lifecycle management, and animated TopToast feedback.
applovin_admob_sdk #
Dual-provider Flutter Ad SDK β switch between AdMob and AppLovin MAX with a single line of config.
Built-in anti-fraud safety layer, route-aware banner lifecycle, animated top-center toast, and VIP device bypass.
π― Features #
| Feature | Description |
|---|---|
| Dual Provider | Switch AdMob β AppLovin with AdProvider.admob / AdProvider.appLovin |
| Ad Types | App Open, Banner, Interstitial, Rewarded |
| Safety Layer | Throttle, session/hourly/daily caps, CTR fraud detection, progressive cooldown |
| RouteAware Banner | Banner automatically pauses/resumes on navigation β no manual code needed |
| VIP Bypass | Specific devices (by GAID) never see ads β perfect for owners/testers |
| Shimmer Placeholder | Beautiful skeleton loading state while banner fills |
| TopToast | Animated glassmorphism toast at top-center β auto-shown when rewarded ad unavailable |
| Localizable Strings | adNotReadyMessage + adLoadingMessage configurable β no hardcoded strings |
π Table of Contents #
- Prerequisites
- Installation
- Android Setup
- iOS Setup
- AdMob Setup
- AppLovin Setup
- Integration Guide (Step-by-Step)
- Ad Types Reference
- AdConfig Reference
- Safety Layer
- VIP Bypass
- TopToast
- Troubleshooting
π§ Prerequisites #
- Flutter β₯ 3.10.0
- Dart β₯ 3.0.0
- An AdMob account at admob.google.com and/or
an AppLovin account at applovin.com - Android minSdk β₯ 21
- iOS deployment target β₯ 12.0
π¦ Installation #
Add to your pubspec.yaml:
dependencies:
applovin_admob_sdk: ^1.0.4
Then run:
flutter pub get
If using AppLovin MAX, you also need the mediation plugin at the app level
(cannot be declared inside a sub-package):dependencies: gma_mediation_applovin: ^1.0.0 # required for AppLovin + AdMob mediation
π€ Android Setup #
1. AndroidManifest.xml #
Open android/app/src/main/AndroidManifest.xml and add inside <application>:
<!-- β
Required for AdMob β get your App ID from admob.google.com -->
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-xxxxxxxxxxxxxxxx~xxxxxxxxxx"/>
<!-- β
Required for AppLovin β get your SDK Key from applovin.com/account -->
<meta-data
android:name="applovin.sdk.key"
android:value="YOUR_86_CHARACTER_SDK_KEY"/>
Also add <uses-permission> before <application>:
<!-- β
Required: GAID permission for VIP bypass + test device detection -->
<uses-permission android:name="com.google.android.gms.permission.AD_ID"/>
2. build.gradle minSdk #
Open android/app/build.gradle and ensure:
android {
defaultConfig {
minSdk 21 // must be β₯ 21 for Google Mobile Ads
}
}
π iOS Setup #
1. Info.plist #
Open ios/Runner/Info.plist and add:
<!-- β
Required for AdMob -->
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-xxxxxxxxxxxxxxxx~xxxxxxxxxx</string>
<!-- β
Required for iOS 14+ tracking -->
<key>NSUserTrackingUsageDescription</key>
<string>This identifier will be used to deliver personalized ads to you.</string>
<!-- β
Required for AppLovin -->
<key>AppLovinSdkKey</key>
<string>YOUR_86_CHARACTER_SDK_KEY</string>
2. Podfile #
Open ios/Podfile and ensure:
platform :ios, '12.0' # must be β₯ 12.0
Then run:
cd ios && pod install
π± AdMob Setup #
- Go to admob.google.com β Apps β Add App
- Create ad units for each type:
- Banner:
ca-app-pub-xxxxxxxx/xxxxxxxxxx - Interstitial:
ca-app-pub-xxxxxxxx/xxxxxxxxxx - App Open:
ca-app-pub-xxxxxxxx/xxxxxxxxxx - Rewarded:
ca-app-pub-xxxxxxxx/xxxxxxxxxx
- Banner:
Test IDs (use these during development, never in production):
App ID: ca-app-pub-3940256099942544~3347511713 Banner: ca-app-pub-3940256099942544/6300978111 Interstitial: ca-app-pub-3940256099942544/1033173712 App Open: ca-app-pub-3940256099942544/9257395921 Rewarded: ca-app-pub-3940256099942544/5224354917
π AppLovin Setup #
- Go to dash.applovin.com β Account β copy SDK Key (86 chars)
- Go to MAX β Mediation β Ad Units β New Ad Unit for each type:
- Banner, Interstitial, App Open, Rewarded (each is a 16-character ID)
β οΈ Important: AppLovin has NO universal test IDs unlike AdMob.
You must use real ad unit IDs from your dashboard.
To see test ads, register your device as a test device using your GAID
(the SDK does this automatically in debug mode β see logs for GAID).
π Integration Guide (Step-by-Step) #
This guide walks you through integrating the SDK from scratch.
Follow every step in order.
Step 1 β Add the package #
pubspec.yaml:
dependencies:
applovin_admob_sdk: ^1.0.4
gma_mediation_applovin: ^1.0.0 # only if using AppLovin
flutter pub get
Step 2 β Add navigatorObservers to your MaterialApp #
The SDK needs to observe navigation to manage the banner lifecycle (pause/resume automatically).
import 'package:applovin_admob_sdk/applovin_admob_sdk.dart';
MaterialApp(
// β
REQUIRED β add both observers
navigatorObservers: [
adRouteObserver, // manages RouteAware banner
AdScreenRouteLogger(), // logs route changes for debugging
],
home: const SplashScreen(),
// ...
)
Why? Without
adRouteObserver, the banner will not pause when another screen pushes on top, causing double-billing.
Step 3 β Create your SplashScreen #
The SDK must be initialized in SplashScreen, not in main(). This ensures the EventBus notifies the listener only after it is registered.
import 'dart:async';
import 'package:applovin_admob_sdk/applovin_admob_sdk.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class SplashScreen extends StatefulWidget {
const SplashScreen({super.key});
@override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
bool _hasNavigated = false;
void Function(BoolEvent)? _eventListener;
Timer? _hardCapTimer;
@override
void initState() {
super.initState();
AdManager().markSplashActive(); // tell SDK splash is shown
AdManager().incrementSplashCount(); // track how many times splash showed
// If this is not the first time (e.g. app re-opened), skip ads
if (AdManager().countInitSplashScreen > 1) {
WidgetsBinding.instance.addPostFrameCallback((_) => _navigateHome());
return;
}
// Hard cap: force navigate after 8 seconds even if ad didn't load
_hardCapTimer = Timer(const Duration(seconds: 8), () {
SafeLogger.d('Splash', 'β° Hard cap β force navigate');
_navigateHome();
});
// β
Register EventBus listener BEFORE calling initialize()
_eventListener = (event) {
if (event.value) {
_loadAndShowAppOpenAd();
} else {
_navigateHome(); // init failed
}
};
SimpleEventBus().listen(_eventListener!);
// β
Initialize AdManager inside postFrameCallback
WidgetsBinding.instance.addPostFrameCallback((_) {
AdManager().initialize(
config: AdConfig(
// β change this to AdProvider.admob for AdMob
provider: AdProvider.appLovin,
admob: AdMobConfig(
bannerId: 'ca-app-pub-3940256099942544/6300978111',
interstitialId: 'ca-app-pub-3940256099942544/1033173712',
appOpenId: 'ca-app-pub-3940256099942544/9257395921',
rewardedId: 'ca-app-pub-3940256099942544/5224354917',
),
appLovin: AppLovinConfig(
sdkKey: 'YOUR_86_CHAR_SDK_KEY',
bannerId: 'YOUR_BANNER_ID',
interstitialId: 'YOUR_INTER_ID',
appOpenId: 'YOUR_APP_OPEN_ID',
rewardedId: 'YOUR_REWARDED_ID',
),
vipDeviceGaids: [], // add your GAID here to skip ads on your device
loadingBufferMs: 1000, // ms to wait after ad loads before showing
adNotReadyMessage: 'Ad not ready β please try again later.',
adLoadingMessage: 'Loadingβ¦',
),
onComplete: (success, gaid) {
SafeLogger.d('Splash', 'SDK ready. GAID: $gaid');
},
);
});
}
void _loadAndShowAppOpenAd() {
AdManager().loadAppOpenAd(onAdLoaded: (loaded) {
if (_hasNavigated) return;
if (loaded) {
if (!mounted) { _navigateHome(); return; }
AdLoadingDialog.showAdBuffer(context, onComplete: () {
if (!mounted) { _navigateHome(); return; }
// β
Cancel hard cap timer BEFORE showing ad
_hardCapTimer?.cancel();
_hardCapTimer = null;
AdManager().showAppOpenAd(
bypassSafety: true, // always bypass on splash
onAdDismiss: (_) => _navigateHome(),
);
});
} else {
_navigateHome();
}
});
}
void _navigateHome() {
if (_hasNavigated) return;
_hasNavigated = true;
_hardCapTimer?.cancel();
_hardCapTimer = null;
if (_eventListener != null) {
SimpleEventBus().remove(_eventListener!);
_eventListener = null;
}
AdManager().markSplashInactive(); // β
always call this exactly once
if (!mounted) return;
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const HomeScreen()),
);
}
@override
void dispose() {
_hardCapTimer?.cancel();
if (_eventListener != null) {
SimpleEventBus().remove(_eventListener!);
_eventListener = null;
}
super.dispose();
}
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
}
Step 4 β Extend AdScreen on screens that show ads #
Instead of StatefulWidget, extend AdScreen. This gives you buildBanner(), showInterstitialAd(), and showRewardedAd() for free.
import 'package:applovin_admob_sdk/applovin_admob_sdk.dart';
import 'package:flutter/material.dart';
class HomeScreen extends AdScreen { // β extend AdScreen
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends AdScreenState<HomeScreen> { // β extend AdScreenState
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
buildBanner(), // β add banner (top or bottom)
Expanded(
child: Center(
child: ElevatedButton(
onPressed: () {
showInterstitialAd(onDone: (shown) {
// called after ad finishes (shown or skipped)
});
},
child: const Text('Show Interstitial'),
),
),
),
],
),
);
}
}
Step 5 β Show Rewarded Ad #
showRewardedAd(
onEarnedReward: (earned) {
if (earned) {
// β
User watched the full ad β grant the reward
giveUserCoins(10);
}
// If not earned, SDK automatically shows "Ad not ready" TopToast
},
);
Step 6 β Show Interstitial Ad #
showInterstitialAd(
onDone: (wasShown) {
// β
This is always called, whether ad showed or not
// Do your navigation or next action here
Navigator.push(context, MaterialPageRoute(builder: (_) => NextScreen()));
},
);
Tip: Always put your navigation inside
onDone, not beforeshowInterstitialAd. This ensures navigation happens after the ad, not during.
Step 7 β Switch Provider #
To switch from AppLovin to AdMob (or vice versa), change only one line:
AdConfig(
provider: AdProvider.admob, // β change here: admob or appLovin
admob: AdMobConfig(...), // AdMob config (used when provider = admob)
appLovin: AppLovinConfig(...), // AppLovin config (used when provider = appLovin)
)
Both configs can exist simultaneously. Only the selected provider is used.
π Ad Types Reference #
Banner Ad #
- Added automatically via
buildBanner()insideAdScreenState - Pauses when another screen pushes on top (via
RouteAware) - Resumes when returning to the screen
- Auto-refreshes every ~15 seconds (managed by network)
- Shows shimmer loading state while filling
// Place at top or bottom of your Scaffold body
buildBanner()
Interstitial Ad #
- Full-screen between user actions (e.g. before navigation)
- Built-in 30-second throttle between shows
- Preloaded automatically on screen init
showInterstitialAd(onDone: (wasShown) { /* navigate here */ });
Rewarded Ad #
- Full-screen opt-in for users to earn rewards
- Built-in throttle (same as interstitial)
- TopToast automatically shown if ad not ready
showRewardedAd(onEarnedReward: (earned) { if (earned) grantReward(); });
App Open Ad #
- Shown on cold start (splash) and when app returns from background
- On splash: call with
bypassSafety: true - On resume: SDK handles automatically via
AppLifecycleState
// On splash only β SDK handles resume automatically
AdManager().showAppOpenAd(
bypassSafety: true,
onAdDismiss: (_) => navigateToHome(),
);
βοΈ AdConfig Reference #
AdConfig(
// ββ Required ββββββββββββββββββββββββββββββββββββββ
provider: AdProvider.appLovin, // or AdProvider.admob
// ββ Provider configs (include both β switch via 'provider' field) ββ
admob: AdMobConfig(
bannerId: '...',
interstitialId: '...',
appOpenId: '...',
rewardedId: '...',
testDeviceIds: ['HASH'], // optional: AdMob test device hash
),
appLovin: AppLovinConfig(
sdkKey: '...', // 86-character SDK key
bannerId: '...', // 16-character ad unit ID
interstitialId: '...',
appOpenId: '...',
rewardedId: '...',
),
// ββ Optional ββββββββββββββββββββββββββββββββββββββ
vipDeviceGaids: ['gaid-1'], // these devices never see ads
loadingBufferMs: 1000, // delay before showing ad (ms), default 1000
adNotReadyMessage: 'Ad not ready β please try again later.',
adLoadingMessage: 'Loadingβ¦',
)
π‘οΈ Safety Layer #
The SDK includes a built-in anti-fraud safety layer that prevents ad spam:
| Limit | Default | Description |
|---|---|---|
| Daily cap | 5 ads/day | Total fullscreen ads per day |
| Hourly cap | 3 ads/hour | Fullscreen ads per hour |
| Session cap | 6 ads/session | Fullscreen ads per app open |
| Throttle | 30s | Minimum gap between fullscreen ads |
| CTR cap | 3 clicks/min | Suspicious click rate detection |
| Cooldown | Progressive | Error backoff to prevent hammering network |
Safety is applied automatically. You do not need to configure it unless you want to customize limits via AdSafetyConfig.
π VIP Bypass #
Add device GAIDs to skip ads for app owners and internal testers:
AdConfig(
vipDeviceGaids: [
'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', // your device GAID
],
// ...
)
How to find your GAID: Run the example app in debug mode. The GAID is logged at init:
[AdManager] ###init GAID: be39dfe0-67f5-4da4-afb3-8407cd481df4
Note: In debug builds, VIP member list is not enforced
(###init Debug mode, skip adding VIP members).
This is intentional β it prevents accidental production GAID exposure.
π TopToast #
A glassmorphism animated toast that slides in from the top of the screen.
Used automatically when a rewarded ad is not ready.
Manual usage:
TopToast.show(
context,
icon: Icons.info_outline, // optional, default: warning icon
message: 'Your message here',
iconColor: Colors.blue, // optional, default: amber
);
π Troubleshooting #
Ads not showing #
- Check
AndroidManifest.xmlhasAPPLICATION_IDmeta-data - Check your ad unit IDs are correct (not placeholder
YOUR_*) - Check network connectivity
- Wait β new ad units take up to 24h to activate on AdMob
- Enable logging and look for
β Failed:orπ‘οΈ blocked:in logs
Hard cap timer fires before App Open Ad shows #
Ensure you call AdManager().markSplashActive() and markSplashInactive() in your splash screen, and that _hardCapTimer?.cancel() is called before showAppOpenAd().
Banner not showing #
adRouteObservermust be innavigatorObservers- Your screen must extend
AdScreen+AdScreenState - Call
buildBanner()inside your widget tree (not conditionally)
AppLovin ads not loading #
- SDK Key must be exactly 86 characters β check
dash.applovin.com/o/account - Ad Unit IDs must be exactly 16 characters β check
dash.applovin.com/o/mediation/ad_units - Register your test device in AppLovin dashboard or rely on debug auto-registration
"Ad not ready" toast appears every time #
The safety throttle is active. Default is 30 seconds between ads. Wait and try again.
π License #
MIT Β© 2026 β see LICENSE