native_googleads 0.0.2 copy "native_googleads: ^0.0.2" to clipboard
native_googleads: ^0.0.2 copied to clipboard

A Flutter plugin for integrating Google Mobile Ads (AdMob) using native platform implementations. Supports interstitial and rewarded ads with comprehensive callbacks.

example/lib/main.dart

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:native_googleads/native_googleads.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Native Google Ads Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

// Shared small UI helpers (kept in this file for the example)
class StatusChip extends StatelessWidget {
  final bool ready;
  const StatusChip({super.key, required this.ready});

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
      decoration: BoxDecoration(
        color: ready ? Colors.green : Colors.red,
        borderRadius: BorderRadius.circular(12),
      ),
      child: Text(
        ready ? 'Ready' : 'Not Ready',
        style: const TextStyle(color: Colors.white),
      ),
    );
  }
}

// (MiniActionButtons removed; not needed after multi-preload example removal)


class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text('Native Google Ads Demo'),
      ),
      body: ListView(
        padding: const EdgeInsets.all(16.0),
        children: [
          Card(
            child: ListTile(
              leading: const Icon(Icons.ad_units),
              title: const Text('Banner Ads'),
              subtitle: const Text('Display banner ads in your app'),
              trailing: const Icon(Icons.arrow_forward_ios),
              onTap: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (_) => const BannerAdPage()),
                );
              },
            ),
          ),
          const SizedBox(height: 8),
          Card(
            child: ListTile(
              leading: const Icon(Icons.timer_outlined),
              title: const Text('Preloaded Fullscreen Ads'),
              subtitle: const Text('Preload interstitial & rewarded, then show'),
              trailing: const Icon(Icons.arrow_forward_ios),
              onTap: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (_) => const PreloadedFullscreenAdsPage()),
                );
              },
            ),
          ),
          const SizedBox(height: 8),
          Card(
            child: ListTile(
              leading: const Icon(Icons.downloading),
              title: const Text('Preloaded Banner'),
              subtitle: const Text('Load first, then render via widget'),
              trailing: const Icon(Icons.arrow_forward_ios),
              onTap: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (_) => const PreloadedBannerPage()),
                );
              },
            ),
          ),
          const SizedBox(height: 8),
          Card(
            child: ListTile(
              leading: const Icon(Icons.view_carousel),
              title: const Text('Native Ads'),
              subtitle: const Text('Display native ads that match your app'),
              trailing: const Icon(Icons.arrow_forward_ios),
              onTap: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (_) => const NativeAdPage()),
                );
              },
            ),
          ),
          const SizedBox(height: 8),
          Card(
            child: ListTile(
              leading: const Icon(Icons.downloading),
              title: const Text('Preloaded Native'),
              subtitle: const Text('Load first, then render via widget'),
              trailing: const Icon(Icons.arrow_forward_ios),
              onTap: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (_) => const PreloadedNativePage()),
                );
              },
            ),
          ),
          const SizedBox(height: 8),
          Card(
            child: ListTile(
              leading: const Icon(Icons.fullscreen),
              title: const Text('Interstitial Ads'),
              subtitle: const Text('Full-screen ads at natural transitions'),
              trailing: const Icon(Icons.arrow_forward_ios),
              onTap: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (_) => const InterstitialAdPage()),
                );
              },
            ),
          ),
          const SizedBox(height: 8),
          Card(
            child: ListTile(
              leading: const Icon(Icons.card_giftcard),
              title: const Text('Rewarded Ads'),
              subtitle: const Text('Reward users for watching video ads'),
              trailing: const Icon(Icons.arrow_forward_ios),
              onTap: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (_) => const RewardedAdPage()),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

// Banner Ad Page
class BannerAdPage extends StatefulWidget {
  const BannerAdPage({super.key});

  @override
  State<BannerAdPage> createState() => _BannerAdPageState();
}

class _BannerAdPageState extends State<BannerAdPage> {
  BannerAdSize _selectedSize = BannerAdSize.adaptive;
  bool _showAd = false;
  bool _bannerVisible = true; // hide slot on failure

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

  Future<void> _initializeAds() async {
    final appId = Platform.isAndroid
        ? AdTestIds.androidAppId
        : AdTestIds.iosAppId;

    await NativeGoogleads.instance.initialize(appId: appId);
  }

  @override
  Widget build(BuildContext context) {
    final adUnitId = Platform.isAndroid
        ? AdTestIds.androidBanner
        : AdTestIds.iosBanner;

    return Scaffold(
      appBar: AppBar(
        title: const Text('Banner Ads'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const Text(
                  'Select Banner Size:',
                  style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                ),
                const SizedBox(height: 8),
                Wrap(
                  spacing: 8.0,
                  children: [
                    for (final size in BannerAdSize.values)
                      ChoiceChip(
                        label: Text(_getSizeName(size)),
                        selected: _selectedSize == size,
                        onSelected: (selected) {
                          if (selected) {
                            setState(() {
                              _selectedSize = size;
                              _showAd = false;
                            });
                          }
                        },
                      ),
                  ],
                ),
                if (_selectedSize == BannerAdSize.leaderboard &&
                    MediaQuery.of(context).size.width < 728)
                  Container(
                    margin: const EdgeInsets.only(top: 8),
                    padding: const EdgeInsets.all(8),
                    decoration: BoxDecoration(
                      color: Colors.orange[50],
                      borderRadius: BorderRadius.circular(4),
                    ),
                    child: Row(
                      children: [
                        Icon(
                          Icons.warning,
                          size: 16,
                          color: Colors.orange[700],
                        ),
                        const SizedBox(width: 8),
                        Expanded(
                          child: Text(
                            'Leaderboard is too wide for this screen. Will use adaptive size instead.',
                            style: TextStyle(
                              fontSize: 12,
                              color: Colors.orange[900],
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),
                const SizedBox(height: 16),
                Center(
                  child: ElevatedButton(
                    onPressed: () {
                      setState(() {
                        _showAd = !_showAd;
                      });
                    },
                    child: Text(_showAd ? 'Hide Banner' : 'Show Banner'),
                  ),
                ),
              ],
            ),
          ),
          if (_showAd)
            Column(
              children: [
                Container(
                  color: Colors.amber[50],
                  padding: const EdgeInsets.all(8.0),
                  margin: const EdgeInsets.symmetric(horizontal: 16.0),
                  child: Row(
                    children: [
                      Icon(
                        Icons.info_outline,
                        color: Colors.amber[700],
                        size: 16,
                      ),
                      const SizedBox(width: 8),
                      Expanded(
                        child: Text(
                          'Note: Banner loads successfully but displays as placeholder',
                          style: TextStyle(
                            fontSize: 12,
                            color: Colors.amber[900],
                          ),
                        ),
                      ),
                    ],
                  ),
                ),
                const SizedBox(height: 8),
                if (_bannerVisible)
                  Container(
                    color: Colors.grey[200],
                    padding: const EdgeInsets.all(8.0),
                    child: BannerAdWidget(
                      key: ValueKey(_selectedSize),
                      adUnitId: adUnitId,
                      size: _selectedSize,
                      onAdLoaded: () {
                        setState(() => _bannerVisible = true);
                        debugPrint('Banner ad loaded');
                        ScaffoldMessenger.of(context).showSnackBar(
                          const SnackBar(
                            content: Text('Banner ad loaded successfully!'),
                            backgroundColor: Colors.green,
                            duration: Duration(seconds: 2),
                          ),
                        );
                      },
                      onAdFailedToLoad: (error) {
                        debugPrint('Banner ad failed to load: $error');
                        setState(() => _bannerVisible = false); // gracefully hide slot
                        ScaffoldMessenger.of(context).showSnackBar(
                          SnackBar(
                            content: Text('Banner unavailable, slot hidden'),
                            backgroundColor: Colors.red,
                          ),
                        );
                      },
                    ),
                  ),
              ],
            ),
          const Spacer(),
        ],
      ),
    );
  }

  String _getSizeName(BannerAdSize size) {
    final screenWidth = MediaQuery.of(context).size.width;
    switch (size) {
      case BannerAdSize.banner:
        return 'Banner (320x50)';
      case BannerAdSize.largeBanner:
        return 'Large (320x100)';
      case BannerAdSize.mediumRectangle:
        return 'Medium (300x250)';
      case BannerAdSize.fullBanner:
        return screenWidth < 468 ? 'Full (468x60) ⚠️' : 'Full (468x60)';
      case BannerAdSize.leaderboard:
        return screenWidth < 728
            ? 'Leaderboard (728x90) ⚠️'
            : 'Leaderboard (728x90)';
      case BannerAdSize.adaptive:
        return 'Adaptive';
    }
  }
}

// Preloaded Banner Page
class PreloadedBannerPage extends StatefulWidget {
  const PreloadedBannerPage({super.key});

  @override
  State<PreloadedBannerPage> createState() => _PreloadedBannerPageState();
}

class _PreloadedBannerPageState extends State<PreloadedBannerPage> {
  final NativeGoogleads _ads = NativeGoogleads.instance;
  String? _bannerId;
  BannerAdSize _size = BannerAdSize.adaptive;
  String _status = 'Idle';

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

  Future<void> _initializeAds() async {
    await _ads.initialize(appId: Platform.isAndroid
        ? AdTestIds.androidAppId
        : AdTestIds.iosAppId);
  }

  Future<void> _preload() async {
    setState(() => _status = 'Loading...');
    final id = await _ads.loadBannerAd(
      adUnitId: Platform.isAndroid ? AdTestIds.androidBanner : AdTestIds.iosBanner,
      size: _size,
    );
    setState(() {
      _bannerId = id;
      _status = id != null ? 'Ready' : 'Failed';
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Preloaded Banner'), backgroundColor: Theme.of(context).colorScheme.inversePrimary),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          Row(
            children: [
              const Text('Size:'),
              const SizedBox(width: 8),
              DropdownButton<BannerAdSize>(
                value: _size,
                onChanged: (v) => setState(() => _size = v ?? _size),
                items: [
                  for (final s in BannerAdSize.values)
                    DropdownMenuItem(value: s, child: Text(s.name)),
                ],
              ),
              const Spacer(),
              ElevatedButton.icon(onPressed: _preload, icon: const Icon(Icons.download), label: const Text('Preload')),
            ],
          ),
          const SizedBox(height: 12),
          Text('Status: $_status'),
          const SizedBox(height: 12),
          if (_bannerId != null)
            Container(
              padding: const EdgeInsets.all(8),
              color: Colors.grey[200],
              child: BannerAdWidget(
                adUnitId: Platform.isAndroid ? AdTestIds.androidBanner : AdTestIds.iosBanner,
                size: _size,
                preloadedBannerId: _bannerId,
                onAdFailedToLoad: (_) {
                  if (!mounted) return;
                  setState(() => _status = 'Failed to render');
                },
              ),
            ),
        ],
      ),
    );
  }
}

// Native Ad Page
class NativeAdPage extends StatefulWidget {
  const NativeAdPage({super.key});

  @override
  State<NativeAdPage> createState() => _NativeAdPageState();
}

class _NativeAdPageState extends State<NativeAdPage> {
  bool _showNativeSlot = true; // hide slot on failure
  @override
  void initState() {
    super.initState();
    _initializeAds();
  }

  Future<void> _initializeAds() async {
    final appId = Platform.isAndroid
        ? AdTestIds.androidAppId
        : AdTestIds.iosAppId;

    await NativeGoogleads.instance.initialize(appId: appId);
  }

  @override
  Widget build(BuildContext context) {
    final adUnitId = Platform.isAndroid
        ? AdTestIds.androidNativeAdvanced
        : AdTestIds.iosNativeAdvanced;

    return Scaffold(
      appBar: AppBar(
        title: const Text('Native Ads'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 16.0),
        child: ListView.separated(
          itemCount: 12,
          separatorBuilder: (context, index) => const SizedBox(height: 16),
          itemBuilder: (context, index) {
            if (index == 5 && _showNativeSlot) {
              return Card(
                elevation: 4,
                child: ConstrainedBox(
                  constraints: const BoxConstraints(
                    minWidth: 300,
                    minHeight: 300,
                    maxHeight: 350,
                    maxWidth: 450,
                  ),
                  child: NativeAdWidget(
                    key: const ValueKey('native_ad'),
                    adUnitId: adUnitId,
                    backgroundColor: Colors.white,
                    onAdLoaded: () {
                      if (!mounted) return;
                      setState(() => _showNativeSlot = true);
                      debugPrint('Native ad loaded');
                      ScaffoldMessenger.of(context).showSnackBar(
                        const SnackBar(
                          content: Text('Native ad loaded successfully!'),
                          backgroundColor: Colors.green,
                          duration: Duration(seconds: 2),
                        ),
                      );
                    },
                    onAdFailedToLoad: (error) {
                      debugPrint('Native ad failed to load: $error');
                      if (!mounted) return;
                      setState(() => _showNativeSlot = false); // gracefully skip the slot
                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(
                          content: const Text('Native ad unavailable, skipping slot'),
                          backgroundColor: Colors.orange,
                        ),
                      );
                    },
                  ),
                ),
              );
            }

            // Normal Contents
            return Card(
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Contents Item ${index + 1}',
                      style: const TextStyle(
                        fontSize: 20,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    const SizedBox(height: 8),
                    Text(
                      'This is where the content for item ${index + 1} will be displayed.',
                      style: const TextStyle(fontSize: 14),
                    ),
                  ],
                ),
              ),
            );
          },
        ),
      ),
    );
  }
}

