🚀

【Flutter】Admobの導入(Provider・Null-Safty版)

2022/03/16に公開

公式ガイド

Flutter公式のgoogle_mobile_adsプラグインの構築手順になります。
※admob_flutterの導入手順ではありません。
https://flutter.dev/ads

公式のガイドはこちら
https://developers.google.cn/admob/flutter/quick-start?hl=ja

前提確認

Flutterバージョン確認

・Flutter 1.22.0 以降であること
以下コマンドを実行し確認

% flutter --version
Flutter 2.0.5 • channel stable • https://github.com/flutter/flutter.git
Framework • revision adc687823a (7 months ago)2021-04-16 09:40:20 -0700
Engine • revision b09f014e96
Tools • Dart 2.12.3

Android Studioのバージョン確認

・Android Studio 3.2 以降
左上の「Android Studio」のタブから「About Android Studio」を選択し確認

Android用設定確認

・minSdkVersionを19以降に設定していること
・compileSdkVersionを28以降に設定していること
アプリレベルのbuild.gradleの以下の箇所を確認

android {
    compileSdkVersion 30

    sourceSets {
        main.java.srcDirs += 'src/main/kotlin'
    }

    defaultConfig {
        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
        applicationId ""
        minSdkVersion 21
        targetSdkVersion 30
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
    }

Admobのプロジェクト作成

公式サイトにアクセスしてホームから「アプリの追加」を行う
https://admob.google.com/home/

プロジェクトの作成(Android・IOS共通)

以下手順をAndroid・IOSの2回分行う
①プラットフォームの設定
以下のように設定
※アプリがリリース済みの場合はストアに登録済みと設定

②アプリ名の設定
「アプリを追加」で設定は完了
※アプリ名はAndroid・IOSで同じにして構いません

③広告ユニットの設定
広告ユニットの作成をクリックし、作成したい広告ユニットを選択

④バーナー名を入力
※広告ユニット名はAndroid・IOSで同じにして構いません
   ただし、プラットフォームごとに一意になるよう設定しましょう

⑤IDを控える
表示されたアプリIDと広告ユニットIDは後ほど使用するので控えておきましょう。
※後から確認も可能です。

Flutterプロジェクトの修正

Android用設定

「/android/app/src/main/AndroidManifest.xml」ファイルの
「application」タグ配下に「meta-data」タグを追加
※「android:value」には先ほど取得したAndroid用のアプリIDを設定する

<manifest>
    <application>
        〜〜〜
        <meta-data
            android:name="com.google.android.gms.ads.APPLICATION_ID"
            android:value="ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy"/>
    <application>
<manifest>

iOS用設定

「ios/Runner/Info.plist」ファイルに以下定義を追加
※「string」には先ほど取得したiOS用のアプリIDを設定する

<key>GADApplicationIdentifier</key>
<string>ca-app-pub-################~##########</string>

pubspec.yamlの編集

以下サイトを参考にプラグインの追加を行う
https://pub.dev/packages/google_mobile_ads/install

Admobの初期化処理(動作確認)

main.dartのmain関数内に以下の処理を追加する
実行し動作確認を行う。

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

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  MobileAds.instance.initialize();

  runApp(MyApp());
}

広告表示実装(Model層)

公式のサンプルソースは以下を参照
https://pub.dev/packages/google_mobile_ads/example

広告表示用のModelクラスの作成

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

class AdManager {
  BannerAd? bannerAd;
  InterstitialAd? interstitialAd;
}

広告ユニットID取得用のメソッドを定義

IOSとAndroidで使用する広告ユニットIDが異なるため、上記で作成したAdManager内にPlatformを判別し、IDを判別するための関数を定義する。
※テストの段階ではテスト広告用のIDを使用してください。
Android
https://developers.google.com/admob/android/test-ads#demo_ad_units
IOS
https://developers.google.com/admob/ios/test-ads#demo_ad_units

  String get bannerAdUnitId {
    if (Platform.isAndroid) {
      //テスト広告
      return "ca-app-pub-3940256099942544/6300978111";
    } else if (Platform.isIOS) {
      //テスト広告
      return "ca-app-pub-3940256099942544/2934735716";
    } else {
      throw new UnsupportedError("Unsupported platform");
    }
  }

  String get interstitialAdUnitId {
    if (Platform.isAndroid) {
      //テスト広告
      return "ca-app-pub-3940256099942544/1033173712";
    } else if (Platform.isIOS) {
      //テスト広告
      return"ca-app-pub-3940256099942544/4411468910";
    } else {
      throw new UnsupportedError("Unsupported platform");
    }
  }

