Flutter/広告バナー(AdMob)/AppTrackingTransparency (ATT)対応/google_mobile_ads,app_tracking_transparency

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;
 }
}

スクリーンショット 2021-05-29 19.18.56

※シミュレータなので英語になっていますが実機では日本語になります。後半のメッセージは、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

GitHub - take424/att
Contribute to take424/att development by creating an account on GitHub.

この記事を気に入っていただけたなら、ぜひ自作アプリのインストールをお願いします。

コメント

タイトルとURLをコピーしました