📖

AdMob のバナー広告を Riverpod で管理する方法

2025/02/25に公開

AdMob と Riverpod の概要

AdMob はアプリの広告プラットフォームで、バナー広告は画面上部または下部に表示されます。
様々な画面に出現します。初期化などの扱いを間違えると例外をはいてしまったりして適切な配信が行われないというようなことがありました。
そこで Riverpod を用いて適切に管理することで再利用可能で保守性の高いコードにすることができます!
この記事ではそんなやり方を紹介していきます。

前提条件

  • Flutter プロジェクトがすでに設定されていること
  • AdMob アカウントがあり、バナー広告の広告ユニット ID を取得済みであること
  • 必要なパッケージのインストール:
flutter pub add google_mobile_ads
flutter pub add flutter_riverpod

実装手順

1. AdState クラスと StateNotifier の定義

広告の状態を管理するために、AdState クラスと Riverpod の StateNotifier を作成します:

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';

enum AdStatus { initial, loading, loaded, failed }

class AdState {
  final AdStatus status;
  final BannerAd? ad;
  final String? errorMessage;

  AdState({
    this.status = AdStatus.initial,
    this.ad,
    this.errorMessage,
  });

  AdState copyWith({
    AdStatus? status,
    BannerAd? ad,
    String? errorMessage,
  }) {
    return AdState(
      status: status ?? this.status,
      ad: ad ?? this.ad,
      errorMessage: errorMessage ?? this.errorMessage,
    );
  }
}

class AdStateNotifier extends StateNotifier<AdState> {
  AdStateNotifier() : super(AdState());

  Future<void> loadAd(String adUnitId) async {
    state = state.copyWith(status: AdStatus.loading);

    try {
      final bannerAd = BannerAd(
        adUnitId: adUnitId,
        size: AdSize.banner,
        request: const AdRequest(),
        listener: BannerAdListener(
          onAdLoaded: (ad) {
            state = state.copyWith(
              status: AdStatus.loaded,
              ad: ad as BannerAd,
            );
          },
          onAdFailedToLoad: (ad, error) {
            ad.dispose();
            state = state.copyWith(
              status: AdStatus.failed,
              errorMessage: error.message,
            );
          },
        ),
      );

      await bannerAd.load();
    } catch (e) {
      state = state.copyWith(
        status: AdStatus.failed,
        errorMessage: e.toString(),
      );
    }
  }

  void disposeAd() {
    if (state.ad != null) {
      state.ad!.dispose();
      state = state.copyWith(
        status: AdStatus.initial,
        ad: null,
      );
    }
  }
}

final adStateProvider = StateNotifierProvider<AdStateNotifier, AdState>((ref) {
  return AdStateNotifier();
});

2. BannerAdWidget の作成

ConsumerStatefulWidget を使用して、広告のライフサイクルを適切に管理します:

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';

class BannerAdWidget extends ConsumerStatefulWidget {
  final String adUnitId;

  const BannerAdWidget({
    Key? key,
    required this.adUnitId,
  }) : super(key: key);

  
  ConsumerState<BannerAdWidget> createState() => _BannerAdWidgetState();
}

class _BannerAdWidgetState extends ConsumerState<BannerAdWidget> {
  
  void initState() {
    super.initState();
    // 広告の読み込みを開始
    ref.read(adStateProvider.notifier).loadAd(widget.adUnitId);
  }

  
  void dispose() {
    // ウィジェットが破棄されるときに広告も破棄
    ref.read(adStateProvider.notifier).disposeAd();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    final adState = ref.watch(adStateProvider);

    switch (adState.status) {
      case AdStatus.initial:
      case AdStatus.loading:
        return const SizedBox(
          height: 50, // バナー広告の一般的な高さ
          child: Center(child: CircularProgressIndicator()),
        );

      case AdStatus.loaded:
        if (adState.ad == null) return const SizedBox.shrink();

        return Container(
          width: adState.ad!.size.width.toDouble(),
          height: adState.ad!.size.height.toDouble(),
          alignment: Alignment.center,
          child: AdWidget(ad: adState.ad!),
        );

      case AdStatus.failed:
        // 開発時のみエラーを表示し、本番環境では何も表示しない
        return SizedBox(
          height: 50,
          child: Center(
            child: Text(
              'Ad failed to load: ${adState.errorMessage ?? "Unknown error"}',
              style: const TextStyle(color: Colors.red),
            ),
          ),
        );
    }
  }
}

3. アプリへの統合

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';

void main() {
  // AdMob の初期化
  WidgetsFlutterBinding.ensureInitialized();
  MobileAds.instance.initialize();

  runApp(
    const ProviderScope(
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'AdMob with Riverpod',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('AdMob Example'),
      ),
      body: Column(
        children: [
          Expanded(
            child: Center(
              child: Text(
                'アプリのコンテンツ',
                style: Theme.of(context).textTheme.headlineMedium,
              ),
            ),
          ),
          // 広告を表示
          BannerAdWidget(
            adUnitId: _getAdUnitId(),
          ),
        ],
      ),
    );
  }

  // テスト用と本番用の広告ユニットIDを切り替える
  // 便宜上main.dart においているが適宜他のファイルに設定しておくのが良さそう
  String _getAdUnitId() {
    // デバッグモードではテスト広告を表示
    if (kDebugMode) { // kDebugMode などで判定する
      return 'ca-app-pub-3940256099942544/6300978111'; // テスト用ID
    } else {
      return 'YOUR_PRODUCTION_AD_UNIT_ID'; // 本番用ID
    }
  }
}

おまけ:広告サイズのバリエーション

AdMob では様々な広告サイズが提供されています:

サイズ名 寸法 (px) 説明
banner 320x50 スマートフォン向けの標準バナー
largeBanner 320x100 より大きなバナー
mediumRectangle 300x250 記事内などに配置する大型広告
fullBanner 468x60 タブレット向けの横長バナー
leaderboard 728x90 大画面デバイス向けの横長バナー
adaptiveBanner 可変 デバイスの画面幅に合わせて調整

使用例:

BannerAd(
  adUnitId: adUnitId,
  size: AdSize.mediumRectangle, // 300x250 の広告
  request: const AdRequest(),
  listener: BannerAdListener(...),
)

まとめ

Riverpod を使用して AdMob のバナー広告を管理することで楽々実装&使い回しが容易になります!

参考資料

宣伝

実際に AdMob を駆使してマネタイズする具体的な手法について、有料本ですが月 10 万を超える!個人開発 ×AdMob 攻略ガイドでも書いてます!よかったら御覧ください

Discussion