vie_app_ads_manager
A lightweight, opinionated ads manager for Flutter that unifies AdMob (Google Mobile Ads) and AppLovin MAX into a single, simple API.
Features
- Full Ad Format Support: Interstitial, Rewarded, Rewarded-Interstitial (AdMob), App Open, Banner, Native
- Smart Fallback System: Automatic fallback from AdMob to AppLovin MAX when configured
- Remote Config Driven Providers: Select AdMob/MAX and enable flags via Firebase Remote Config
- Widget-Based Ads: Banner and Native ad widgets that auto-select provider via RC
- Event Streaming: Real-time ad lifecycle events for UI state management
- Test Mode Support: Built-in AppLovin MAX mediation debugger and AdMob test ads
- Robust Error Handling: Comprehensive logging and state management
Works with: google_mobile_ads, applovin_max.
Installation
Add to your pubspec.yaml:
dependencies:
vie_app_ads_manager: ^2.0.2
Then run:
flutter pub get
Platform setup
Follow the official setup for each SDK your app will use and apply the additional configuration below.
Android configuration
-
Project-level Gradle (
android/build.gradle)Add the AppLovin repository and configure dependencies with version alignment:
buildscript { repositories { google() mavenCentral() maven { url = uri("https://artifacts.applovin.com/android") } } dependencies { classpath 'com.android.tools.build:gradle:8.1.2' // Use your current version classpath 'com.google.gms:google-services:4.3.15' // Add this line for Google Services } } allprojects { repositories { google() mavenCentral() maven { url = uri("https://artifacts.applovin.com/android") } } configurations.all { resolutionStrategy { force( "com.google.android.gms:play-services-ads:24.7.0", "com.applovin.mediation:google-adapter:24.7.0.0" ) } } }Note: Adapt versions to the latest recommended by google_mobile_ads and applovin_max packages.
-
App-level Gradle (
android/app/build.gradle)Add the mediation adapter dependencies to the
dependenciesblock:dependencies { // Align Google Mobile Ads SDK with AppLovin Google adapter implementation("com.google.android.gms:play-services-ads:24.7.0") implementation("com.applovin.mediation:google-adapter:24.7.0.0") } -
AndroidManifest (
android/app/src/main/AndroidManifest.xml)Add required permissions, AdMob application ID, and AppLovin MAX activities:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <application android:name="${applicationName}" android:icon="@mipmap/launcher_icon" android:label="YOUR_APP_NAME"> <!-- Your main activity configuration --> <activity android:name=".MainActivity" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:exported="true" android:hardwareAccelerated="true" android:launchMode="singleTask" android:taskAffinity="${applicationId}" android:theme="@style/LaunchTheme" android:windowSoftInputMode="adjustResize"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <!-- AdMob App ID required for Google Mobile Ads SDK initialization --> <meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="YOUR_ADMOB_APP_ID_ANDROID" /> <!-- AppLovin MAX Ad Activities - Force them to use our task --> <activity android:name="com.applovin.adview.AppLovinInterstitialActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize" android:excludeFromRecents="true" android:taskAffinity="${applicationId}" tools:replace="android:configChanges,android:taskAffinity" /> <activity android:name="com.applovin.adview.AppLovinFullscreenActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize" android:excludeFromRecents="true" android:taskAffinity="${applicationId}" tools:replace="android:configChanges,android:taskAffinity" /> <!-- Optional: For webview-based ads or browsers --> <activity android:name="com.applovin.sdk.AppLovinWebViewActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize" android:excludeFromRecents="true" android:taskAffinity="${applicationId}" tools:replace="android:configChanges,android:taskAffinity" /> <!-- Optional: If using the mediation debugger --> <!-- REMOVE THIS IN PRODUCTION --> <!-- <activity--> <!-- android:name="com.applovin.mediation.MaxDebuggerActivity"--> <!-- android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"--> <!-- android:excludeFromRecents="true"--> <!-- android:taskAffinity="${applicationId}"--> <!-- tools:replace="android:configChanges,android:taskAffinity" />--> <meta-data android:name="flutterEmbedding" android:value="2" /> </application> <queries> <intent> <action android:name="android.intent.action.PROCESS_TEXT" /> <data android:mimeType="text/plain" /> </intent> </queries> </manifest>For AdMob Mediation Update your gradle settings (Android only) Add the following lines to your settings.gradle file, so you can use the plugin's Android APIs:
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}()
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
Important:
- Replace
YOUR_ADMOB_APP_ID_ANDROIDwith your real AdMob app ID - Replace
YOUR_APP_NAMEwith your app name - The AppLovin MAX activities ensure ads stay within your app's task
- Uncomment the debugger activity only during development
- The
<queries>tag is required for some ad networks to work properly
iOS configuration
This package works on iOS as well as Android. The Dart implementation already handles iOS IDs (for example
appIdIos) and requests App Tracking Transparency consent on iOS. You only need to complete the platform-specific Xcode setup below.
-
Info.plist AdMob app ID & tracking permission
In your iOS Runner target (
ios/Runner/Info.plist), add your AdMob app ID and a tracking description (required by App Tracking Transparency and recommended by Google):<key>GADApplicationIdentifier</key> <string>YOUR_ADMOB_APP_ID_IOS</string> <key>NSUserTrackingUsageDescription</key> <string>This identifier will be used to deliver personalized ads to you.</string>- Replace
YOUR_ADMOB_APP_ID_IOSwith your real AdMob iOS app ID. - See the official setup guide for more Info.plist options (e.g.
SKAdNetworkItems):
- Replace
-
AppLovin MAX
- On iOS, the Flutter
applovin_maxplugin is initialized entirely in Dart viaMaxIds.sdkKeyandAppLovinMAX.initialize(...), which this package already calls. - For a basic integration, you do not need additional Info.plist keys.
- For advanced configuration (e.g. additional
SKAdNetworkItemsentries for MAX and mediated networks), follow the official AppLovin MAX iOS docs:
- On iOS, the Flutter
-
Pods & adapters
- The necessary iOS SDKs and mediation adapters are pulled in automatically via CocoaPods from the
google_mobile_adsandapplovin_maxFlutter plugins. - After adding this package and the required plugins to your
pubspec.yaml, runpod installinside theiosdirectory (orflutter build ios/flutter run, which will trigger CocoaPods). - You usually do not need to manually edit
Podfileor add adapter pods yourself unless the upstream plugin documentation explicitly tells you to.
- The necessary iOS SDKs and mediation adapters are pulled in automatically via CocoaPods from the
-
Adjust (iOS)
- This package initializes Adjust from Dart using the
adjustAppTokenyou pass intoAdsManager.instance.initialize(...). - The native iOS Adjust SDK and required pods are integrated via the official
adjust_sdkFlutter plugin; you normally do not need extra manual Podfile changes. - For advanced features (e.g.,
SKAdNetworkItems, deep links, or special attribution settings), follow the official Adjust Flutter documentation and add any additional Info.plist keys they recommend for iOS.
- This package initializes Adjust from Dart using the
Make sure you use your own ad unit IDs in production. Use test IDs during development.
Getting started
Import the package:
import 'package:vie_app_ads_manager/ads_manager.dart';
Initialize as early as possible (e.g., in main or first page load). You must initialize Firebase and the provided AnalyticsManager to fetch Remote Config before initializing AdsManager:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(home: const Home());
}
}
class Home extends StatefulWidget {
const Home({super.key});
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
bool _initializing = true;
@override
void initState() {
super.initState();
_initAds();
}
Future<void> _initAds() async {
// 1) Initialize Analytics + Remote Config
await AnalyticsManager.instance.init(
options: /* Your DefaultFirebaseOptions.currentPlatform */,
minimumFetchInterval: Duration.zero,
);
// 2) Provide your AdMob and (optionally) AppLovin MAX IDs
final admobIds = AdmobIds(
appIdAndroid: 'YOUR_ADMOB_APP_ID_ANDROID',
appIdIos: 'YOUR_ADMOB_APP_ID_IOS',
bannerId: 'YOUR_ADMOB_BANNER_ID',
interstitialId: 'YOUR_ADMOB_INTERSTITIAL_ID',
rewardedId: 'YOUR_ADMOB_REWARDED_ID',
rewardedInterstitialId: 'YOUR_ADMOB_REWARDED_INTERSTITIAL_ID',
nativeId: 'YOUR_ADMOB_NATIVE_ID',
appOpenId: 'YOUR_ADMOB_APPOPEN_ID',
);
// Optional: provide AppLovin MAX IDs to enable fallback or to force MAX
final maxIds = MaxIds(
sdkKey: 'YOUR_MAX_SDK_KEY',
bannerId: 'YOUR_MAX_BANNER_ID',
interstitialId: 'YOUR_MAX_INTERSTITIAL_ID',
rewardedId: 'YOUR_MAX_REWARDED_ID',
nativeId: 'YOUR_MAX_NATIVE_ID',
appOpenId: 'YOUR_MAX_APPOPEN_ID',
);
// 3) Initialize AdsManager (it will read provider flags from Remote Config)
await AdsManager.instance.initialize(
admobIds: admobIds,
maxIds: maxIds,
adjustAppToken: 'YOUR_ADJUST_APP_TOKEN',
);
if (!mounted) return;
setState(() => _initializing = false);
}
}
## Remote Config Provider Control
Provider selection and feature flags are controlled via Firebase Remote Config (RC). Configure these keys in RC:
- `show_banner` (bool): enable/disable banner widget
- `native_ad_enabled` (bool): enable/disable native widget
- `show_app_open` (bool): enable/disable app open button/flow
- `banner_provider` (int): 0=off, 1=AdMob only, 2=MAX only
- `interstitial_provider` (int): 0=off, 1=AdMob only, 2=MAX only
- `reward_provider` (int): 0=off, 1=AdMob only, 2=MAX only
- `app_open_provider` (int): 0=off, 1=AdMob only, 2=MAX only
- `native_ad_provider` (int): 0=off, 1=AdMob only, 2=MAX only
AdsManager reads these values during `initialize()` via `AnalyticsManager`.
### How selection works
- `0` → No-Op: show methods return false; widgets render nothing
- `1` → AdMob only (no MAX fallback)
- `2` → MAX only
- Otherwise: default logic attempts AdMob first, with MAX as fallback when available
### Interstitial
```dart
// Provider is selected via RC
final shown = await AdsManager.instance.showInterstitial();
Rewarded
final shown = await AdsManager.instance.showRewarded(
onEarnedReward: (amount) {
// grant reward
},
);
App Open
final shown = await AdsManager.instance.showAppOpen();
Widgets
Banner
A banner widget that auto-selects provider via Remote Config, with automatic fallback when allowed.
// Default behavior (provider sourced from RC)
const BannerAdManager()
// Custom size
const BannerAdManager(adSize: AdSize.largeBanner)
Note: AdSize is re-exported by this package. You only need:
import 'package:ads_manager/ads_manager.dart';
No direct import from google_mobile_ads is required just to use AdSize.
Native
A native ad widget that auto-selects provider via Remote Config, with automatic fallback when allowed.
// Default behavior (provider sourced from RC)
const NativeAdManager(height: 300)
// Custom styling
NativeAdManager(
height: 300,
style: NativeTemplateStyle(templateType: TemplateType.medium),
)
Events
Listen to the stream for ad lifecycle events to drive UI state (e.g., loading spinners):
final sub = AdsManager.instance.onEvent.listen((event) {
final type = event.keys.first; // AdUnitType
final ev = event.values.first; // AdEventType
// handle
});
AdUnitType: banner, interstitial, rewarded, rewardedInterstitial, native, appOpen
AdEventType: loading, loaded, failedToLoad, showed, dismissed, failedToShow, earnedReward
Testing & Debugging
AppLovin MAX Test Mode
The ads manager automatically enables AppLovin MAX mediation debugger during initialization for testing purposes. This provides:
- Real-time mediation waterfall information
- Network adapter status
- Test ad controls
- Revenue and performance metrics
AdMob Test Ads Helper
You can switch to AdMob test IDs at runtime and preload again:
await AdsManager.instance.showTestAds();
This is handy to validate your integration without touching your production IDs.
Provider-Based Testing
Use the helper to switch to AdMob test IDs. To simulate networks, change RC provider keys at runtime and call AnalyticsManager().refreshRemoteConfig() before showing again.
// Switch to AdMob test IDs and preload test ads
await AdsManager.instance.showTestAds();
// Change RC: banner_provider / interstitial_provider / reward_provider / app_open_provider
await AnalyticsManager().refreshRemoteConfig();
Adjust Subscription Tracking
The ads manager includes a helper to track subscription purchases in Adjust using product metadata. This keeps Adjust usage inside the ads manager package.
void trackSubscriptionWithAdsManager({
required String eventToken,
required ProductDetails product,
}) {
AdsManager.instance.trackSubscriptionPurchase(
eventToken: eventToken,
price: product.rawPrice,
currencyCode: product.currencyCode,
productId: product.id,
title: product.title,
description: product.description,
);
};
purchase listener (after confirming a successful purchase):
if (purchaseDetails.status == PurchaseStatus.purchased ||
purchaseDetails.status == PurchaseStatus.restored) {
final product = getProduct(purchaseDetails.productID);
if (product != null) {
trackSubscriptionWithAdsManager(
eventToken: 'YOUR_ADJUST_SUBSCRIPTION_EVENT_TOKEN',
product: product,
);
}
}
Example
See the example/ app for a full usage demo with buttons for interstitial, rewarded, app open, banner, and native ads plus event-handling.
Notes & Tips
General
- Always use Google's test IDs or your own test placements during development to avoid policy violations.
- AppLovin MAX requires your real SDK key and ad unit IDs. Ensure network permissions and platform setup are complete.
- If you prefer not to use MAX, initialize without
max:and the manager will work with AdMob only.
Provider Behavior
- Provider 0 (no ads): All show methods return
false. Banner/Native widgets render nothing and do not load. - Provider 1 (AdMob only): No fallback to MAX, even if MAX is configured. Use this for pure AdMob testing.
- Provider 2 (MAX only): Direct MAX ad loading, bypasses AdMob entirely.
- Default (null provider): Smart fallback system - tries AdMob first, falls back to MAX on failure.
- Widgets: Banner and Native widgets respect provider settings and handle fallbacks accordingly.
Performance
- MAX mediation debugger adds overhead - disable in production builds if needed.
- Provider selection allows you to test individual networks without interference.
- Event streaming helps optimize UI responsiveness during ad loading.
License
MIT