Flutterアプリの広告バナーは、admob_flutterを使っていましたが、公式からgoogle_mobile_adsがリリースされたのでそちらに乗り換えつつ、 気は進みませんでしたが、ATT対応もやってみたので、そのメモを残します。
パッケージ
# pubspec.yaml(抜粋)
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
# ここから追加
# Admob用
google_mobile_ads: ^0.12.2+1
# ATT用
app_tracking_transparency: ^2.0.0
# ここまで追加
設定
iOS
# ios/Runner/Info.plist(抜粋)
<!-- ここから追加 -->
<key>GADApplicationIdentifier</key>
<!-- テスト用ID 本番では差し替えること -->
<string>ca-app-pub-3940256099942544~1458002511</string>
<key>SKAdNetworkItems</key>
<array>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>cstr6suwn9.skadnetwork</string>
</dict>
</array>
<key>NSUserTrackingUsageDescription</key>
<string>お客様の興味関心に合わせた広告を表示するために利用します。</string>
<!-- ここまで追加 -->
</dict>
</plist>
Android
# android/app/src/main/AndroidManifest.xml(抜粋)
<!-- ここから追加 -->
<!-- テスト用ID 本番では差し替えること -->
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-3940256099942544~3347511713"/>
<!-- ここまで追加 -->
</application>
</manifest>
# android/app/build.gradle(抜粋)
//minSdkVersion 16
minSdkVersion 19 // 変更
実装
AppTrackingTransparency (ATT)認証ダイアログを表示
iOS14.0以上の場合は、初期状態は、「未決定(notDetermined)」のため、認証ダイアログを表示して、「承認(authorized)」か「否認(denied)」をユーザーに選択してもらい、その結果を返します。
「承認」された場合、getAdvertisingIdentifier()でIDFAを取得することができますが、シミュレータの場合、常に「00000000-0000-0000-0000-000000000000」となり、取得できないことに注意してください。
iOS14.0未満の場合は、初期状態は、「非対応(notSupported)」となっており、認証ダイアログは表示できないため、「承認」として返します。
Androidも同様にATTは存在しないため、「承認」として返します。
2回目以降は、1回目の結果と同じものが返ります。
# lib/att.dart(全文) ファイル新規作成
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:app_tracking_transparency/app_tracking_transparency.dart';
// Singleton
class ATT {
static final ATT instance = ATT._();
ATT._();
Future<bool> requestPermission() async {
var result = false;
// 2021.10.02
// AppTrackingTransparency内でAndroidの判定が入っていることがわかったのでコメントアウト
//if (Platform.isIOS) {
TrackingStatus trackingStatus =
await AppTrackingTransparency.trackingAuthorizationStatus;
debugPrint("trackingStatus:$trackingStatus");
try {
if (trackingStatus == TrackingStatus.notDetermined) {
var status =
await AppTrackingTransparency.requestTrackingAuthorization();
debugPrint("requestTrackingAuthorization:$status");
if (status == TrackingStatus.authorized) {
result = true;
}
} else if (trackingStatus == TrackingStatus.authorized) {
result = true;
} else if (trackingStatus == TrackingStatus.notSupported) {
result = true;
}
} on PlatformException {
debugPrint('PlatformException was thrown');
}
//} else {
// result = true;
//}
final idfa = await AppTrackingTransparency.getAdvertisingIdentifier();
debugPrint('[AppTrackingTransparency]IDFA:$idfa');
debugPrint('[AppTrackingTransparency]result:$result');
return result;
}
}

