0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Flutterで広告を貼る備忘録(admob)

Last updated at Posted at 2022-09-02

flutterで開発したアプリに広告を貼る方法をメモしておく。
勉強中の備忘録なのでかなり雑な文章だと思うし、間違えもあると思ういます。なんなら指摘して欲しいです。

環境とか

バージョン
Flutter 3.3.0
Dart 2.18.0
google_mobile_ads 2.0.1
provider 6.0.3

勉強方法 

とりあえずYouTubeでFlutter公式の動画を見る。

おじさんの顔が終始愛らしくていい感じ。

実際の手順 

こっから↑の動画の内容をただただ書き綴っていく(?)

1 Google AdMobの設定

アカウント登録 

↑これはやってたので省略。。。
https://admob.google.com/home/?utm_source=sem&utm_medium=text&utm_campaign=2022-admob-apac-jp&utm_content=rsa&gclid=EAIaIQobChMIuZvWrZ72-QIVFnmLCh3TmgY9EAAYASAAEgKAtfD_BwE&gclsrc=aw.ds

アプリの登録

左のappsタブからadd appを選択

androidかiosを選択(後でもう片方も作るので適当でいい)

「アプリストアに登録してる?」的な項目はNO(開発中なので)

作ったものを選択してADD AD UNIT

適当なフォーマットを選ぶ
今回は動画と同じようにするてめbanneを選ぶ

適当なAd unit nameを決めてcreate
動画内では「A fun unit」なのでまじでなんでもいい

2 Flutterでadmobの設定

google_mobile_adsを追加

バージョンは多分https://pub.dev/packages/google_mobile_ads から適当にコピペしてきたもので大丈夫。
google_mobile_ads: ^2.0.1をルートディレクトリのpubspeck.yamlに追加する。

pubspeck.yaml
dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^1.0.2

  google_mobile_ads: ^2.0.1

app idを設定

android用とios用で別々の場所に記載する必要があるから注意!!

android

android > app > src > main > AndroidManifest.xmlのapplication内の最後に<meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="{app id}" />を追記

AndroidManifest.xml
        </activity>
        <!-- Don't delete the meta-data below.
             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
        <meta-data
            android:name="com.google.android.gms.ads.APPLICATION_ID"
            android:value="ca-app-pub-0000000000~0000000000"  />
</application>

ios

iosフォルダを右クリックしてxcodeで開く(vscodeなら)

Runner > infoを開いて右クリックでadd row

key => GADApplicationIdentifier
type => String
value => ios用のapp id

3 広告を貼る(テスト用)

initializeする

とりあえずmain.dartにgoogle_mobile_adsをインポート

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


main関数内でイニシャライズ

main.dart
void main() {
  WidgetsFlutterBinding.ensureInitialized();
  final initFuture = MobileAds.instance.initialize();
  runApp(const MyApp());
}

WidgetsFlutterBinding.ensureInitialized();を入れると、runApp()の前に呼び出すとランタイムエラーを回避できるらしい。。。
よくわかんないけどいい感じっぽいのでとりあえずやっとく。 

lib直下にad_state.dartを作ってAdStateクラスを定義する。

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

class AdState {
  Future<InitializationStatus> initialization;

  AdState(this.initialization);

  String get bannerAdUnitId => Platform.isAndroid
      ? "ca-app-pub-3940256099942544/6300978111"
      : "ca-app-pub-3940256099942544/2934735716";
}

とりあえずテスト広告ユニットを使う
android→https://developers.google.com/admob/android/test-ads?hl=ja
ios→https://developers.google.com/admob/ios/test-ads?hl=ja
ここでも愛らしいおじさんが動画で解説してくれる。うれしい。

main関数に戻っていろいろ追記
(ここでproviderをpubspeckに追加しなきゃいけない。なんか多分言及されていない?)

main.dart
import 'package:flutter/material.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'ad_state.dart'; // add
import 'package:provider/provider.dart'; // add

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  final initFuture = MobileAds.instance.initialize();
  final adState = AdState(initFuture); // add
  runApp(
    Provider.value(
      // add
      value: adState, // add
      builder: (context, child) => const MyApp(), // add
    ),
  );
}

