ad_flow 1.0.0+1 copy "ad_flow: ^1.0.0+1" to clipboard
ad_flow: ^1.0.0+1 copied to clipboard

A complete AdMob integration solution for Flutter. Easily add banner, interstitial, native, and app open ads with built-in GDPR/ATT consent management, remove-ads functionality, and clean widget-based APIs.

ad_flow - Professional AdMob Integration for Flutter #

A production-ready, fully compliant AdMob integration package for Flutter with GDPR, US Privacy, and iOS ATT support.

pub package Flutter google_mobile_ads License

✨ Features #

Feature Status Description
Banner Ads Adaptive banners that fit any screen
Collapsible Banners Expandable banners for higher engagement
Interstitial Ads Full-screen ads with smart cooldown
App Open Ads Ads on app launch/resume
GDPR Consent EU/UK/Switzerland compliance
US Privacy CCPA and state regulations
iOS ATT App Tracking Transparency
Native Ads Custom ads matching your app design
Remove Ads Built-in IAP support to disable ads
Auto Preloading Ads ready when you need them
Retry Logic Exponential backoff on failures

📦 Installation #

1. Add Dependency #

# pubspec.yaml
dependencies:
  google_mobile_ads: ^6.0.0

2. Android Setup #

android/app/src/main/AndroidManifest.xml:

<manifest>
    <application>
        <!-- AdMob App ID -->
        <meta-data
            android:name="com.google.android.gms.ads.APPLICATION_ID"
            android:value="ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX"/>
    </application>
</manifest>

3. iOS Setup #

📱 ios/Runner/Info.plist (click to expand)
<!-- AdMob App ID -->
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX</string>

<!-- App Tracking Transparency -->
<key>NSUserTrackingUsageDescription</key>
<string>This identifier will be used to deliver personalized ads to you.</string>

<!-- SKAdNetwork IDs (required for iOS 14+) -->
<key>SKAdNetworkItems</key>
<array>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>cstr6suwn9.skadnetwork</string>
    </dict>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>4fzdc2evr5.skadnetwork</string>
    </dict>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>4pfyvq9l8r.skadnetwork</string>
    </dict>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>2fnua5tdw4.skadnetwork</string>
    </dict>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>ydx93a7ass.skadnetwork</string>
    </dict>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>5a6flpkh64.skadnetwork</string>
    </dict>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>p78aez3dza.skadnetwork</string>
    </dict>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>v72qych5uu.skadnetwork</string>
    </dict>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>c6k4g5qg8m.skadnetwork</string>
    </dict>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>s39g8k73mm.skadnetwork</string>
    </dict>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>3qy4746246.skadnetwork</string>
    </dict>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>3sh42y64q3.skadnetwork</string>
    </dict>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>f38h382jlk.skadnetwork</string>
    </dict>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>hs6bdukanm.skadnetwork</string>
    </dict>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>prcb7njmu6.skadnetwork</string>
    </dict>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>wzmmz9fp6w.skadnetwork</string>
    </dict>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>yclnxrl5pm.skadnetwork</string>
    </dict>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>7ug5zh24hu.skadnetwork</string>
    </dict>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>9rd848q2bz.skadnetwork</string>
    </dict>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>n6fk4nfna4.skadnetwork</string>
    </dict>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>kbd757ywx3.skadnetwork</string>
    </dict>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>9t245vhmpl.skadnetwork</string>
    </dict>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>4468km3ulz.skadnetwork</string>
    </dict>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>m8dbw4sv7c.skadnetwork</string>
    </dict>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>zmvfpc5aq8.skadnetwork</string>
    </dict>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>ejvt5qm6ak.skadnetwork</string>
    </dict>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>5lm9lj6jb7.skadnetwork</string>
    </dict>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>44jx6755aq.skadnetwork</string>
    </dict>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>t38b2kh725.skadnetwork</string>
    </dict>
    <dict>
        <key>SKAdNetworkIdentifier</key>
        <string>24t9a8vw3c.skadnetwork</string>
    </dict>
</array>

🚀 Quick Start #

⚠️ Important: Initialize Only ONCE #

AdService is a singleton - you only need to initialize it once for your entire app, typically on your first screen (splash or home page). All other pages can simply use AdService.instance to show ads.