※シミュレータなので英語になっていますが実機では日本語になります。後半のメッセージは、info.plistに設定したものが表示されています。
広告バナーWidgetの作成
# lib/banner_ad_widget.dart(全文) ファイル新規作成
import 'dart:io';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
class BannerAdWidget extends StatefulWidget {
/*
* size patterns
* AdSize.banner : 320x50
* AdSize.largetBanner : 320x100
* AdSize.mediumRectangle : 320x250
* AdSize.fullBanner : 468x60
* AdSize.leaderboard : 728x90
*/
final AdSize size = AdSize.banner;
@override
State<StatefulWidget> createState() => BannerAdState();
}
class BannerAdState extends State<BannerAdWidget> {
late BannerAd _bannerAd;
final Completer<BannerAd> bannerCompleter = Completer<BannerAd>();
@override
void initState() {
debugPrint('[LifeCycle][BannerAdWidget] initState.');
super.initState();
_bannerAd = BannerAd(
adUnitId: getBannerAdUnitId(),
request: AdRequest(),
size: widget.size,
//listener: AdListener(
listener: AdListener(
onAdLoaded: (Ad ad) {
debugPrint('$BannerAd loaded.');
bannerCompleter.complete(ad as BannerAd);
},
onAdFailedToLoad: (Ad ad, LoadAdError error) {
ad.dispose();
debugPrint('$BannerAd failedToLoad: $error');
bannerCompleter.completeError(error);
},
onAdOpened: (Ad ad) => debugPrint('$BannerAd onAdOpened.'),
onAdClosed: (Ad ad) => debugPrint('$BannerAd onAdClosed.'),
onApplicationExit: (Ad ad) =>
debugPrint('$BannerAd onApplicationExit.'),
),
);
Future<void>.delayed(Duration(seconds: 1), () => _bannerAd.load());
}
@override
void dispose() {
debugPrint('[LifeCycle][BannerAdWidget] dispose.');
super.dispose();
_bannerAd.dispose();
}
@override
Widget build(BuildContext context) {
return FutureBuilder<BannerAd>(
future: bannerCompleter.future,
builder: (BuildContext context, AsyncSnapshot<BannerAd> snapshot) {
Widget child;
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
child = Container();
break;
case ConnectionState.done:
if (snapshot.hasData) {
child = AdWidget(ad: _bannerAd);
} else {
child =
Text('Error loading $BannerAd.' + snapshot.error.toString());
}
}
return Container(
width: _bannerAd.size.width.toDouble(),
height: _bannerAd.size.height.toDouble(),
color: Colors.transparent,
child: child,
);
},
);
}
bool isRelease = const bool.fromEnvironment('dart.vm.product');
String getBannerAdUnitId() {
String bannerAdUnitId = '';
if (Platform.isAndroid) {
bannerAdUnitId = (isRelease
? 'ca-app-pub-0000000000000000/0000000000' // 本番で差し替える
: 'ca-app-pub-3940256099942544/6300978111'); // テスト用ID
} else if (Platform.isIOS) {
bannerAdUnitId = (isRelease
? 'ca-app-pub-0000000000000000/0000000000' // 本番で差し替える
: 'ca-app-pub-3940256099942544/2934735716'); // テスト用ID
}
return bannerAdUnitId;
}
}
認証ダイアログと広告バナーの組み込み
flutter createで作ったものを更にシンプルにしたものをベースとしています。
initState()で、ATT認証ダイログを表示、「承認」されたら広告バナーを初期化し、ウィジェットを表示しています。
「否認」の場合は、表示しません。
# lib/main.dart(全文)
import 'package:flutter/material.dart';
import 'package:att/att.dart'; // 追加
import 'package:att/banner_ad_widget.dart'; // 追加
import 'package:google_mobile_ads/google_mobile_ads.dart'; // 追加
void main() {
WidgetsFlutterBinding.ensureInitialized(); // 追加
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool isATTPermission = false; // 追加
@override
void initState() {
super.initState();
// ここから追加
ATT.instance.requestPermission().then((result) {
setState(() {
isATTPermission = result;
if (isATTPermission) {
// ATTの許可が取れたので広告バナーを初期化する
MobileAds.instance.initialize();
}
});
});
// ここまで追加
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ATT Sample'),
),
body: Center(
child: Column(
children: [
Text('hello ATT.'),
// ここから追加
Visibility(
// ATTの許可が取れたので広告バナーを表示する
visible: isATTPermission,
child: BannerAdWidget(),
),
// ここまで追加
],
),
),
);
}
}
動作確認済み端末
- iOS13.7 シミュレータ
- iOS14.3 シミュレータ
- iOS14.4 実機
- iOS14.5 実機、シミュレータ
- Android10 実機
GitHub
この記事を気に入っていただけたなら、ぜひ自作アプリのインストールをお願いします。
コメント