applovin_admob_sdk 1.0.14 copy "applovin_admob_sdk: ^1.0.14" to clipboard
applovin_admob_sdk: ^1.0.14 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 #

pub.dev Flutter License: MIT

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 #

  1. Prerequisites
  2. Installation
  3. Android Setup
  4. iOS Setup
  5. AdMob Setup
  6. AppLovin Setup
  7. Integration Guide (Step-by-Step)
  8. Ad Types Reference
  9. AdConfig Reference
  10. Safety Layer
  11. VIP Bypass
  12. TopToast
  13. 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.14

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 #

  1. Go to admob.google.com β†’ Apps β†’ Add App
  2. 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

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 #

  1. Go to dash.applovin.com β†’ Account β†’ copy SDK Key (86 chars)
  2. 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.13
  gma_mediation_applovin: ^1.0.0   # only if using AppLovin
flutter pub get

Step 2 β€” Register navigatorKey and navigatorObservers #

The SDK needs:

  1. A navigator key β€” so it can show loading dialogs from lifecycle callbacks (e.g. App Open on resume)
  2. Route observers β€” to manage the banner lifecycle (pause/resume automatically)
import 'package:applovin_admob_sdk/applovin_admob_sdk.dart';

// βœ… Create a global navigator key
final navigatorKey = GlobalKey<NavigatorState>();

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // βœ… Register navigator key BEFORE runApp
  AdManager().setNavigatorKey(navigatorKey);
  runApp(const MyApp());
}

// In your MaterialApp:
MaterialApp(
  navigatorKey: navigatorKey,    // βœ… REQUIRED β€” pass key to MaterialApp
  navigatorObservers: [
    adRouteObserver,              // manages RouteAware banner
    AdScreenRouteLogger(),        // logs route changes for debugging
  ],
  home: const SplashScreen(),
)

Why navigator key? Without it, the App Open ad shown when returning from background won't display a loading dialog (1s spinner) before showing. The SDK needs the navigator context to show dialogs from lifecycle observers.

Why route observers? 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 before showInterstitialAd. 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 #

  • Added automatically via buildBanner() inside AdScreenState
  • 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
  • Loading dialog (1s spinner) is shown automatically before the ad β€” requires setNavigatorKey() in main()
// 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 #

  1. Check AndroidManifest.xml has APPLICATION_ID meta-data
  2. Check your ad unit IDs are correct (not placeholder YOUR_*)
  3. Check network connectivity
  4. Wait β€” new ad units take up to 24h to activate on AdMob
  5. Enable logging and look for ❌ Failed: or πŸ›‘οΈ blocked: in logs

Loading dialog stuck on screen (never dismisses) #

Root cause: The loading dialog shown before every fullscreen ad (inter / rewarded / app open) can get stuck if the parent screen is disposed while the buffer timer is running.

Flow that triggers the bug (pre-1.0.8):

showAdBuffer() called β†’ dialog shown β†’ await 1000ms
  β†’ screen disposed during 1s wait (user navigates back fast)
  β†’ context.mounted == false β†’ pop() was skipped
  β†’ dialog stays on screen forever, nobody dismisses it

Fix (1.0.8+): NavigatorState is now captured before the async delay. Since NavigatorState is owned by the root navigator (not the screen), it outlives any screen disposal and can always pop the dialog.

// βœ… Correct β€” navigator captured before any await
final navigator = Navigator.of(context, rootNavigator: true);
await Future.delayed(duration);
navigator.pop(); // always works, even if screen is disposed

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().

  1. adRouteObserver must be in navigatorObservers
  2. Your screen must extend AdScreen + AdScreenState
  3. Call buildBanner() inside your widget tree (not conditionally)

AppLovin ads not loading #

  1. SDK Key must be exactly 86 characters β€” check dash.applovin.com/o/account
  2. Ad Unit IDs must be exactly 16 characters β€” check dash.applovin.com/o/mediation/ad_units
  3. 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.

Rewarded ad watched but reward not granted #

Check that onEarnedReward(true) is being called β€” look for log:

[AdManager] πŸ† [AppLovin] Rewarded Ad Earned Reward

If missing, the user skipped the ad (did not watch fully). The SDK calls onEarnedReward(false) automatically in this case.


πŸ“„ License #

MIT Β© 2026 β€” see LICENSE

3
likes
150
points
347
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Dual-provider ad SDK for Flutter (AdMob + AppLovin MAX) with built-in safety throttle, VIP bypass, RouteAware banner lifecycle management, and animated TopToast feedback.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

advertising_id, applovin_max, connection_notifier, flutter, google_mobile_ads, shared_preferences

More

Packages that depend on applovin_admob_sdk