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の基礎文法を素早くインプットできる/