(ここでcocoapodsのなんちゃらが古すぎるからなんちゃらって言われてflutter runできなかったので、言われるがままにpod repo updateした。なんとかなった。)

広告を貼るスペースを確保する

動画に則って進めるためにいい感じにリストビューを表示するスクリーン用のファイルを作った(list_screen.dart)
広告用のスペースはColumn > childern > Expandedの並列のとこに置いとくといい感じ。
SiedBoxの部分。

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

class ListScreen extends StatefulWidget {
  const ListScreen({super.key});

  @override
  State<ListScreen> createState() => _ListScreenState();
}

class _ListScreenState extends State<ListScreen> {
  Widget _tile(int index) {
    return Container(
      decoration: const BoxDecoration(
        border: Border(
          bottom: BorderSide(
            width: 1.0,
            color: Colors.grey,
          ),
        ),
      ),
      child: ListTile(
        title: Text(
          "item $index",
          style: const TextStyle(
            fontSize: 20.0,
            color: Colors.black,
          ),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("admob sample"),
      ),
      body: Column(
        children: <Widget>[
          Expanded(
            child: ListView.builder(
              itemBuilder: (context, index) => _tile(index + 1),
              itemCount: 100,
            ),
          ),
          const SizedBox(height: 50),
        ],
      ),
    );
  }
}

横320*縦50が一般的でちょうどいいらしい(ま?)

adListenerを追加

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

class AdState {
  Future<InitializationStatus> initialization;

  AdState(this.initialization);

  String get bannerAdUnitId => Platform.isAndroid
      ? "ca-app-pub-3940256099942544/6300978111"
      : "ca-app-pub-3940256099942544/2934735716";

  BannerAdListener get adListener => _adListener;

  BannerAdListener _adListener = BannerAdListener(
    onAdLoaded: (ad) => print('Ad loaded: ${ad.adUnitId}'),
    onAdClosed: (ad) => print('Ad closed: ${ad.adUnitId}'),
    onAdFailedToLoad: (ad, error) =>
        print('Ad failed to load ${ad.adUnitId}, $error'),
    onAdOpened: (ad) => print('Ad opened ${ad.adUnitId}'),
  );
}

動画ではAdListenerだけどないって言われる。代わりにBannerAdListenerにするといいっぽい。
この記事を参考にした。
ここら辺は普通に意味わかってない。

AdStateクラスの中でBannerAdListener初期化するのやめた(追記 9/13)

この記事(上のと同じやつ)に則って、didChangeDependencies()の中のbanner = BannerAd()のlistenerにBannerAdListener()を直接渡す?(表現が合ってるのかわからん)

list_screen.dart
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    final adState = Provider.of<AdState>(context);
    adState.initialization.then((status) {
      setState(() {
        banner = BannerAd(
          adUnitId: adState.bannerAdUnitId,
          size: AdSize.banner,
          request: const AdRequest(),
          listener: BannerAdListener(
            onAdLoaded: (ad) {
              print('${ad.adUnitId} loaded');
              setState(() {
                _hasBanner = true;
              });
            },
            onAdFailedToLoad: (ad, error) {
              print('${ad.adUnitId} failed; $error');
              setState(() {
                _hasBanner = false;
              });
            },
            onAdOpened: (ad) => print('${ad.adUnitId} opend'),
            onAdClosed: (ad) => print('${ad.adUnitId} closed'),
          ),
        )..load();
      });
      // _hasBanner = true;
    });
  }

↓理由
setState()の中で_hasBannerの値を変更したかった。
ProviderとChangeNotifierを使ったり、then()とかwhenComplete()でチェーンしようとしたけど何かダメだった。ダメだった理由はいまいち理解できてないからいつか理解したい。

↓とりあえずそれっぽい考察

then() or whenComplete()