// Preloaded Native Page
class PreloadedNativePage extends StatefulWidget {
  const PreloadedNativePage({super.key});

  @override
  State<PreloadedNativePage> createState() => _PreloadedNativePageState();
}

class _PreloadedNativePageState extends State<PreloadedNativePage> {
  final NativeGoogleads _ads = NativeGoogleads.instance;
  String? _nativeId;
  String _status = 'Idle';

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

  Future<void> _initializeAds() async {
    await _ads.initialize(appId: Platform.isAndroid
        ? AdTestIds.androidAppId
        : AdTestIds.iosAppId);
  }

  Future<void> _preload() async {
    setState(() => _status = 'Loading...');
    final id = await _ads.loadNativeAd(
      adUnitId: Platform.isAndroid ? AdTestIds.androidNativeAdvanced : AdTestIds.iosNativeAdvanced,
    );
    setState(() {
      _nativeId = id;
      _status = id != null ? 'Ready' : 'Failed';
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Preloaded Native'), backgroundColor: Theme.of(context).colorScheme.inversePrimary),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          Row(
            children: [
              ElevatedButton.icon(onPressed: _preload, icon: const Icon(Icons.download), label: const Text('Preload')),
              const SizedBox(width: 12),
              Text('Status: $_status'),
            ],
          ),
          const SizedBox(height: 12),
          if (_nativeId != null)
            Card(
              elevation: 4,
              child: SizedBox(
                height: 300,
                child: NativeAdWidget(
                  adUnitId: Platform.isAndroid ? AdTestIds.androidNativeAdvanced : AdTestIds.iosNativeAdvanced,
                  preloadedNativeAdId: _nativeId,
                  backgroundColor: Colors.white,
                  onAdFailedToLoad: (_) {
                    if (!mounted) return;
                    setState(() => _status = 'Failed to render');
                  },
                ),
              ),
            ),
        ],
      ),
    );
  }
}