┌─────────────────────────────────────────────────────────────┐
│  YOUR APP                                                    │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐   │
│  │   Page 1     │    │   Page 2     │    │   Page 3     │   │
│  │  (Splash)    │    │   (Home)     │    │  (Details)   │   │
│  │              │    │              │    │              │   │
│  │ initialize() │───▶│ showBanner() │───▶│ showBanner() │   │
│  │    ✅        │    │     ✅        │    │     ✅        │   │
│  └──────────────┘    └──────────────┘    └──────────────┘   │
│         │                    │                    │          │
│         └────────────────────┴────────────────────┘          │
│                              │                               │
│                    ┌─────────▼─────────┐                    │
│                    │    AdService      │                    │
│                    │    (Singleton)    │                    │
│                    │                   │                    │
│                    │  • BannerManager  │                    │
│                    │  • Interstitial   │                    │
│                    │  • AppOpenAd      │                    │
│                    │  • Consent        │                    │
│                    └───────────────────┘                    │
│                                                              │
└─────────────────────────────────────────────────────────────┘
Action Where How Often
initialize() First page only Once per app launch
Show ads Any page As needed

Initialize in main.dart #

import 'package:flutter/material.dart';
import 'ads/ads.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize AdMob with consent handling (ONCE!)
  await AdService.instance.initialize(
    onComplete: (canRequestAds) {
      debugPrint('Ads ready: $canRequestAds');
    },
    preloadInterstitial: true,  // Preload interstitial
    preloadAppOpen: true,       // Preload app open ad
    enableAppOpenOnForeground: true, // Show ad on app resume
  );

  runApp(const MyApp());
}

📱 Usage Examples #

import 'ads/ads.dart';

// Just drop this widget anywhere!
@override
Widget build(BuildContext context) {
  return Scaffold(
    body: YourContent(),
    
    // One line for a banner ad:
    bottomNavigationBar: const EasyBannerAd(),
  );
}
class MyPage extends StatefulWidget {
  @override
  State<MyPage> createState() => _MyPageState();
}

class _MyPageState extends State<MyPage> {
  final BannerAdManager _bannerManager = BannerAdManager();

  @override
  void initState() {
    super.initState();
    _loadBanner();
  }

  Future<void> _loadBanner() async {
    await _bannerManager.loadAdaptiveBanner(
      context: context,
      onAdLoaded: (ad) => setState(() {}),
      onAdFailedToLoad: (ad, error) {
        debugPrint('Banner failed: ${error.message}');
      },
    );
  }

  @override
  void dispose() {
    _bannerManager.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: YourContent(),
      bottomNavigationBar: _bannerManager.isLoaded
          ? _bannerManager.buildAdWidget()
          : const SizedBox.shrink(),
    );
  }
}

Collapsible Banner Ads #

// Using EasyBannerAd widget
const EasyBannerAd(collapsible: true)

// Or with BannerAdManager
await _bannerManager.loadCollapsibleBanner(
  context: context,
  placement: CollapsibleBannerPlacement.bottom, // or .top
  onAdLoaded: (ad) => setState(() {}),
);

Interstitial Ads #

// Show interstitial (auto-preloaded on init)
await AdService.instance.interstitial.showAd(
  onAdDismissed: () {
    // Continue with your app
    Navigator.pushNamed(context, '/nextScreen');
  },
  onAdFailedToShow: () {
    // Ad not ready, proceed anyway
    Navigator.pushNamed(context, '/nextScreen');
  },
);

// Check if ready before showing
if (AdService.instance.interstitial.isLoaded) {
  AdService.instance.interstitial.showAd();
}

Interstitial with Frequency Control #

int _actionCount = 0;

void _onUserAction() {
  _actionCount++;
  
  // Show interstitial every 5 actions
  if (_actionCount % 5 == 0) {
    if (AdService.instance.interstitial.isLoaded) {
      AdService.instance.interstitial.showAd();
    }
  }
}

App Open Ads #

App open ads are automatically handled when you set enableAppOpenOnForeground: true during initialization. They show when the user brings your app to the foreground.

// Manual control (if needed)
if (AdService.instance.appOpen.isAdAvailable) {
  await AdService.instance.appOpen.showAdIfAvailable(
    onAdDismissed: () {
      // App resumed
    },
  );
}

Privacy Settings Button #

// Check if user needs privacy options (GDPR regions)
if (AdService.instance.isPrivacyOptionsRequired) {
  IconButton(
    icon: const Icon(Icons.privacy_tip),
    onPressed: () {
      AdService.instance.showPrivacyOptions(
        onComplete: () {
          // User updated privacy settings
        },
      );
    },
  );
}

Ad Inspector (Debug Mode) #

// Open the Ad Inspector for debugging
AdService.instance.openAdInspector();