Admob・広告ユニット初期化用のメソッドを定義する

上記で作成したAdManager内にAdmobの初期化処理(動作確認)にて作成したAdmobの初期化処理(動作確認)を一部、AdManagerクラスに処理を移譲する。
また、Banner広告とInterstitial広告の初期化処理を定義する。
※Banner広告はAdSizeのメソッドで表示するサイズを調整可能
https://developers.google.com/admob/flutter/banner#banner_sizes
※adLoadCallbackメソッドを定義することで、初期化のイベントをトリガーに行いたい処理を定義できる。

  Future<void> initAdmob() {
    return MobileAds.instance.initialize();
  }

  void initBannerAd() {
    bannerAd = BannerAd(
        adUnitId: bannerAdUnitId,
        size: AdSize.fullBanner,
        request: AdRequest(),
        listener: BannerAdListener());
  }

  void initInterstitialAd() {
    InterstitialAd.load(
        adUnitId: interstitialAdUnitId,
        request: AdRequest(),
        adLoadCallback: InterstitialAdLoadCallback(
            onAdLoaded: (InterstitialAd ad){
              interstitialAd = ad;
              _numInterstitialAdLoadAttempt = 0;
            },
            onAdFailedToLoad: (LoadAdError error){
              print('InterstitialAd failed to load: $error');
              interstitialAd = null;
              _numInterstitialAdLoadAttempt++;
              if (maxFailedToAttempt >= _numInterstitialAdLoadAttempt) initInterstitialAd();
            },
        ),);
  }

広告ユニットの読み込み処理を定義

Banner広告とInterstitial広告の読み込みと破棄の処理を定義する。
これらの関数はView層でProvider経由で呼び出される。

  void loadBannerAd() {
    bannerAd?.load();
  }

  void dispose() {
    bannerAd?.dispose();
  }

  void loadInterstitialAd() {
    if (interstitialAd == null) return;

    interstitialAd!.fullScreenContentCallback = FullScreenContentCallback(
      onAdDismissedFullScreenContent: (InterstitialAd ad) {
        ad.dispose();
        initInterstitialAd();
      },
      onAdFailedToShowFullScreenContent: (InterstitialAd ad, AdError error) {
        ad.dispose();
        initInterstitialAd();
      },
    );

    interstitialAd!.show();
    interstitialAd = null;
  }

作成したModel層の全文

import 'dart:io';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:{AdManegerクラスのパス}/ad_manager.dart';

class AdManager {
  BannerAd? bannerAd;
  InterstitialAd? interstitialAd;

  bool isInterstitialAdReady = false;
  int maxFailedToAttempt = 3;
  int _numInterstitialAdLoadAttempt = 0;

  Future<void> initAdmob() {
    return MobileAds.instance.initialize();
  }

  void initBannerAd() {
    bannerAd = BannerAd(
        adUnitId: bannerAdUnitId,
        size: AdSize.fullBanner,
        request: AdRequest(),
        listener: BannerAdListener());
  }

  void initInterstitialAd() {
    InterstitialAd.load(
      adUnitId: interstitialAdUnitId,
      request: AdRequest(),
      adLoadCallback: InterstitialAdLoadCallback(
        onAdLoaded: (InterstitialAd ad) {
          interstitialAd = ad;
          _numInterstitialAdLoadAttempt = 0;
        },
        onAdFailedToLoad: (LoadAdError error) {
          print('InterstitialAd failed to load: $error');
          interstitialAd = null;
          _numInterstitialAdLoadAttempt++;
          if (maxFailedToAttempt >= _numInterstitialAdLoadAttempt)
            initInterstitialAd();
        },
      ),
    );
  }

