Flutter✖️AppsFlyerでディープリンク・ディファード（遅延）ディープリンクを実現する

　
```Firebase Dynamic Links```が非推奨となったことにより、[AppsFlyer](https://www.appsflyer.com/ja/)や[Adjust](https://www.adjust.com/ja/)がその代替手段として上がっています。今回はAppsFlyerについて実装したことを紹介します。

# 背景
QRコード(URL)の読み込みから、アプリの特定の画面へ遷移させたい
例：QRコードを読み取ったユーザーに、アプリの画面で割引クーポンを取得したことを表示

- アプリをインストールしている場合は特定の画面へ遷移させる
- アプリをインストールしていない場合はストアへ遷移させる
    - iOSであればAppStoreへ、AndroidであればGooglePlayストアへ遷移させる
    - インストール後に特定の画面へ遷移させる

# 用語の整理
ディファード（遅延）ディープリンクについての参考サイト

https://qiita.com/y-matsumoto/items/c244d6749f85165d8741

ユニバーサルリンク/AppsLink/URIスキームなどの参考サイト

https://zenn.dev/msorz/articles/72a282ff71a350

# AppsFlyer Onelink
[公式ドキュメント](
https://support.appsflyer.com/hc/ja/articles/208874366-OneLink%E3%83%AA%E3%83%B3%E3%82%AF%E3%81%A8%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%9A%E3%83%AA%E3%82%A8%E3%83%B3%E3%82%B9)

## 料金
上記要件であれば基本的に無料。
（※細かい料金発生条件はご自身の責任で確認してください。）

複数のURLを一度に発行するためのAPI利用に料金がかかるよう。
手動でまかなえる範囲で必要な場合は、管理画面で手動で追加していけば良いと思います。

# 導入までの流れ
大まかにいうと下記になる

1. AppsFlyer管理画面でアカウント作成・Onelink作成
1. アプリの設定でユニバーサルリンク/AppsLink/URIスキームの設定
1. アプリ側でディープリンクのためのリスナーを設定
1. リスナーの中で特定の画面へ遷移させるロジックを実装

## 1. AppsFlyer管理画面でアカウント作成・Onelink作成
アカウント作成については省略。

「Onelinkテンプレート」を設定して、それに紐づくURL（QR）を個別に発行していく形になる。

### iOSとAndroidのアプリをid入力で識別させる
リリースされてなくてもOKだが、アプリIDが必要なのでストアには上がっている必要がある。

![スクリーンショット 2023-12-02 13.58.44（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/795935/80edf7be-cb74-dbb9-15f1-a40747ca9ed0.png)

### サブドメインを設定
.onelink.meは固定。アプリ専用の名称を設定できる。（myservice-onelink.onelink.meなど）

![スクリーンショット 2023-12-02 14.00.31（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/795935/ccd8fdc1-41e9-6c6f-0cd1-2400d516fca9.png)



### アプリインストールしていた場合の遷移方法を定義
ユニバーサルリンク・AppLinksまたはURIスキーム を設定

※管理画面上だとユニバーサルリンク・AppLinksのフォールバックとしてURIスキームが提供されているよう
※画像の例はAppLinks未対応

![スクリーンショット 2023-12-02 14.01.56（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/795935/b37e61b7-d0fa-a742-e1ee-9563c89a9c9a.png)

iOSユニバーサルリンクの設定にはチームIDが、
AndroidAppLinksの設定にはSHA256 フィンガープリントがそれぞれ必要です。

app-site-associationファイルなどを置くのはAppsFlyer側で自動で対応されるため、
そこを自前でサーバー立ててファイル配置してドメイン設定して・・としなくていいのは助かります。

### アプリインストールしていない場合の遷移先を定義
基本アプリストアだが、WebページなどでもOK

## 2. アプリの設定でユニバーサルリンク/AppsLink/URIスキームの設定
[iOS設定](https://ja.dev.appsflyer.com/hc/docs/dl_ios_init_setup)
[Android設定](https://ja.dev.appsflyer.com/hc/docs/dl_android_init_setup)

ここは通常のユニバーサルリンクやAppLinksの設定と特に変わらないので他の記事に譲ります。

## 3. アプリ側でディープリンクのためのリスナーを設定

https://pub.dev/packages/appsflyer_sdk

https://github.com/AppsFlyerSDK/appsflyer-flutter-plugin/tree/master

SDKの設定と、リスナーの設定に分かれます。
GitHubのREADMEが非常によくまとまっているので、迷ったら訳しながら読むことをお勧めします。

### SDKの初期化

```dart
flutter pub add appsflyer_sdk
```

```dart
import 'package:appsflyer_sdk/appsflyer_sdk.dart';

AppsFlyerOptions appsFlyerOptions = AppsFlyerOptions(
    afDevKey: Env.readEnv(name: "AF_DEV_KEY"),
    appId: Env.readEnv(name: "AF_APP_ID"),
    showDebug: true,
    timeToWaitForATTUserAuthorization: 50,
    disableAdvertisingIdentifier: true,
    disableCollectASA: true);

AppsflyerSdk appsflyerSdk = AppsflyerSdk(appsFlyerOptions);
```

- ```afDevKey```
AppsFlyer管理画面のアプリ設定>SDK認証ページのDevキー。
Envから取得するようにしていますが、お好きに調整してください。

- ```appId```
AppsFlyerのダッシュボードで設定したアプリケーションのApp ID（iOSのみ必須）
管理画面のURLでも確認できます。
"id"は不要。stgなどでテストアプリがある場合は、デバック用にそのidを入れる。

- ```timeToWaitForATTUserAuthorization```
ATT要求ダイアログ表示後に許諾が取れるまではAppsFlyerへのデータ送信が許可されないため、その遅延をどれくらい許容するかの数値？
許可されれば、その前に取得していたデータも送信されるので、インストール情報やディープリンク情報も取得できる

- ```disableAdvertisingIdentifier```,```disableCollectASA```
今回は不要なのでtrueにした。マーケティング目的で取得することが多いと思うが、(限りなく？)個人情報だと思うので不用意な取得は避けたい。

その他のオプション引数もあるが省略。
初期化処理は以下

```dart
appsflyerSdk.initSdk(
    registerConversionDataCallback: true,
    registerOnAppOpenAttributionCallback: true,
    registerOnDeepLinkingCallback: true
);
```

3つともtrueにする必要はなさそうだが、とりあえずtrueにしている。
ドキュメントを読む感じ1番下の```OnDeepLinkingCallback```だけでも良さそう。

### リスナーの設定
動作や仕組みは[公式doc](https://github.com/AppsFlyerSDK/appsflyer-flutter-plugin/blob/master/doc/DeepLink.md)が非常に分かりやすいので、詳細はそちらを参照ください。

```dart
 appsflyerSdk.onDeepLinking((DeepLinkResult dp) {
      switch (dp.status) {
        case Status.FOUND:
          print(dp.deepLink?.toString());
          print("deep link value: ${dp.deepLink?.deepLinkValue}");
          break;
        case Status.NOT_FOUND:
          print("deep link not found");
          break;
        case Status.ERROR:
          print("deep link error: ${dp.error}");
          break;
        case Status.PARSE_ERROR:
          print("deep link status parsing error");
          break;
      }
    }
```

ディープリンクからアプリを起動した場合は、この```Status.FOUND```を通ることになります。

```dp.deepLink```というDeepLinkインスタンスから、管理画面で設定したURLパラメータなどの情報が取得できます。例えば、```dp.deepLink?.deepLinkValue```など。

```dp.deepLink?.isDeferred```でディファードディープリンクかどうかも判定可能です！

:::note info
initSdk処理の前にリスナーを設定しましょう
> Note: The code implementation for onDeepLinking must be made prior to the initialization code of the SDK.
:::

:::note warn
サブパラメータの「af_sub_1」がなぜかディファードディープリンクの時だけ取得できないようでした。「deep_link_sub1」のパラメータの方は取得できたので、同じ現象が起きた方は```dp.deepLink?.getStringValue('deep_link_sub1')```
の方で取得するのをお勧めします。
（自分の勘違いかもしれないですが、詳しく調査していません）
:::



## 4. リスナーの中で特定の画面へ遷移させるロジックを実装

ご自身のアプリの設計に合わせて設定してください。

Navigato1.0の場合Buildcontextが必要になるため、
AppsFlyer関係の処理は別でまとめて、メソッドを呼び出すのが良いのではないでしょうか。

以下は例です。


```dart:.app.dart
  @override
  void initState() {
    AppsFlyerService.addAppsFlyerDeepLinkListener((params) {
      _handleNavigation(params);
    });
    AppsFlyerService.init();
  }

  Future<void> _handleNavigation(Map<String, String?> params) async {
    if (_isAfterLogin) {
      Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => QRReadedScreen(params: params),
          ));
    }
  }

```

```dart:.appsflyer_service.dart
 static void addAppsFlyerDeepLinkListener(
      Function(Map<String, String?>) callback) {
    return _appsflyerSdk.onDeepLinking((DeepLinkResult dp) {
      switch (dp.status) {
        case Status.FOUND:
        // 遅延ディープリンクの場合に特定の処理を挟む場合はここで対応
          bool? isDeferred = dp.deepLink?.isDeferred;
        // 特定の画面へパラメータ付きで遷移させる処理を受け付ける
          callback(params);
          break;
        // 以下省略
      }
```

# URL・QRコードの発行

URL（QRコード）の発行は管理画面の説明に従っていけば特に迷うことはないので、省略します。