flutterで開発したアプリに広告を貼る方法をメモしておく。
勉強中の備忘録なのでかなり雑な文章だと思うし、間違えもあると思ういます。なんなら指摘して欲しいです。
環境とか
バージョン | |
---|---|
Flutter | 3.3.0 |
Dart | 2.18.0 |
google_mobile_ads | 2.0.1 |
provider | 6.0.3 |
勉強方法
とりあえずYouTubeでFlutter公式の動画を見る。
おじさんの顔が終始愛らしくていい感じ。実際の手順
こっから↑の動画の内容をただただ書き綴っていく(?)
1 Google AdMobの設定
アカウント登録
アプリの登録
左の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に追加する。
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}" />
を追記
</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をインポート
import 'package:google_mobile_ads/google_mobile_ads.dart';
↓
main関数内でイニシャライズ
void main() {
WidgetsFlutterBinding.ensureInitialized();
final initFuture = MobileAds.instance.initialize();
runApp(const MyApp());
}
WidgetsFlutterBinding.ensureInitialized();
を入れると、runApp()の前に呼び出すとランタイムエラーを回避できるらしい。。。
よくわかんないけどいい感じっぽいのでとりあえずやっとく。
↓
lib直下にad_state.dartを作ってAdStateクラスを定義する。
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に追加しなきゃいけない。なんか多分言及されていない?)
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の部分。
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を追加
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()
を直接渡す?(表現が合ってるのかわからん)
@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の中に追加
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
を用意して切り替えを行う
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.
というエラーが出る。
軽く調べた感じ、本番用の広告はストアに登録してるアプリじゃないとロードできないっぽい?
シミュレーターとか、テスト用に登録したデバイスでは本番用のユニットを使ってもテスト広告を流してくれる的なことがどっかに書いてあった気がする、仕様に変更があった?
理由はよくわからなかった(というかそこまで調べる気力が湧かなかった)けど、とりあえずデバッグでは現状確認できなかった。
余談
リストの途中にちょいちょい広告を挟む方法が最後に紹介されているけど、面倒だったのでスキップした。
この記事の人は公式のドキュメントを読んでやったらしい。記事内でも言われてるけど、バージョンアップの関係(?)で時々そのまま書いてもダメなところがある。公式大事。動画も公式だけど。
プログラムを書きながらこの記事を書いてるので、修正し忘れてるところとかあるかも。がんばれ未来の自分。