FlutterにAdMob広告をいれたいなと思い、
いろいろ調べたときの備忘録(*´ω`*)
google_mobile_ads | Flutter packageを使えばいいっぽい
ドキュメントやチュートリアルも用意されている
広告ユニットは作成済みの想定
前提条件はこんな感じ
インストール
とりあえずインストール
$ fvm flutter pub add google_mobile_ads
プラットフォーム固有の設定
Andorid
AndroidManifest.xml
にmeta-deta
を追加
テスト用のサンプルIDはca-app-pub-3940256099942544~3347511713
<manifest> <application> <meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="ca-app-pub-3940256099942544~3347511713"/> <application> <manifest>
iOS
Info.plist
にGADApplicationIdentifier
を追加
テスト用のサンプルIDはca-app-pub-3940256099942544~1458002511
<key>GADApplicationIdentifier</key> <string>ca-app-pub-3940256099942544~1458002511</string>
AdMobの初期化
初期化はこんな感じ
import 'package:google_mobile_ads/google_mobile_ads.dart'; import 'package:flutter/material.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); MobileAds.instance.initialize(); runApp(MyApp()); }
バナー広告の実装
テスト用の広告ユニットIDはこれ
Android: ca-app-pub-3940256099942544/6300978111 iOS: ca-app-pub-3940256099942544/2934735716
登場人物としては、こんな感じ
BannerAd
... バナー広告の情報BannerAdListener
... BannerAdのイベントリスナーAdWidget
... BannerAdを表示するWidget
広告の読み込み
広告を表示する前に読み込み(load)が必要。こんな感じ。
BannerAd
を作成BannerAd().load()
で読み込み開始- 読み込みが完了したかは、
BannerAdListener
のonAdLoaded
で扱う
class BannerExampleState extends State<BannerExample> { BannerAd? _bannerAd; bool _isLoaded = false; // TODO: replace this test ad unit with your own ad unit. final adUnitId = Platform.isAndroid ? 'ca-app-pub-3940256099942544/6300978111' : 'ca-app-pub-3940256099942544/2934735716'; /// Loads a banner ad. void loadAd() { _bannerAd = BannerAd( adUnitId: adUnitId, request: const AdRequest(), size: AdSize.banner, listener: BannerAdListener( // Called when an ad is successfully received. onAdLoaded: (ad) { debugPrint('$ad loaded.'); setState(() { _isLoaded = true; }); }, // Called when an ad request failed. onAdFailedToLoad: (ad, err) { debugPrint('BannerAd failed to load: $error'); // Dispose the ad here to free resources. ad.dispose(); }, ), )..load(); } }
正常に読み込まれると、AdMobで設定したタイミングで自動更新されるが、
onAdFailedToLoad
などでエラーになると、再読み込みなどはされないので、
再度、BannerAd
を作り直して、load()
しないといけない。
常に表示する広告だと、ネットワークが一時中断されると表示されなくなるけど、
ネットワークが復活しても広告が表示されない。。
また、多くの場合は、Wigetのmount時に読み込みを開始すると、
表示までに時間がかかるので、ChangeNotifierやriverpodを使って、
Widget外で事前読み込みをしておくとよいっぽい。
- Add ads to your mobile Flutter app or game | Flutter
- ここの一番最後にTipsに記載されれる
広告の表示
広告の表示はWidgetが用意されているので、
読み込みが完了したBannerAd
をAdWidget
に渡せばOK
... if (_bannerAd != null) { Align( alignment: Alignment.bottomCenter, child: SafeArea( child: SizedBox( width: _bannerAd!.size.width.toDouble(), height: _bannerAd!.size.height.toDouble(), child: AdWidget(ad: _bannerAd!), ), ), ) } ...
読み込んだ広告に対して表示できる幅/高さが足りないと表示されないので、
SizedBox
などで表示領域を確保しておくとよいっぽい。
広告の破棄
表示の必要がなくなったら、dispose()
を呼び出して破棄する。
Widgetのdispose
時や、BannerAdListenerのonAdFailedToLoad
など。
@override void dispose() { // TODO: Dispose a BannerAd object _bannerAd?.dispose(); ... super.dispose(); }
BannerAdListener( // Called when an ad request failed. onAdFailedToLoad: (ad, err) { debugPrint('BannerAd failed to load: $error'); // Dispose the ad here to free resources. ad.dispose(); }, )
広告のサイズ
サイズはあらかじめいくつか用意されているので、
標準的なバナーサイズであれば、これらを選択すればOK
AdSize.banner // 320x 50 標準バナー AdSize.largeBanner // 320x100 バナー(大) AdSize.mediumRectangle // 320x250 レクタングル(中) AdSize.fullBanner // 468x 60 フルサイズ バナー AdSize.leaderboard // 728x 90 リーダーボード
ほかにもいろいろ用語がある
- アンカーバナー/アンカー型バナー
- 画面の上部or下部に固定されるバナー全般
- インラインバナー
- 記事の途中などで表示されるタイプのバナー
- スクロールするコンテンツの中に表示する
- スマートバナー
- 高さを固定したバナー
- 現在はDeprecatedで、アダプティブバナーが推奨
- アダプティブバナー
- アンカーアダプティブバナー
- インラインアダプティブバナー
- インラインバナーのアダプティブ版
- アンカーアダプティブバナーよりも大きく背の高いバナー
- 固定の高さではなく最大の高さが利用される
- インラインアダプティブバナー | Google for Developers
アダプティブバナーは、サイズが動的なので、
MediaQuery.of(context)
で画面サイズを取得したり、
広告のサイズを取得し直したりとちょっと手間がかかる
アンカーアダプティブバナーの実装
画面の上か下に固定するアンカータイプのアダプティブバナー
注意点はこんな感じで書かれている
- 広告を配置するView幅の把握が必要
- デバイスの幅とセーフエリアやカットアウト考慮が必要
- アダプティブバナーは横幅いっぱいにしておくと最大限の成果が得られるように設計
- 特定のデバイスで特定の幅に対して返されるサイズは常に同じ
- アンカー型バナーの高さは、デバイスの高さの15%以下、
または密度非依存ピクセルが90ピクセル以上、密度非依存ピクセルが50以上
横幅をデバイス幅にする場合はこんな感じ。
final adUnitId = Platform.isAndroid ? 'ca-app-pub-3940256099942544/6300978111' : 'ca-app-pub-3940256099942544/2934735716'; Future<void> _loadAd() async { // デバイスのサイズの取得 final deviseSize = MediaQuery.of(context).size; // 基準となる広告の横幅を取得 final adWidth = deviseSize.width.truncate(); // 広告サイズの計算 final AnchoredAdaptiveBannerAdSize? size = await AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize(adWidth); if (size == null) { print('Unable to get height of anchored banner.'); return; } _anchoredAdaptiveAd = BannerAd( adUnitId: adUnitId, size: size, request: AdRequest(), listener: BannerAdListener( onAdLoaded: (Ad ad) { print('$ad loaded: ${ad.responseInfo}'); setState(() { // 読み込みが完了すると、広告サイズが設定された // BannerAdを得られるので、保存しておく _anchoredAdaptiveAd = ad as BannerAd; _isLoaded = true; }); }, onAdFailedToLoad: (Ad ad, LoadAdError error) { print('Anchored adaptive banner failedToLoad: $error'); ad.dispose(); }, ), ); return _anchoredAdaptiveAd!.load(); }
AdSize
を継承したAnchoredAdaptiveBannerAdSize
を利用する感じ。
サイズ計算はいくつか用意されている感じ。
基本は横幅のみ指定して、事前読み込みするときに向きも指定するっぽい
/// 横幅のみ指定。向きは現在の向きを取得 Future<AnchoredAdaptiveBannerAdSize?> getCurrentOrientationAnchoredAdaptiveBannerAdSize( int width, ) /// 横幅と向きを指定 Future<AnchoredAdaptiveBannerAdSize?> getAnchoredAdaptiveBannerAdSize( Orientation orientation, int width, )
インラインアダプティブバナー
スクロールコンテンツ内に配置するタイプのアダプティブバナー
アンカータイプよりも大きめで背が高い
注意点はこんな感じで書かれている
- インラインアダプティブバナーは、利用可能な全幅を使用した場合に最も効果を発揮するように設計。
ほとんどの場合、デバイスの画面の全幅になり、該当するセーフエリアを必ず考慮が必要 - インラインアダプティブバナーは、スクロール可能なコンテンツに配置されるように設計。
バナーの高さは、APIに応じて、デバイスの画面と同じか、最大の高さに制限 - 広告の実際の高さは、読み込み完了時にわかる
実装はこんな感じ
final adUnitId = Platform.isAndroid ? 'ca-app-pub-3940256099942544/6300978111' : 'ca-app-pub-3940256099942544/2934735716'; Future<void> _loadAd() async { // デバイスのサイズの取得 final deviseSize = MediaQuery.of(context).size; // 基準となる広告の横幅を取得 final adWidth = deviseSize.width.truncate(); // 広告サイズの計算 final AdSize size = await AdSize.getCurrentOrientationInlineAdaptiveBannerAdSize(adWidth); _inlineAdaptiveAd = BannerAd( adUnitId: adUnitId, size: size, request: AdRequest(), listener: BannerAdListener( onAdLoaded: (Ad ad) { print('$ad loaded: ${ad.responseInfo}'); setState(() { BannerAd bannerAd = (ad as BannerAd); // 読み込みが完了した広告を使って、 // 現在のプラットフォームにあうサイズを取得 final AdSize? size = await bannerAd.getPlatformAdSize(); if (size == null) { print('Error: getPlatformAdSize() returned null for $bannerAd'); return; } // 広告と合わせて、広告サイズも保持しておく _inlineAdaptiveAd = bannerAd; _adSize = size; _isLoaded = true; }); }, onAdFailedToLoad: (Ad ad, LoadAdError error) { print('Anchored adaptive banner failedToLoad: $error'); ad.dispose(); }, ), ); return _anchoredAdaptiveAd!.load(); }
サンプルではAdSize
が使われているが、実際には、
AdSize
を継承したInlineAdaptiveSize
を扱っている感じ。
こちらもサイズ計算の関数がいくつか用意されている。
/// 横幅のみ指定。向きは現在の向きを取得。高さは0で固定 InlineAdaptiveSize getCurrentOrientationInlineAdaptiveBannerAdSize(int width) /// 横向きの広告サイズを取得。横幅のみ指定。高さは0で固定 InlineAdaptiveSize getLandscapeInlineAdaptiveBannerAdSize(int width) /// 縦向きの広告サイズを取得。横幅のみ指定。高さは0で固定 InlineAdaptiveSize getPortraitInlineAdaptiveBannerAdSize(int width) /// 横幅と最大サイズを指定 InlineAdaptiveSize getInlineAdaptiveBannerAdSize(int width, int maxHeight);
getPlatformAdSize
の定義はこんな感じ。
class BannerAd extends AdWithView { // ...略 /// Returns the AdSize of the associated platform ad object. /// /// The dimensions of the [AdSize] returned here may differ from [size], /// depending on what type of [AdSize] was used. /// The future will resolve to null if [load] has not been called yet. Future<AdSize?> getPlatformAdSize() async { return await instanceManager.getAdSize(this); } }
instanceManager
はなにかというと、MethodChannel
を管理するクラスのよう。
/// Maintains access to loaded [Ad] instances and handles sending/receiving /// messages to platform code. class AdInstanceManager { AdInstanceManager( String channelName, {this.webViewControllerUtil = const WebViewControllerUtil()}) : channel = MethodChannel(channelName, StandardMethodCodec(AdMessageCodec()),) {...});
- googleads-mobile-flutter/packages/google_mobile_ads/lib/src/ad_instance_manager.dart at main · googleads/googleads-mobile-flutter
- Writing custom platform-specific code | Flutter
- MethodChannel class - services library - Dart API
バナー広告の選び方
ざっくりこんな感じがいい気がする
エラーメッセージ
BannerAdListenerのonAdFailedToLoad
で確認したメッセージ
とりあえず、確認済みのものを記載しておく
BannerAdListener( onAdFailedToLoad: (ad, error) { debugPrint(error.message); }, )
Ad size will not fit on screen
広告のサイズが画面サイズを超えている?
# width=500 / maxHeight=500でリクエスト final adSize = AdSize.getInlineAdaptiveBannerAdSize(500, 500); # => Error building request URL: Ad size will not fit on screen. a_w=500, a_h=500, s_w=412, s_h=815 # 画面サイズが、width:height=412/815なのでNG
maxHeightを調整すると、画面サイズを超えていても行ける時がある。。
# width=500 / maxHeight=400だと何故かうまくいく final adSize = AdSize.getInlineAdaptiveBannerAdSize(500, 400); # 取得できた広告サイズは、width:height=500/345
Error while connecting to ad server
ネットにつながってないときのメッセージ
Error while connecting to ad server: Unable to resolve host "googleads.g.doubleclick.net": No address associated with hostname
ATT/GDPR/app-ads.txt対応
長くなったので、とりあえずの参考リンクのみ。
このあたりを見ながら対応していこう。。
- ATT(App Tracking Transparency/Appleで必須)
- GDPR(ヨーロッパでは必須)
- ATT と GDPR の両立【プログラム・テクニカル】|AdMob|Flutter|THE CUTBOSS POST
- 参考リンクもよさそう
- app-ads.txt
以上!! 広告をつけるだけでも大変だ。。(*´ω`*)
参考にしたサイトさま
- google_mobile_ads | Flutter package
- Flutter アプリに AdMob 広告を追加する
- 始める | Flutter | Google for Developers
- 【Flutter】Androidのdebugモード時に、FirebaseAuthのGoogleサインインするとsign_in_failed
- Google OAuthのSHA
- Unhandled Exception: PlatformException(sign_in_failed, com.google.android.gms.common.api.ApiException: 10: , null, null)の対処法 #Flutter - Qiita
- firebase - how to get sha1 of android app in Vs code - Stack Overflow
cd android && ./gradlew app:signingReport
- or
Task :app:signingReport