Remove Ads (In-App Purchase) #

Built-in support for "Remove Ads" purchases:

// After successful IAP purchase
await AdService.instance.disableAds();

// All ad widgets automatically hide!
// EasyBannerAd, EasyNativeAd, etc. respect this setting.
// Check if ads are enabled
if (AdService.instance.isAdsEnabled) {
  // Show ads
}

// Re-enable ads (e.g., restore purchase failed)
await AdService.instance.enableAds();
// Reactive UI with StreamBuilder
StreamBuilder<bool>(
  stream: AdService.instance.adsEnabledStream,
  builder: (context, snapshot) {
    final adsEnabled = snapshot.data ?? true;
    if (!adsEnabled) return const SizedBox.shrink();
    return const EasyBannerAd();
  },
)

📂 Package Structure #

lib/ads/
├── ads.dart                        # Barrel export (import this)
├── ad_config.dart                  # Configuration & ad unit IDs
├── ad_service.dart                 # Main service (singleton)
├── ads_enabled_manager.dart        # Remove Ads feature
├── consent_manager.dart            # GDPR/ATT consent handling
├── consent_explainer_dialog.dart   # Pre-consent explainer dialogs
├── consent_explainer_localizations.dart # Multi-language support
├── banner_ad_manager.dart          # Banner ad management
├── easy_banner_widget.dart         # Drop-in banner widget
├── interstitial_ad_manager.dart    # Interstitial ad management
├── app_open_ad_manager.dart        # App open ad management
├── app_lifecycle_reactor.dart      # App state monitoring
├── native_ad_manager.dart          # Native ad management
└── native_ad_widget.dart           # Drop-in native ad widgets

⚙️ Configuration #

Ad Unit IDs #

Edit lib/ads/ad_config.dart to set your production ad unit IDs:

class AdConfig {
  // Replace with your production IDs from AdMob console
  
  static String get bannerAdUnitId {
    if (Platform.isAndroid) {
      return 'ca-app-pub-YOUR_ID/BANNER_ID';
    } else if (Platform.isIOS) {
      return 'ca-app-pub-YOUR_ID/BANNER_ID';
    }
    throw UnsupportedError('Unsupported platform');
  }

  static String get interstitialAdUnitId {
    if (Platform.isAndroid) {
      return 'ca-app-pub-YOUR_ID/INTERSTITIAL_ID';
    } else if (Platform.isIOS) {
      return 'ca-app-pub-YOUR_ID/INTERSTITIAL_ID';
    }
    throw UnsupportedError('Unsupported platform');
  }

  static String get appOpenAdUnitId {
    if (Platform.isAndroid) {
      return 'ca-app-pub-YOUR_ID/APP_OPEN_ID';
    } else if (Platform.isIOS) {
      return 'ca-app-pub-YOUR_ID/APP_OPEN_ID';
    }
    throw UnsupportedError('Unsupported platform');
  }
}

Behavior Settings #

// In ad_config.dart

/// Cache app open ads for max 4 hours (Google's recommendation)
static const Duration appOpenAdMaxCacheDuration = Duration(hours: 4);

/// Minimum time between interstitial ads
static const Duration minInterstitialInterval = Duration(seconds: 60);

/// Number of retry attempts for failed ad loads
static const int maxLoadRetries = 3;

/// Delay between retries
static const Duration retryDelay = Duration(seconds: 5);

Test Device IDs #

Add your test device ID to avoid invalid impressions during development:

// In ad_config.dart
static const List<String> testDeviceIds = [
  'YOUR_DEVICE_HASHED_ID', // From logcat/console
];

Find your device ID in the console logs:

I/Ads: Use RequestConfiguration.Builder().setTestDeviceIds(Arrays.asList("YOUR_DEVICE_ID"))

🔒 Privacy & Compliance #

GDPR (Europe) #

  • ✅ Automatically shows consent form for EU/UK/Switzerland users
  • ✅ Uses Google's certified UMP SDK
  • ✅ Stores consent for future sessions
  • ✅ Respects user's privacy choices

US Privacy (CCPA) #

  • ✅ Supports US state privacy regulations
  • ✅ Handles opt-out requests

iOS ATT (App Tracking Transparency) #

  • ✅ Integrated with consent flow
  • ✅ Shows system permission dialog
  • ✅ Respects user's tracking choice

How It Works #

App Start
    │
    ▼
┌─────────────────────┐
│ Check Consent Status │
└─────────────────────┘
    │
    ▼ (if GDPR region)
