Flutter: AdMobインタースティシャル広告のload完了をawaitで待てない
結論:Completerをコールバック関数内に設置して完了を待たせる
2023/1/28 Flutter エラー・バグ日記
AdMobのインタースティシャル広告を表示するには、あらかじめloadメソッド(非同期・戻り値なしのFuture<void>型のメソッド)で、広告データをロード(読み込む)必要がある。
ロードが終わらずに、広告を表示させるshowメソッドを実行しても、広告は表示されないので、awaitでloadメソッドの完了を待ってから、次の処理を実行しようとした。
しかし、なぜかawaitが機能せず、ロードが終わる前に次の処理に進んでしまう。
バナー広告のloadメソッドは、awaitできちんとロード完了を待てるのだが、インタースティシャル広告では待つことができない。。
awaitが機能しないときのコード
loadメソッド部分のコードは、「google_mobile_ads」パッケージのExampleコード
を参考にして、以下のように記述していた(ロードメソッド部分のみ抜粋)。
// クラス名、メソッド名、プロパティ名(変数名)について、筆者が作成したもの(名前変更可のもの) // の名前の末尾には、大文字のオー「O」をつけています // ※ライブラリ(パッケージ)で予め決められているもの(名前の変更不可のもの)と、 // 自分で作成したもの(名前の変更可のもの)の区別をしやすくするため // インタースティシャル広告のデータを入れるプロパティ InterstitialAd? interstitialAdO; // インタースティシャル広告の試行最大回数と試行回数 int maxFailedToAttemptO = 3; int _numInterstitialLoadAttemptO = 0; // インタースティシャル広告のロード処理のために作成したメソッド Future<void> initInterstitialAdO() async{ // インタースティシャル広告のロードメソッド // ロード完了を待つために、awaitをつける await InterstitialAd.load( // ここではAndroid用のテスト広告IDを記載 adUnitId: "ca-app-pub-3940256099942544/1033173712", request: const AdRequest(), adLoadCallback: InterstitialAdLoadCallback( // ロード成功時のコールバック関数 onAdLoaded: (InterstitialAd adO) { // 広告データの代入 interstitialAdO = adO; // 成功したので、試行回数カウントを初期化する _numInterstitialLoadAttemptO = 0; debugPrint("インタースティシャルadロード完了"); }, // ロード失敗時のコールバック関数 onAdFailedToLoad: (LoadAdError errorO) { debugPrint("インタースティシャルadロードできず"); // 念のため広告データを初期化 interstitialAdO = null; // 失敗したので、3回まで試行するため、カウントを増やす _numInterstitialLoadAttemptO++; // 3回以内ならロードメソッドを再帰的に呼ぶ if (_numInterstitialLoadAttemptO <= maxFailedToAttemptO) { initInterstitialAdO(); } }, ), ); } // 以降、ロード完了後の処理 // ・・・・・・
Exampleのコードでは、async awaitは付いていないが、「interstitialAdO」に広告データが格納された後に、次の処理に進んでほしいので、「InterstitialAd.load」メソッドにasync awaitを付けておいた。
しかし、実行すると「interstitialAdO」がnullのままで(広告データが入る前に)、先に進んでしまった。。
「onAdLoaded:」のコールバック関数内に、ロード完了時のコメントをdebugPrintで仕込んだところ、次の処理の実行中にコメントが表示されたため、awaitが効いていないことが確認できた。
コールバック関数内の処理完了を待つには、Completerクラスを使う必要あり
調べてみると、全く同じ悩みに関するQA記事があった(大変助かりましたm(_ _)m)。
「Completer」というクラスのインスタンスを作った上で、loadメソッドの引数である「onAdLoaded:」と「onAdFailedToLoad:」の各コールバック関数内に、完了地点を設置すれば良いらしい。
loadメソッドに対してawaitを付けるだけだと、コールバック関数のように、間接的な位置にある非同期処理の終了までは、待てないらしい。
恥ずかしながら、「Completer」というクラスは初めて知った。。
こちらの記事で詳しく説明されており、大変勉強になった(ありがとうございますm(_ _)m)。
非同期処理を自作したいとき(まさに、コールバック関数からの非同期での返り値取得など)に用いるらしい。
「Completer」クラスのFlutter公式の説明は以下。
確かに、前述のQA記事を参考に、下記のとおりコードを修正したら、loadメソッド完了を待って、次の処理を実行できるようになった。
// 前述のコードから追加した部分のみ、コメントを記載 InterstitialAd? interstitialAdO; int maxFailedToAttemptO = 3; int _numInterstitialLoadAttemptO = 0; Future<void> initInterstitialAdO() async{ // 【追加】 Completer(戻り値なし)のインスタンス作成 var adCompleterO = Completer<void>(); await InterstitialAd.load( adUnitId: "ca-app-pub-3940256099942544/1033173712", request: const AdRequest(), adLoadCallback: InterstitialAdLoadCallback( onAdLoaded: (InterstitialAd adO) { interstitialAdO = adO; _numInterstitialLoadAttemptO = 0; debugPrint("インタースティシャルadロード完了"); // 【追加】 ロード成功時の完了地点の設置: ここまでの処理が終わったら完了とする adCompleterO.complete(); }, onAdFailedToLoad: (LoadAdError errorO) { debugPrint("インタースティシャルadロードできず"); interstitialAdO = null; _numInterstitialLoadAttemptO++; if (_numInterstitialLoadAttemptO <= maxFailedToAttemptO) { initInterstitialAdO(); } // 【追加】 ロード失敗時の完了地点の設置: エラーログを返す adCompleterO.completeError(errorO); }, ), ); // 【追加】 上記完了地点に到達した時点で、このメソッド全体を終了(voidのため戻り値はなし) await adCompleterO.future; } // 以降、ロード完了後の処理 // ・・・・・・
若干修正が必要:AdMob審査期間中は、loadできないと処理が完了しない
上記コードだと1点問題があり、広告データがloadできなかった場合に、処理が完了せず、アプリが止まってしまった。
アプリをリリースした直後は、AdMobの管理画面にストア情報を登録し、AdMob側で審査してもらう必要がある。
審査期間中は、広告が表示されないため、ロード失敗時の「onAdFailedToLoad:」に設定したコールバック関数が実行されるのだが、この処理が完了扱いにならず、次の処理に進まなくなっていた。
結論として、完了地点に設定した「completeError」メソッドを、ロード成功時の「complete」メソッドに変更したところ、広告がロードできない状況でも、次の処理に進むようになった。
最終的な修正後のコードは以下のとおり。
// 1つ前のコードから修正した部分のみ、コメントを記載 InterstitialAd? interstitialAdO; int maxFailedToAttemptO = 3; int _numInterstitialLoadAttemptO = 0; Future<void> initInterstitialAdO() async{ var adCompleterO = Completer<void>(); await InterstitialAd.load( adUnitId: "ca-app-pub-3940256099942544/1033173712", request: const AdRequest(), adLoadCallback: InterstitialAdLoadCallback( onAdLoaded: (InterstitialAd adO) { interstitialAdO = adO; _numInterstitialLoadAttemptO = 0; debugPrint("インタースティシャルadロード完了"); adCompleterO.complete(); }, onAdFailedToLoad: (LoadAdError errorO) { debugPrint("インタースティシャルadロードできず"); interstitialAdO = null; _numInterstitialLoadAttemptO++; if (_numInterstitialLoadAttemptO <= maxFailedToAttemptO) { initInterstitialAdO(); } // adCompleterO.completeError(errorO); // 【修正】 以下に修正 adCompleterO.complete(); }, ), ); await adCompleterO.future; } // 以降、ロード完了後の処理 // ・・・・・・
AdMobの審査承認後、「onAdFailedToLoad:」の完了設定を「completeError」メソッドに戻し、あえてネットワークの接続を切って実験すると、アプリは停止せず、次の処理に進むことができた。
十分検証しきれていないが、AdMob審査中の状況だと「completeError」メソッドがうまく動作しないのかもしれない。。(間違っていそうですが、、m(_ _)m)
エラーログが無くても、「interstitialAdO」がnullの場合に、showメソッドを発動しないよう制御できるので、とりあえずエラーログが不要なら、ロード失敗時の完了設定も「complete」メソッドにしておく方が無難、というのが現状の理解。
\一般的なエラー対処法をまとめた記事はこちら/
リリースしたアプリ(全てFlutterで開発)
個人アプリ開発で役立ったもの
おすすめの学習教材
\超初心者向けでオススメな元Udemyの講座/
\キャンペーン時を狙えば安価で網羅的な内容が学べる(日本語訳あり)/
\Gitの基礎について無料で学べる/
おすすめの学習書籍
\実用的。image_pickerに関してかなり助けられた/
\Dartの基礎文法を素早くインプットできる/