// Interstitial Ad Page
class InterstitialAdPage extends StatefulWidget {
  const InterstitialAdPage({super.key});

  @override
  State<InterstitialAdPage> createState() => _InterstitialAdPageState();
}

class _InterstitialAdPageState extends State<InterstitialAdPage> {
  final NativeGoogleads _ads = NativeGoogleads.instance;
  bool _isAdReady = false;
  String _status = 'Not initialized';

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

  Future<void> _initializeAds() async {
    setState(() {
      _status = 'Initializing...';
    });

    _ads.setAdCallbacks(
      onAdDismissed: (adType) {
        if (adType == 'interstitial') {
          setState(() {
            _isAdReady = false;
            _status = 'Ad dismissed';
          });
          // Auto-preload runs natively; refresh readiness shortly after
          Future<void>(() async {
            await Future.delayed(const Duration(milliseconds: 200));
            if (_interstitialAdUnitId != null) {
              final ready = await _ads.isInterstitialReady(
                _interstitialAdUnitId!,
              );
              if (!mounted) return;
              setState(() {
                _isAdReady = ready;
                if (ready) _status = 'Ready';
              });
            }
          });
        }
      },
      onAdShowed: (adType) {
        if (adType == 'interstitial') {
          setState(() {
            _status = 'Ad showing';
          });
        }
      },
      onAdFailedToShow: (adType, error) {
        if (adType == 'interstitial') {
          setState(() {
            _status = 'Failed to show: $error';
          });
          _showSnackBar('Failed to show ad: $error');
          // Try to reflect auto-preload status
          Future<void>(() async {
            await Future.delayed(const Duration(milliseconds: 200));
            if (_interstitialAdUnitId != null) {
              final ready = await _ads.isInterstitialReady(
                _interstitialAdUnitId!,
              );
              if (!mounted) return;
              setState(() {
                _isAdReady = ready;
                if (ready) _status = 'Ready';
              });
            }
          });
        }
      },
    );

    final appId = Platform.isAndroid
        ? AdTestIds.androidAppId
        : AdTestIds.iosAppId;

    final result = await _ads.initialize(appId: appId);

    if (result != null && result['isReady'] == true) {
      setState(() {
        _status = 'Initialized';
      });
    } else {
      setState(() {
        _status = 'Initialization failed';
      });
    }
  }