┌─────────────────────┐
│ Show Consent Form   │
└─────────────────────┘
    │
    ▼ (if iOS)
┌─────────────────────┐
│ ATT Permission      │
└─────────────────────┘
    │
    ▼
┌─────────────────────┐
│ Initialize Ads SDK  │
└─────────────────────┘
    │
    ▼
┌─────────────────────┐
│ Preload Ads         │
└─────────────────────┘

For a friendlier user experience, you can show an explainer dialog before the official consent popups appear. This gives users context about why they're being asked for consent.

// Option 1: Initialize with explainer (recommended for better UX)
class _MyHomePageState extends State<MyHomePage> {
  @override
  void initState() {
    super.initState();
    // Show explainer after first frame renders
    WidgetsBinding.instance.addPostFrameCallback((_) {
      AdService.instance.initializeWithExplainer(
        context: context,
        onComplete: (canRequestAds) {
          debugPrint('Ads ready: $canRequestAds');
        },
      );
    });
  }
}

// Option 2: Standard initialization (consent popups appear immediately)
await AdService.instance.initialize(
  onComplete: (canRequestAds) {
    // Ready
  },
);

The explainer shows:

  • 🎯 General privacy explainer - "Your Privacy Matters" with benefits
  • 📱 iOS ATT explainer - Brief explanation before the system ATT popup

You can also show the dialogs manually:

// Show the general consent explainer
await ConsentExplainerDialog.show(context);

// Show the iOS ATT explainer (iOS only)
await ATTExplainerDialog.show(context);

Multi-Language Support #

Built-in localized texts for consent explainers:

Language Consent Texts ATT Texts
English (default) kDefaultConsentExplainerTexts kDefaultATTExplainerTexts
Persian (فارسی) kPersianConsentExplainerTexts kPersianATTExplainerTexts
Spanish (Español) kSpanishConsentExplainerTexts kSpanishATTExplainerTexts
// Use pre-defined language texts
AdService.instance.initializeWithExplainer(
  context: context,
  consentTexts: kPersianConsentExplainerTexts,
  attTexts: kPersianATTExplainerTexts,
  onComplete: (canRequestAds) {
    debugPrint('Ads ready: $canRequestAds');
  },
);
// Or get texts by language code
final (consentTexts, attTexts) = getExplainerTextsForLanguage('es');

AdService.instance.initializeWithExplainer(
  context: context,
  consentTexts: consentTexts,
  attTexts: attTexts,
);
// Create custom texts for any language
const myCustomTexts = ConsentExplainerTexts(
  title: 'Your Title',
  description: 'Your description...',
  benefitRelevantAds: 'Relevant ads',
  benefitDataSecure: 'Data stays secure',
  benefitKeepFree: 'Keeps app free',
  settingsHint: 'Change anytime in Settings.',
  continueButton: 'Continue',
  skipButton: 'Decide later',
);

💰 Revenue Optimization Tips #

1. Ad Placement Best Practices #

Do ✅ Don't ❌
Place banners at natural content breaks Cover content with ads
Show interstitials at natural pauses Show interstitials during gameplay
Use app open ads on cold start Show too many app open ads
Test different placements Ignore user experience

2. Interstitial Frequency #

// Recommended: Every 3-5 user actions or natural breaks
static const Duration minInterstitialInterval = Duration(seconds: 60);

3. Banner Refresh #

Banners automatically refresh every 60 seconds (AdMob default). Don't manually refresh more frequently.

4. Fill Rate Optimization #

  • ✅ Use adaptive banners (auto-sizes)
  • ✅ Keep HTTP timeout at 30 seconds
  • ✅ Implement retry logic (included)
  • ✅ Test on real devices

5. eCPM Optimization #

  • ✅ Enable all ad formats
  • ✅ Use mediation (optional, advanced)
  • ✅ Target appropriate content rating
  • ✅ Maintain high user engagement

🔍 API Reference #

AdService #

// Singleton instance
AdService.instance

// Properties
bool isInitialized              // SDK initialized?
bool isMobileAdsInitialized     // Mobile Ads ready?
bool isPrivacyOptionsRequired   // Show privacy button?
bool isAdsEnabled               // Ads enabled? (Remove Ads)
bool isAdsDisabled              // Ads disabled?

// Managers
ConsentManager consent          // Consent handling
BannerAdManager banner          // Banner ads
InterstitialAdManager interstitial  // Interstitial ads
AppOpenAdManager appOpen        // App open ads
NativeAdManager native          // Native ads

