Flutterでの広告ID取得に関して考える (Android/iOS)

最近、iOSではiOS14.5からApp Tracking Transparency(名称が長いのでこの先はATTと略す)に対応しないと完全にIDFAの取得ができなくなりました

iOS 14.5以上サポートしているアプリではATT対応はもちろん必要ですが、サードパーティ製の広告SDKを使わずに会社独自の広告配信基盤などを持っている場合は標準ライブラリを使って広告IDを取得するケースがあると思います (基本、広告SDKが内部的に広告IDをよしなに取得してくれてるイメージ)

両OS含めてFlutterで取得する場合はどうすればいいかと気になり、調べたので簡単にまとめてみます

# 環境
* Flutter 2.0.4
* Dart 2.12.2

# 広告ID取得のパッケージ
スコア高め(Pub Point 110 / Popularity 80以上)とNull Safety対応しているかの点で以下の2つかなと思いました

* [app_tracking_transparency](https://pub.dev/packages/app_tracking_transparency)
* [advertising_id](https://pub.dev/packages/advertising_id)

使い分けに関してですが

* AAIDを取得したい → `advertising_id`
* (あんまりないケースだと思いますが)  iOS 14未満で`ASIdentifierManager`クラスの有効フラグも使ってIDFAを取得したい　→ `advertising_id`
* iOS 14以上でIDFA取得したい　→　`app_tracking_transparency`

ちなみに advertising_idパッケージも内部的にATT対応( [requestTrackingAuthorization](https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager/3547037-requesttrackingauthorization) メソッド呼んでるなど)しているのでiOS 14でも使えます

大まかな違いがATTの`AuthorizationStatus`がFlutter側で使えるか使えないかになります

# AAIDの取得

以下のパッケージをインストールします

```pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  advertising_id: ^2.0.0
```


有効フラグは `AdvertisingId.isLimitAdTrackingEnabled`
AAIDは `AdvertisingId.id`　で
それぞれの値が返ってくるので以下のコードで実現できます


```Dart
  final _notAdID = '取得失敗用文字列';
  
  Future<String> _getAdIDByAndroidPlatform() async {
    try {
      final isTrackingEnable = await AdvertisingId.isLimitAdTrackingEnabled;

      // 有効でなければ取得しない
      if (isTrackingEnable != null && !isTrackingEnable) {
        return _notAdID;
      }

      final aaid = await AdvertisingId.id(true); // Future型で返ってくる

      if (aaid == null) {
        return _notAdID;
      } else {
        return aaid;
      }
    } catch (e) {
      print(e);
      return _notAdID;
    }
  }

```

ビルドして確認する際は
端末の設定アプリ > Google > 広告 の画面のスイッチボタンでオプトイン/オプトアウトして確認してください


# IDFAの取得

以下の広告IDを取得するパッケージと端末情報を取得するパッケージをインストールします

```pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  app_tracking_transparency: ^2.0.0+1
  device_info: ^2.0.0
```

Flutterで以下のようなOSを判定するコードは存在しますが、

```Dart
    if (Platform.isAndroid) {
      // Android
    } else if (Platform.isIOS) {
      // iOS
    } else {
      // 他プラットフォーム (例　Webとか)
    }

```

OSのバージョンまで判定するコードが標準にはないです
[device_info](https://pub.dev/packages/device_info)パッケージを使ってOSバージョンを取得できるので、それを使ってバージョン判定するコードを自作できます


iOSでは以下のコードで端末のOSバージョンを取得できます

```Dart
  Future<String> getIOSOSVersion() async {
    try {
      final iOSInfo = await DeviceInfoPlugin().iosInfo;
      return iOSInfo.systemVersion;
    } on PlatformException {
      // 失敗
    }
  }

```

また、[3桁のバージョン比較はこちらの記事](https://zenn.dev/nkysyuichi/articles/8ba23b38a8878a)が参考になるのでこの記事では割愛します

`app_tracking_transparency` パッケージでは以下の3つのことができます

* IDFAの取得 `AppTrackingTransparency.getAdvertisingIdentifier()`
* ATTのステータス `AppTrackingTransparency.trackingAuthorizationStatus`
* ATTのパーミッションリクエスト `AppTrackingTransparency.requestTrackingAuthorization()`

以下のコードで場合分けして取得するのがいいと思います

```Dart
final _zeroID = '00000000-0000-0000-0000-000000000000';
final _notAdID = '取得失敗用文字列';

  Future<String> _getAdIDByIOSPlatform() async {
    try {
      final osVersion = await deviceInfoRepository.getIOSOSVersion();

      // iOS 14.5以上なら
      if (/*バージョン判定コード*/) {
        final TrackingStatus trackStatus =
            await AppTrackingTransparency.trackingAuthorizationStatus;

        // 初めての場合はATTのパーミッションリクエスト
        if (trackStatus == TrackingStatus.notDetermined) {
          final status =
              await AppTrackingTransparency.requestTrackingAuthorization();

          if (status == TrackingStatus.authorized) {
            return AppTrackingTransparency.getAdvertisingIdentifier();
          } else {
            return _notAdID;
          }
        }
      }

      // iOS 14 ~ 14.5未満なら
      if (/*バージョン判定コード*/) {
        final status =
            await AppTrackingTransparency.trackingAuthorizationStatus;

        if (status == TrackingStatus.notDetermined ||
            status == TrackingStatus.authorized) {
          return AppTrackingTransparency.getAdvertisingIdentifier();
        } else {
          return _notAdID;
        }
      }
      // iOS 14.0未満のバージョンは
      final idfa = await AppTrackingTransparency.getAdvertisingIdentifier();
      
      // 取得失敗すると00000~~が返ってくる
      if (_zeroID == idfa) {
        return _notAdID;
      } else {
        return idfa;
      }
    } catch (e) {
      print(e);
      return _notAdID;
    }
  }

```

気を付けるポイントは ATTは使用できますが、iOS 14.0 ~ 14.5未満の間ならパーミッションリクエストしなくても端末の設定に応じてIDFAができます　
オプトアウトしている場合は `Tracking.denied`が返ってきます

iOS 14未満の場合は文字列の比較をしてあげるとよいです
オプトアウトしている場合にIDFA取得しようとすると0000~が返ってくるので、文字列一致の判定でオプトイン/オプトアウトの判定できます

また、ATTのパーミッションのリクエストする際はInfo.plistで文言の設定も必要になってくるので忘れずに設定しましょう

```Info.plist
<dict>
	<key>NSUserTrackingUsageDescription</key>
	<string>IDFA取得しますよ</string>
</dict>
```

![スクリーンショット 2021-05-20 10.19.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80912/e91a7b3d-833d-496c-983a-7adaa228acde.png)

初めてリクエストする際はこのように表示されます

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80912/e927c938-9ed1-f888-ea9b-43e75faf7a2f.png" width="350.0">


もし何か間違っている箇所があれば気軽にご指摘お願いします :pray: 

# 参考にした記事

[iOS14でのIDFA取得](https://qiita.com/yofuru/items/213b88b85553631204e4#requesttrackingauthorizationwithcompletionhandler%E3%81%AE%E6%8C%99%E5%8B%95)
[[iOSアプリ開発] バージョン番号を比較する仕組みを作る](https://zenn.dev/nkysyuichi/articles/8ba23b38a8878a)