  String? _interstitialAdUnitId;

  Future<void> _preloadInterstitialAd() async {
    setState(() {
      _status = 'Loading ad...';
    });

    final adUnitId = Platform.isAndroid
        ? AdTestIds.androidInterstitial
        : AdTestIds.iosInterstitial;

    _interstitialAdUnitId = adUnitId;
    final success = await _ads.preloadInterstitialAd(adUnitId: adUnitId);

    setState(() {
      _isAdReady = success;
      _status = success ? 'Ad loaded' : 'Failed to load ad';
    });

    if (!success) {
      _showSnackBar('Failed to load interstitial ad');
    }
  }

  Future<void> _showInterstitialAd() async {
    if (!_isAdReady) {
      _showSnackBar('Interstitial ad is not ready');
      return;
    }

    final success = await _ads.showInterstitialAd(
      adUnitId: _interstitialAdUnitId!,
    );
    if (!success) {
      _showSnackBar('Failed to show interstitial ad');
    }
  }

  void _showSnackBar(String message) {
    ScaffoldMessenger.of(
      context,
    ).showSnackBar(SnackBar(content: Text(message)));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Interstitial Ads'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    children: [
                      Icon(
                        Icons.fullscreen,
                        size: 64,
                        color: Theme.of(context).colorScheme.primary,
                      ),
                      const SizedBox(height: 16),
                      const Text(
                        'Interstitial Ad',
                        style: TextStyle(
                          fontSize: 20,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      const SizedBox(height: 8),
                      Text(
                        'Status: $_status',
                        style: const TextStyle(fontSize: 14),
                      ),
                      const SizedBox(height: 8),
                      Container(
                        padding: const EdgeInsets.symmetric(
                          horizontal: 12,
                          vertical: 4,
                        ),
                        decoration: BoxDecoration(
                          color: _isAdReady ? Colors.green : Colors.red,
                          borderRadius: BorderRadius.circular(12),
                        ),
                        child: Text(
                          _isAdReady ? 'Ready' : 'Not Ready',
                          style: const TextStyle(color: Colors.white),
                        ),
                      ),
                      const SizedBox(height: 16),
                      Row(
                        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                        children: [
                          ElevatedButton.icon(
                            onPressed: _preloadInterstitialAd,
                            icon: const Icon(Icons.download),
                            label: const Text('Preload'),
                          ),
                          ElevatedButton.icon(
                            onPressed: _isAdReady ? _showInterstitialAd : null,
                            icon: const Icon(Icons.play_arrow),
                            label: const Text('Show Ad'),
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

// Rewarded Ad Page
class RewardedAdPage extends StatefulWidget {
  const RewardedAdPage({super.key});

  @override
  State<RewardedAdPage> createState() => _RewardedAdPageState();
}

// Preloaded Fullscreen Ads Page (Interstitial + Rewarded)
class PreloadedFullscreenAdsPage extends StatefulWidget {
  const PreloadedFullscreenAdsPage({super.key});

  @override
  State<PreloadedFullscreenAdsPage> createState() => _PreloadedFullscreenAdsPageState();
}

class _PreloadedFullscreenAdsPageState extends State<PreloadedFullscreenAdsPage> {
  final NativeGoogleads _ads = NativeGoogleads.instance;

  String? _interstitialId;
  String? _rewardedId;

  bool _interstitialReady = false;
  bool _rewardedReady = false;
  String _interstitialStatus = 'Idle';
  String _rewardedStatus = 'Idle';
  int _rewardedEarned = 0;

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

  Future<void> _setup() async {
    _ads.setAdCallbacks(
      onAdDismissed: (type) async {
        if (!mounted) return;
        if (type == 'interstitial') {
          setState(() {
            _interstitialReady = false;
            _interstitialStatus = 'Dismissed';
          });
          await Future.delayed(const Duration(milliseconds: 200));
          if (_interstitialId != null) {
            final ready = await _ads.isInterstitialReady(_interstitialId!);
            if (!mounted) return;
            setState(() {
              _interstitialReady = ready;
              if (ready) _interstitialStatus = 'Ready';
            });
          }
        } else if (type == 'rewarded') {
          setState(() {
            _rewardedReady = false;
            _rewardedStatus = 'Dismissed';
          });
          await Future.delayed(const Duration(milliseconds: 200));
          if (_rewardedId != null) {
            final ready = await _ads.isRewardedReady(_rewardedId!);
            if (!mounted) return;
            setState(() {
              _rewardedReady = ready;
              if (ready) _rewardedStatus = 'Ready';
            });
          }
        }
      },
      onAdShowed: (type) {
        if (!mounted) return;
        if (type == 'interstitial') {
          setState(() => _interstitialStatus = 'Showing');
        } else if (type == 'rewarded') {
          setState(() => _rewardedStatus = 'Showing');
        }
      },
      onAdFailedToShow: (type, error) async {
        if (!mounted) return;
        if (type == 'interstitial') {
          setState(() => _interstitialStatus = 'Failed: $error');
          await Future.delayed(const Duration(milliseconds: 200));
          if (_interstitialId != null) {
            final ready = await _ads.isInterstitialReady(_interstitialId!);
            if (!mounted) return;
            setState(() {
              _interstitialReady = ready;
              if (ready) _interstitialStatus = 'Ready';
            });
          }
        } else if (type == 'rewarded') {
          setState(() => _rewardedStatus = 'Failed: $error');
          await Future.delayed(const Duration(milliseconds: 200));
          if (_rewardedId != null) {
            final ready = await _ads.isRewardedReady(_rewardedId!);
            if (!mounted) return;
            setState(() {
              _rewardedReady = ready;
              if (ready) _rewardedStatus = 'Ready';
            });
          }
        }
      },
      onUserEarnedReward: (type, amount) {
        if (!mounted) return;
        setState(() {
          _rewardedEarned += amount;
        });
      },
    );

    // Initialize with test App ID and preload both
    await _ads.initialize(appId: Platform.isAndroid
        ? AdTestIds.androidAppId
        : AdTestIds.iosAppId);

    _interstitialId = Platform.isAndroid
        ? AdTestIds.androidInterstitial
        : AdTestIds.iosInterstitial;
    _rewardedId = Platform.isAndroid
        ? AdTestIds.androidRewarded
        : AdTestIds.iosRewarded;

    await _preloadInterstitial();
    await _preloadRewarded();
  }

  Future<void> _preloadInterstitial() async {
    if (_interstitialId == null) return;
    setState(() => _interstitialStatus = 'Loading...');
    final ok = await _ads.preloadInterstitialAd(adUnitId: _interstitialId!);
    if (!mounted) return;
    setState(() {
      _interstitialReady = ok;
      _interstitialStatus = ok ? 'Ready' : 'Failed to load';
    });
  }

  Future<void> _showInterstitial() async {
    if (!_interstitialReady || _interstitialId == null) return;
    final ok = await _ads.showInterstitialAd(adUnitId: _interstitialId!);
    if (!ok && mounted) {
      setState(() => _interstitialStatus = 'Failed to show');
    }
  }

  Future<void> _checkInterstitialReady() async {
    if (_interstitialId == null) return;
    final ready = await _ads.isInterstitialReady(_interstitialId!);
    if (!mounted) return;
    setState(() {
      _interstitialReady = ready;
      if (ready) _interstitialStatus = 'Ready';
    });
  }

  Future<void> _preloadRewarded() async {
    if (_rewardedId == null) return;
    setState(() => _rewardedStatus = 'Loading...');
    final ok = await _ads.preloadRewardedAd(adUnitId: _rewardedId!);
    if (!mounted) return;
    setState(() {
      _rewardedReady = ok;
      _rewardedStatus = ok ? 'Ready' : 'Failed to load';
    });
  }

  Future<void> _showRewarded() async {
    if (!_rewardedReady || _rewardedId == null) return;
    final ok = await _ads.showRewardedAd(adUnitId: _rewardedId!);
    if (!ok && mounted) {
      setState(() => _rewardedStatus = 'Failed to show');
    }
  }

  Future<void> _checkRewardedReady() async {
    if (_rewardedId == null) return;
    final ready = await _ads.isRewardedReady(_rewardedId!);
    if (!mounted) return;
    setState(() {
      _rewardedReady = ready;
      if (ready) _rewardedStatus = 'Ready';
    });
  }

  List<Widget> _actionButtons({
    required VoidCallback onPreload,
    required VoidCallback onRefresh,
    required VoidCallback onShow,
    required bool canShow,
  }) {
    return [
      ElevatedButton.icon(
        onPressed: onPreload,
        icon: const Icon(Icons.download),
        label: const Text('Preload'),
      ),
      ElevatedButton.icon(
        onPressed: onRefresh,
        icon: const Icon(Icons.refresh),
        label: const Text('Refresh Ready'),
      ),
      ElevatedButton.icon(
        onPressed: canShow ? onShow : null,
        icon: const Icon(Icons.play_arrow),
        label: const Text('Show'),
      ),
    ];
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Preloaded Fullscreen Ads'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Row(
                    children: [
                      Icon(Icons.fullscreen, color: Theme.of(context).colorScheme.primary),
                      const SizedBox(width: 8),
                      const Text('Interstitial', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                      const Spacer(),
                      StatusChip(ready: _interstitialReady),
                    ],
                  ),
                  const SizedBox(height: 8),
                  Text('Status: $_interstitialStatus'),
                  const SizedBox(height: 12),
                  Wrap(
                    alignment: WrapAlignment.center,
                    spacing: 8,
                    runSpacing: 8,
                    children: _actionButtons(
                      onPreload: _preloadInterstitial,
                      onRefresh: _checkInterstitialReady,
                      onShow: _showInterstitial,
                      canShow: _interstitialReady,
                    ),
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 16),
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Row(
                    children: [
                      Icon(Icons.card_giftcard, color: Theme.of(context).colorScheme.primary),
                      const SizedBox(width: 8),
                      const Text('Rewarded', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                      const Spacer(),
                      StatusChip(ready: _rewardedReady),
                    ],
                  ),
                  const SizedBox(height: 8),
                  Text('Status: $_rewardedStatus'),
                  const SizedBox(height: 4),
                  Text('Rewards earned: $_rewardedEarned'),
                  const SizedBox(height: 12),
                  Wrap(
                    alignment: WrapAlignment.center,
                    spacing: 8,
                    runSpacing: 8,
                    children: _actionButtons(
                      onPreload: _preloadRewarded,
                      onRefresh: _checkRewardedReady,
                      onShow: _showRewarded,
                      canShow: _rewardedReady,
                    ),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}


class _RewardedAdPageState extends State<RewardedAdPage> {
  final NativeGoogleads _ads = NativeGoogleads.instance;
  bool _isAdReady = false;
  String _status = 'Not initialized';
  int _rewardAmount = 0;

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

  Future<void> _initializeAds() async {
    setState(() {
      _status = 'Initializing...';
    });

    _ads.setAdCallbacks(
      onAdDismissed: (adType) {
        if (adType == 'rewarded') {
          setState(() {
            _isAdReady = false;
            _status = 'Ad dismissed';
          });
          // Auto-preload runs natively; refresh readiness shortly after
          Future<void>(() async {
            await Future.delayed(const Duration(milliseconds: 200));
            if (_rewardedAdUnitId != null) {
              final ready = await _ads.isRewardedReady(_rewardedAdUnitId!);
              if (!mounted) return;
              setState(() {
                _isAdReady = ready;
                if (ready) _status = 'Ready';
              });
            }
          });
        }
      },
      onAdShowed: (adType) {
        if (adType == 'rewarded') {
          setState(() {
            _status = 'Ad showing';
          });
        }
      },
      onAdFailedToShow: (adType, error) {
        if (adType == 'rewarded') {
          setState(() {
            _status = 'Failed to show: $error';
          });
          _showSnackBar('Failed to show ad: $error');
          // Try to reflect auto-preload status
          Future<void>(() async {
            await Future.delayed(const Duration(milliseconds: 200));
            if (_rewardedAdUnitId != null) {
              final ready = await _ads.isRewardedReady(_rewardedAdUnitId!);
              if (!mounted) return;
              setState(() {
                _isAdReady = ready;
                if (ready) _status = 'Ready';
              });
            }
          });
        }
      },
      onUserEarnedReward: (type, amount) {
        setState(() {
          _rewardAmount += amount;
          _status = 'Reward earned!';
        });
        _showSnackBar('You earned $amount $type!');
      },
    );

    final appId = Platform.isAndroid
        ? AdTestIds.androidAppId
        : AdTestIds.iosAppId;

    final result = await _ads.initialize(appId: appId);

    if (result != null && result['isReady'] == true) {
      setState(() {
        _status = 'Initialized';
      });
    } else {
      setState(() {
        _status = 'Initialization failed';
      });
    }
  }

  String? _rewardedAdUnitId;

  Future<void> _preloadRewardedAd() async {
    setState(() {
      _status = 'Loading ad...';
    });

    final adUnitId = Platform.isAndroid
        ? AdTestIds.androidRewarded
        : AdTestIds.iosRewarded;

    _rewardedAdUnitId = adUnitId;
    final success = await _ads.preloadRewardedAd(adUnitId: adUnitId);

    setState(() {
      _isAdReady = success;
      _status = success ? 'Ad loaded' : 'Failed to load ad';
    });

    if (!success) {
      _showSnackBar('Failed to load rewarded ad');
    }
  }

  Future<void> _showRewardedAd() async {
    if (!_isAdReady) {
      _showSnackBar('Rewarded ad is not ready');
      return;
    }

    final success = await _ads.showRewardedAd(adUnitId: _rewardedAdUnitId!);
    if (!success) {
      _showSnackBar('Failed to show rewarded ad');
    }
  }

  void _showSnackBar(String message) {
    ScaffoldMessenger.of(
      context,
    ).showSnackBar(SnackBar(content: Text(message)));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Rewarded Ads'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    children: [
                      Icon(
                        Icons.card_giftcard,
                        size: 64,
                        color: Theme.of(context).colorScheme.primary,
                      ),
                      const SizedBox(height: 16),
                      const Text(
                        'Rewarded Ad',
                        style: TextStyle(
                          fontSize: 20,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      const SizedBox(height: 8),
                      Text(
                        'Total Rewards: $_rewardAmount',
                        style: const TextStyle(
                          fontSize: 18,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      const SizedBox(height: 8),
                      Text(
                        'Status: $_status',
                        style: const TextStyle(fontSize: 14),
                      ),
                      const SizedBox(height: 8),
                      Container(
                        padding: const EdgeInsets.symmetric(
                          horizontal: 12,
                          vertical: 4,
                        ),
                        decoration: BoxDecoration(
                          color: _isAdReady ? Colors.green : Colors.red,
                          borderRadius: BorderRadius.circular(12),
                        ),
                        child: Text(
                          _isAdReady ? 'Ready' : 'Not Ready',
                          style: const TextStyle(color: Colors.white),
                        ),
                      ),
                      const SizedBox(height: 16),
                      Row(
                        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                        children: [
                          ElevatedButton.icon(
                            onPressed: _preloadRewardedAd,
                            icon: const Icon(Icons.download),
                            label: const Text('Preload'),
                          ),
                          ElevatedButton.icon(
                            onPressed: _isAdReady ? _showRewardedAd : null,
                            icon: const Icon(Icons.play_arrow),
                            label: const Text('Watch Ad'),
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
0
likes
150
points
40
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin for integrating Google Mobile Ads (AdMob) using native platform implementations. Supports interstitial and rewarded ads with comprehensive callbacks.

Homepage
Repository (GitHub)
View/report issues
Contributing

Topics

#ads #admob #google-ads #monetization #advertising

Documentation

Documentation
API reference

Funding

Consider supporting this project:

github.com

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on native_googleads

Packages that implement native_googleads