load()とかsetState(() {//...いろいろ})にチェーンでできないかと思ったがダメだった。
AdStateクラスの中のonAdLoad()とonAdFailedToLoad()が実行されるのが遅いため、then()とかwhenComplete()の中身の方が先に実行されてしまうっぽい?
いや、こいつらって処理が全部終わらないと実行されないんじゃないの?ってめっちゃ思った。
おそらく、banner = BannerAd()自体は終わるからんなんでしょう。わからん。。。
Futureで解決できんじゃね?とか思ったけど理解が浅いので保留したら解決してた。

Provider & ChangeNotifier

AdStateクラス内のonAdLoad()onAdFailedToLoad()の中で状態の変化を検知できればいいんじゃないかと考えたけど、contextを渡せなくて詰んだ。そもそもWidgetじゃないから無理?
工夫すればできたのかも知れないけど、これも理解が浅いのでがんばれなかった。次は頑張れるようになっていたい。

広告をロードする

_ListScreenStateの中に追加

list_screen.dart
class _ListScreenState extends State<ListScreen> {
  late BannerAd banner; // add

  @override
  void didChangeDependencies() { // add
    super.didChangeDependencies();
    final adState = Provider.of<AdState>(context);
    adState.initialization.then((status) {
      setState(() {
        banner = BannerAd(
          adUnitId: adState.bannerAdUnitId,
          size: AdSize.banner,
          request: const AdRequest(),
          listener: adState.adListener,
        )..load();
      });
    });
  }

広告を表示する

動画のままだと一瞬エラーが出てだるいので少し変更を加える
bool _hasBannerを用意して切り替えを行う

list_screen.dart
import 'package:flutter/material.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:provider/provider.dart';
import 'ad_state.dart';

class ListScreen extends StatefulWidget {
  const ListScreen({super.key});

  @override
  State<ListScreen> createState() => _ListScreenState();
}

class _ListScreenState extends State<ListScreen> {
  late BannerAd banner;
  bool _hasBanner = false; // add

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    final adState = Provider.of<AdState>(context);
    adState.initialization.then((status) {
      setState(() {
        banner = BannerAd(
          adUnitId: adState.bannerAdUnitId,
          size: AdSize.banner,
          request: const AdRequest(),
          listener: adState.adListener,
        )..load();
      });
      _hasBanner = true; // add
    });
  }

  Widget _tile(int index) {
    return Container(
      decoration: const BoxDecoration(
        border: Border(
          bottom: BorderSide(
            width: 1.0,
            color: Colors.grey,
          ),
        ),
      ),
      child: ListTile(
        title: Text(
          "item $index",
          style: const TextStyle(
            fontSize: 20.0,
            color: Colors.black,
          ),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("admob sample"),
      ),
      body: Column(
        children: <Widget>[
          Expanded(
            child: ListView.builder(
              itemBuilder: (context, index) => _tile(index + 1),
              itemCount: 100,
            ),
          ),
          if (_hasBanner == false) // change
            const SizedBox(height: 50)
          else
            Container(
              height: 50,
              child: AdWidget(ad: banner),
            ),
        ],
      ),
    );
  }
}

動画の愛くるしいおじさんはif (banner == null)で切り替えてるけど、そもそもnull-safety(?)でwarning出るし、画面が一瞬エラーになる。

4 本番用の広告

本番用の広告を貼ろうとしたら、

Publisher data not found

というエラーを吐かれてロードできなかった。 
admobのヘルプを読むと

お支払い情報に未入力の箇所がないか、その他に設定上の問題がないかなど、アカウントのエラーを確認する

とあるので、支払い情報を設定するといけるかも?
他にもadmobの設定してから1時間ほどお待ちくださいっていうのがあったけど、余裕で超えてるので多分これは違う。

支払い情報設定後(追記 9/13)

情報を入力してもダメだった

Request Error: No ad to show.

というエラーが出る。
軽く調べた感じ、本番用の広告はストアに登録してるアプリじゃないとロードできないっぽい?
シミュレーターとか、テスト用に登録したデバイスでは本番用のユニットを使ってもテスト広告を流してくれる的なことがどっかに書いてあった気がする、仕様に変更があった?
理由はよくわからなかった(というかそこまで調べる気力が湧かなかった)けど、とりあえずデバッグでは現状確認できなかった。

余談

リストの途中にちょいちょい広告を挟む方法が最後に紹介されているけど、面倒だったのでスキップした。
この記事の人は公式のドキュメントを読んでやったらしい。記事内でも言われてるけど、バージョンアップの関係(?)で時々そのまま書いてもダメなところがある。公式大事。動画も公式だけど。
プログラムを書きながらこの記事を書いてるので、修正し忘れてるところとかあるかも。がんばれ未来の自分。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?