ExpoのManaged workflowでFirebase Dynamic Linksを実装する

# 前提
・ストアにアプリがリリースされている（ストアのURLを知っている）
・firebaseに登録している
・Expoを利用している

# firebaseの設定
## 1. ドメイン登録
firebaseコンソール > エンゲージメント > Dynamic Links

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/453380/0c7c4990-d705-5987-a25d-b08b46417c0d.png)

※　`<yourapp>.page.link`などのURLは無料で取得できます

## 2. ダイナミック リンクの設定
ディープリンクURLはWebからアクセスされた場合なので、適当にAppストアのリンクを設定します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/453380/9ca6fb2c-fe51-fd7f-8454-983a3d70edb1.png)
ただし、URLのパターンを登録しておく必要があります。


## 3. Apple 用のリンク動作の定義
✅ ディープリンクを Apple アプリで開く
まだアプリを追加していなければ、「Apple アプリを追加」を押し
・バンドル ID
・App Store ID
・チーム ID
を追加します

アプリがインストールされていない場合のユーザーの移動先:
✅ アプリの App Store ページ

## 4. Android 用のリンク動作の定義
✅ Android アプリ内でディープリンクを開く
・パッケージ名
・デバッグ用の署名証明書
（Auth で Dynamic Links、Google ログイン、電話番号をサポートするために必須です）

# Expoの設定

## 1. app.jsonの編集

以下の内容を追記します

```json: Android向けの設定
"android": {
  "intentFilters": [
    {
      "action": "VIEW",
      "data": [
        {
          "scheme": "https",
          "host": "＜登録したドメイン＞",
          "pathPrefix": "/"
        },
      ],
      "category": [
        "BROWSABLE",
        "DEFAULT"
      ],
      "autoVerify": true
    }
  ]
}
```
```json: iOS向けの設定
"ios": {
  "associatedDomains": ["applinks:＜登録したドメイン＞"]
}
```

## 2. Androidのプラグイン登録

Android 12以降で動作しないissueがあるので、ワークアラウンドのプライグインを導入します

プラグインについては、[Expoのドキュメント](https://docs.expo.dev/guides/deep-linking/#handling-app-links-on-android-with-expo-dev-client)にコードが書かれています

<details><summary>./plugin/withAndroidVerifiedLinksWorkaroundにファイルを追加します</summary><div>

```js: ./plugin/withAndroidVerifiedLinksWorkaround
const { createRunOncePlugin, withAndroidManifest } = require('@expo/config-plugins');

/**
 * @typedef {import('@expo/config-plugins').ConfigPlugin} ConfigPlugin
 * @typedef {import('@expo/config-plugins').AndroidManifest} AndroidManifest
 */

/**
 * Remove the custom Expo dev client scheme from intent filters, which are set to `autoVerify=true`.
 * The custom scheme `<data android:scheme="exp+<slug>"/>` seems to block verification for these intent filters.
 * This plugin makes sure there is no scheme in the autoVerify intent filters, that starts with `exp+`.
 *
 * @type {ConfigPlugin}
 */
const withAndroidVerifiedLinksWorkaround = config =>
  withAndroidManifest(config, config => {
    config.modResults = removeExpoSchemaFromVerifiedIntentFilters(config.modResults);
    return config;
  });

/**
 * Iterate over all `autoVerify=true` intent filters, and pull out schemes starting with `exp+`.
 *
 * @param {AndroidManifest} androidManifest
 */
function removeExpoSchemaFromVerifiedIntentFilters(androidManifest) {
  for (const application of androidManifest.manifest.application || []) {
    for (const activity of application.activity || []) {
      if (activityHasSingleTaskLaunchMode(activity)) {
        for (const intentFilter of activity['intent-filter'] || []) {
          if (intentFilterHasAutoVerification(intentFilter) && intentFilter?.data) {
            intentFilter.data = intentFilterRemoveSchemeFromData(intentFilter, scheme =>
              scheme?.startsWith('exp+')
            );
          }
        }
        break;
      }
    }
  }

  return androidManifest;
}

/**
 * Determine if the activity should contain the intent filters to clean.
 *
 */
function activityHasSingleTaskLaunchMode(activity) {
  return activity?.$?.['android:launchMode'] === 'singleTask';
}

/**
 * Determine if the intent filter has `autoVerify=true`.
 */
function intentFilterHasAutoVerification(intentFilter) {
  return intentFilter?.$?.['android:autoVerify'] === 'true';
}

/**
 * Remove schemes from the intent filter that matches the function.
 */
function intentFilterRemoveSchemeFromData(intentFilter, schemeMatcher) {
  return intentFilter?.data?.filter(entry => !schemeMatcher(entry?.$['android:scheme'] || ''));
}

module.exports = createRunOncePlugin(
  withAndroidVerifiedLinksWorkaround,
  'withAndroidVerifiedLinksWorkaround',
  '1.0.0'
);
```

</div></details>

Expoのコンフィグにこのpluginを追加します

```json: app.json > expo > plugins
{
  "plugins": ["./plugins/withAndroidVerifiedLinksWorkaround"]
}
```

# アプリのコード

```shell: expo-linkingをインストール
npx expo install expo-linking
```

`Linking.useURL()`の機能を使うことで、ハンドリングできます

https://docs.expo.dev/guides/linking/#handling-links


<details><summary>サンプルコード</summary><div>

```react: deepLinkController.tsx
import React from 'react';
import * as Linking from 'expo-linking';

export const DeepLinkController = () => {
  const url = Linking.useURL();

  React.useEffect(() => {
    (async () => {
      if (!url) return;
      const params = parseParamsFromUrl(url);
      
      // Do something
      
    })();
  }, [url]);

  return null;
};

const parseParamsFromUrl = (url: string) => {
  const parsedUrl = Linking.parse(url);
  const linkUrl = parsedUrl.queryParams?.link as string;
  if (!linkUrl) return;

  const params = Linking.parse(linkUrl).queryParams;
  return params;
};
```

</div></details>


以上です。お目汚し失礼いたしました。