Flutterでマネタイズをする際、AdMobを使用した広告がよく使用されます。
私も自分のアプリにAdMobを使用して広告を表示していますが、実装時に躓いた点やエラーで引っかかった箇所が少なかれあります。
本記事ではFlutterでAdMobのバナー広告を実装するサンプルと、引っかかるポイントを備忘録として記載します。
AdMobバナーで使用するパッケージ
FlutterでAdMobを表示する際は以下のパッケージを使用します。
こちらはGoogle本家から出ているパッケージで、基本的にこれを使用しておけばAdMob関連の操作をすることが可能です。
もちろん、この記事の対象であるバナー広告以外の、リワード広告やインタースティシャル広告などでもこのパッケージを使用します。
pubspec.yamlでパッケージの使用を宣言しておきましょう。
AdMobバナーを表示するサンプルソース
AdMobのバナー広告を表示するサンプルは以下のとおりです。
AdMobのバナー用クラス
AdMobのバナー用クラスを作成します。
※2022/2/14追記
最新のgoogle_mobile_adsを使用している場合、「BannerAd.testAdUnitId」がDeprecated(非推奨)となっています。
公式ドキュメントのデモ広告IDを使用しなさいということなので、それを使用するといいでしょう。
(今の所、BannerAd.testAdUnitIdとドキュメントのデモ広告IDは同じですが、いつの間にか変わっているかもしれないので、非推奨の方は使用しないほうが良いと思います。)
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
class AdBanner extends StatefulWidget {
const AdBanner({
Key? key,
required this.size,
}) : super(key: key);
final AdSize size;
@override
_AdBannerState createState() => _AdBannerState();
}
class _AdBannerState extends State<AdBanner> {
late BannerAd banner;
@override
void initState() {
super.initState();
banner = _createBanner(widget.size);
}
@override
void dispose() {
banner.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SizedBox(
width: banner.size.width.toDouble(),
height: banner.size.height.toDouble(),
child: AdWidget(ad: banner),
);
}
String get bannerAdUnitId {
if (kDebugMode) {
// return BannerAd.testAdUnitId; // Deprecatedになった
// 公式ドキュメントで公開されているデモ広告ユニットIDを指定する
// https://developers.google.com/admob/android/test-ads?hl=ja
// https://developers.google.com/admob/ios/test-ads?hl=ja
return Platform.isAndroid ? 'ca-app-pub-3940256099942544/6300978111'
: 'ca-app-pub-3940256099942544/2934735716';
}
if (Platform.isAndroid) {
return androidBannerId;
} else if (Platform.isIOS) {
return iOSBannerId;
} else {
// return BannerAd.testAdUnitId; // Deprecatedになった
// https://developers.google.com/admob/android/test-ads?hl=ja
// AndroidのテストIDを暫定で返却しておく
return 'ca-app-pub-3940256099942544/6300978111';
}
}
BannerAd _createBanner(AdSize size) {
return BannerAd(
size: size,
adUnitId: bannerAdUnitId,
listener: BannerAdListener(
onAdFailedToLoad: (Ad ad, LoadAdError error) {
banner.dispose();
},
),
request: const AdRequest(),
)..load();
}
}
バナーを表示したい箇所でバナー用クラスを呼び出す
バナー広告を表示したいところで、バナー用クラスを呼び出していきます。
今回のサンプルではボトムナビゲーションバーの上に、バナー広告を表示するという、よくあるパターンで実装しています。
class MainScreen extends StatefulWidget {
const MainScreen({Key? key}) : super(key: key);
@override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
var _currentIndex = 1;
final _pages = [
const ShopPage(),
const MyPage(),
const HomePage(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: _pages[_currentIndex],
bottomNavigationBar: Column(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
FutureBuilder(
future: AdSize.getAnchoredAdaptiveBannerAdSize(Orientation.portrait,
MediaQuery.of(context).size.width.truncate()),
builder: (
BuildContext context,
AsyncSnapshot<AnchoredAdaptiveBannerAdSize?> snapshot,
) {
if (snapshot.hasData) {
final data = snapshot.data;
if (data != null) {
return Container(
height: 70,
color: Colors.white70,
child: AdBanner(size: data),
);
} else {
return Container(
height: 70,
color: Colors.white70,
);
}
} else {
return Container(
height: 70,
color: Colors.white70,
);
}
},
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: GNav(
rippleColor: Colors.orange.withOpacity(0.1),
hoverColor: Colors.orange.withOpacity(0.1),
haptic: true,
tabBorderRadius: 15,
curve: Curves.easeOutExpo,
duration: const Duration(milliseconds: 200),
gap: 0,
color: kGreyColorSix,
activeColor: Colors.orange,
iconSize: 30.h,
tabBackgroundColor: Colors.orange.withOpacity(0.1),
padding: EdgeInsets.symmetric(
horizontal: 20.w, vertical: 20.h),
selectedIndex: _currentIndex,
onTabChange: (value) {
setState(() {
_currentIndex = value;
});
},
tabs: const [
GButton(
icon: FontAwesomeIcons.gift,
),
GButton(
icon: FontAwesomeIcons.userGraduate,
),
GButton(
icon: FontAwesomeIcons.home,
),
],
),
),
],
),
);
}
}
ポイントはバナーのサイズ取得処理で、アダプティブバナーサイズを非同期で取得している箇所です。
このようにサイズを取得することでGoogleがオススメしているアダプティブバナーを実装することが可能です。端末や広告のサイズからいい感じのサイズにしてくれます。
また、バナー広告の背景にはContainerなどを用いて、あらかじめ幅を取っておくことをオススメします。
バナー広告のサイズが想定していないようなサイズになった時などに、他のWidgetと被ったりしてしまうと、広告を故意に押させるのはNGであるというAdMobのポリシーに引っかかってしまう可能性があります。
そうならないためにも、バナー広告の背景にContainerを設定し、あらかじめ幅をとっておくと良いでしょう。
個人的にAdMobのバナー広告で引っかったポイント
ここからは私が実装したときに引っかかったことを列挙しておきます。同じ状況に陥ったときに参考になれば幸いです。
画面が非常に重くなる
これは本当に単純なミスだったのですが、本番環境で初めて気づいたのでかなりびっくりしました。
広告の読み込みに失敗した際に、再度広告を読み込むようにバナー作成処理を読み込み失敗時に再実行するようにしていました。
確かに広告読み込み失敗時に再実行されていたのですが、たいてい失敗しているときは何度読み込んでも失敗するので、ひたすら作っては失敗を繰り返していてメモリを食いつぶしていました。
どうせ失敗するので、再作成する処理を消すことで解決としました。もしうまくやるとしたら、リトライ回数を保持して3回までは再作成を試すなどでも良いかもしれませんね。
BannerAd _createBanner(AdSize size) {
return BannerAd(
size: size,
adUnitId: bannerAdUnitId,
listener: BannerAdListener(
onAdFailedToLoad: (Ad ad, LoadAdError error) {
banner.dispose();
_createBanner(size);
},
),
request: const AdRequest(),
)..load();
}
テストIDで広告がロードできなくなった(未解決)
もともとはテストIDで広告がしっかりと表示されていたのですが、ある日突然広告の読み込みに失敗するようになりました。
その際のエラーはリクエストはできているけど、表示する広告がないというものでした。
テスト広告でそんな風になるかな?とは思うのですが、突然復活したという情報もあったので、私も少し待ってみようと思います。
復活したらその旨記載します。
まとめ
FlutterでAdMobのバナー広告を表示するのは、公式ドキュメントやネットに転がっている情報などをそのまま使えばある程度は実装できると思います。
とはいえ、本記事で紹介したアダプティブバナーの実装方法など、あまりサンプルが存在しないものもあるので、この記事で備忘録として残しておきます。
○Flutter中級者以上にオススメの参考書
○Flutter初学者にはやはりこの参考書がオススメ