// Methods
Future<void> initialize({...})  // Initialize everything
Future<void> disableAds()       // Disable ads (Remove Ads)
Future<void> enableAds()        // Re-enable ads
void showPrivacyOptions({...})  // Show privacy form
void openAdInspector()          // Debug tool

// Streams
Stream<bool> adsEnabledStream   // Reactive ads enabled state

BannerAdManager #

// Properties
bool isLoaded                   // Banner ready?
bool isLoading                  // Loading in progress?
BannerAd? bannerAd             // The ad object

// Methods
Future<void> loadAdaptiveBanner({...})    // Load adaptive banner
Future<void> loadCollapsibleBanner({...}) // Load collapsible banner
Widget? buildAdWidget()                    // Get AdWidget
void dispose()                             // Clean up

InterstitialAdManager #

// Properties
bool isLoaded                   // Ad ready?
bool isLoading                  // Loading in progress?
bool isShowing                  // Currently displayed?
bool canShowAd                  // Cooldown passed?

// Methods
Future<void> loadAd({...})     // Load interstitial
Future<bool> showAd({...})     // Show interstitial
void dispose()                  // Clean up

AppOpenAdManager #

// Properties
bool isLoaded                   // Ad loaded?
bool isAdAvailable             // Ready & not expired?

// Methods
Future<void> loadAd({...})           // Load app open ad
Future<void> showAdIfAvailable({...}) // Show if available
void dispose()                        // Clean up

EasyBannerAd Widget #

const EasyBannerAd({
  bool collapsible = false,  // Use collapsible format?
})

NativeAdManager #

// Properties
bool isLoaded                   // Ad loaded?
bool isLoading                  // Loading in progress?
NativeAd? nativeAd             // The ad object

// Methods
Future<void> loadAd({...})     // Load native ad
void dispose()                  // Clean up

EasyNativeAd Widget #

const EasyNativeAd({
  required String factoryId,   // Native ad factory ID
  double? height,              // Ad height
  Widget? placeholder,         // Loading placeholder
})

AdsEnabledManager #

// Singleton instance
AdsEnabledManager.instance

// Properties
bool isEnabled                 // Ads enabled?
bool isDisabled                // Ads disabled?

// Methods
Future<void> disableAds()      // Disable all ads
Future<void> enableAds()       // Re-enable ads
void addListener(callback)     // Listen for changes
void removeListener(callback)  // Remove listener

// Stream
Stream<bool> stream            // Reactive state changes

🐛 Troubleshooting #

Ads Not Loading #

  1. Check internet connection
  2. Verify ad unit IDs are correct
  3. Wait 24-48 hours after creating new ad units
  4. Check logs for error codes:
    • Error 0: Internal error
    • Error 1: Invalid request
    • Error 2: Network error
    • Error 3: No fill
  1. Form only shows in GDPR regions (EU/UK/Switzerland)
  2. Use VPN to test from GDPR region
  3. Add test device ID for consent debugging

iOS Build Errors #

  1. Run pod install in ios folder
  2. Update minimum iOS version to 13.0+
  3. Ensure Info.plist has all required keys

Android Build Errors #

  1. Check minSdkVersion is 21+
  2. Ensure AndroidManifest.xml has App ID
  3. Run flutter clean && flutter pub get

📋 Checklist Before Release #

  • ❌ Replace test ad unit IDs with production IDs
  • ❌ Remove test device IDs
  • ❌ Set enableConsentDebug = false
  • ❌ Test on real devices
  • ❌ Test consent flow in GDPR region (use VPN)
  • ❌ Verify iOS ATT dialog appears
  • ❌ Test all ad formats load and display
  • ❌ Check ads don't block UI elements
  • ❌ Review AdMob policies compliance
  • ❌ Add privacy policy to app/store listing

📜 License #

MIT License - Feel free to use in any project.


🙏 Credits #

Built with:


📞 Support #

For issues or questions:

  1. Check AdMob Help Center
  2. Review google_mobile_ads documentation
  3. See Flutter AdMob samples

Happy Monetizing! 💰

2
likes
0
points
726
downloads

Publisher

verified publisherfaizahmaddae.com

Weekly Downloads

A complete AdMob integration solution for Flutter. Easily add banner, interstitial, native, and app open ads with built-in GDPR/ATT consent management, remove-ads functionality, and clean widget-based APIs.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

app_tracking_transparency, cupertino_icons, flutter, google_mobile_ads, shared_preferences

More

Packages that depend on ad_flow