  static String get appId {
    if (Platform.isAndroid) {
      return "ca-app-pub-################~##########";
    } else if (Platform.isIOS) {
      return "ca-app-pub-################~##########";
    } else {
      throw new UnsupportedError("Unsupported platform");
    }
  }

  String get bannerAdUnitId {
    if (Platform.isAndroid) {
      //テスト広告
      return "ca-app-pub-3940256099942544/6300978111";
    } else if (Platform.isIOS) {
      //テスト広告
      return "ca-app-pub-3940256099942544/2934735716";
    } else {
      throw new UnsupportedError("Unsupported platform");
    }
  }

  String get interstitialAdUnitId {
    if (Platform.isAndroid) {
      //テスト広告
      return "ca-app-pub-3940256099942544/1033173712";
    } else if (Platform.isIOS) {
      //テスト広告
      return"ca-app-pub-3940256099942544/4411468910";
    } else {
      throw new UnsupportedError("Unsupported platform");
    }
  }

  void loadBannerAd() {
    bannerAd?.load();
  }

  void dispose() {
    bannerAd?.dispose();
  }

  void loadInterstitialAd() {
    if (interstitialAd == null) return;

    interstitialAd!.fullScreenContentCallback = FullScreenContentCallback(
      onAdDismissedFullScreenContent: (InterstitialAd ad) {
        ad.dispose();
        initInterstitialAd();
      },
      onAdFailedToShowFullScreenContent: (InterstitialAd ad, AdError error) {
        ad.dispose();
        initInterstitialAd();
      },
    );

    interstitialAd!.show();
    interstitialAd = null;
  }
}

ViewModel層の作成

コンストラクターでAdManagerを受け取り、各種初期化処理を行う。
また、広告のloadと破棄を行う関数を定義する。

import 'package:flutter/material.dart';
class ViewModel extends ChangeNotifier {
  AdManager adManager;

  ViewModel(this.adManager) {
  adManager
  ..initAdmob()
  ..initBannerAd()
  ..initInterstitialAd();
  }

  void loadBannerAd() {
    adManager.loadBannerAd();
  }

  void loadInterstitialAd() {
    adManager.loadInterstitialAd();
  }

  void disposeAd() {
    adManager.dispose();
  }

Main関数の修正

今回はProviderライブラリを使用して、ViewModel層を作成する。
導入未済の場合は以下サイトを参考にpubspec.yamlを編集する。
https://pub.dev/packages/provider

※このタイミングで動作確認をするのがおすすめ

import 'package:{viewmodelクラスのパス}/viewmodel.dart';
import 'package:provider/provider.dart';
import 'package:flutter/material.dart';

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

  runApp(ChangeNotifierProvider<ViewModel>(
    create: (_) => ViewModel(prefs,AdManager()),
    child: MyApp(),
  ));
}

Banner広告の表示

Banner広告の読み込み

build関数直下など、画面表示処理の前にViewModelに定義したloadBannerAdを呼び出す。

  
  Widget build(BuildContext context) {
    context.select((ViewModel avm) => avm.loadBannerAd());
    return Scaffold(

Banner広告の表示

以下関数を定義し、好みの場所で広告配置を行う。

  Widget _footerAdContent(BuildContext context) {
    var bannerAd = context.select((ViewModel avm) => avm.adManager.bannerAd);
    return bannerAd == null
        ? Container()
        : Center(
            child: Container(
              width: bannerAd.size.width.toDouble(),
              height: bannerAd.size.height.toDouble(),
              child: AdWidget(
                ad: bannerAd,
              ),
            ),
          );
  }

Interstitial広告の表示

戻る系のボタンなどのクリックイベントに合わせて、以下のように画面遷移前にViewModel層に定義した、loadInterstitialAdメソッドを呼び出しましょう。

onPressed: () {
   context.read<AppViewModel>().loadInterstitialAd(null);
   Navigator.of(context).pop(1);
},

Discussion