Skip to content

Instantly share code, notes, and snippets.

@mrtry
Last active October 7, 2024 03:16
Show Gist options
  • Save mrtry/20944c7e0f51e11eb34c6fb5b9c30d10 to your computer and use it in GitHub Desktop.
Save mrtry/20944c7e0f51e11eb34c6fb5b9c30d10 to your computer and use it in GitHub Desktop.
expo-routerで(Deep|Web) Link対応

title: expo-routerで(Deep|Web) Link対応 author: mrtry date: 2024/10/04

このスライドでわかること

  • (Deep|Web) Linkがどういうものかわかる
  • expo-routerでの(Deep|Web) Linkの対応方法がわかる

(Deep|Web) Link

Deep Link

  • アプリごとにURL Schemeを定め、そのSchemeの指定することでアプリを起動できるようにする仕組み

Web Link

  • アプリをインストールした状態でWebサイトを開くと、アプリが開かれる仕組み

(Deep|App) Link

名称 URL Scheme 補足
Deep Link [任意文字列]:// examble-app:// Android/iOSどちらも共通名称
native deep-link [任意文字列]:// examble-app:// ExpoのcontextでのDeep Link
Web Link https://... https://example-site.com App LinkとUniversal Linkをまとめた表現
Androidのドキュメントでちょっと出てくる
Universal Link https://... https://example-site.com iOSのcontextでのWeb Link
App Link https://... https://example-site.com AndroidのcontextでのWeb Link
web deep-links https://... https://example-site.com ExpoのcontextでのWeb Link

Deep Linkを実装する

どう実装するのか

  • expo-routerを利用している場合は、app.config.tsでschemeを定めるだけ
  • <scheme>://<dir-path>の指定でアプリが起動する
    • scheme: my-app
    • app/home/index.ts がある
    • Deep Linkは my-app://home になる

動作確認

React Navigationのドキュメントに書いてある

# Simulatorやら実機でアプリを立ち上げた状態で以下を実行する
$ npx uri-scheme open <scheme>://<expo-routerで設定しているpath> --<ios|android>

Web Linkを実装する

どう実装するのか

  1. Webサイトに設定ファイルをアップロードしておく
  2. app.config.tsに設定を追加する
  3. 「WebサイトのURLに対応するアプリの画面を開く」処理を実装する

1. Webサイトに設定ファイルをアップロードしておく

Webサイトに設定ファイルをアップロードしておく

それぞれのformatに従ったJSONを、Webサービスのドメインからアクセスできるようにする

apple-app-site-association

  • <team-id>.<bundle-id> の形式でアプリを指定する
  • components で、「どのpathをアプリで起動する/除外する」を定義できる
    • 制御はここでやらないほうがいい (最後にTipsで説明する)
{
  "applinks": {
    "details": [
      {
        "app-ids": ["<team-id>.<bundle-id>"],
        "components": [
          {
            "/": "*"
          },
          {
            "/": "/sign_up",
            "exclude": true,
          },
        ]
      }
    ]
  },
  "webcredentials": {
    "apps": ["<team-id>.<bundle-id>"]
  }
}

apple-app-site-association

iOSは設定確認が面倒なので注意

asset-link.json

  • iOSの設定ファイルと違い、「どのpathをアプリで起動する/除外する」を定義できないので注意
[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "<package-name>",
    "sha256_cert_fingerprints":
    ["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
  }
}]

asset-link.json

  • Play Storeに設定例が出ている
  • コピペして、Develop/Staging環境などはpackage nameを変更すればいい

2. app.config.tsに設定を追加する

app.config.tsに設定を追加する

  • app.config.tsを書き換える
  • Appleのサイトで、Associate Domainを有効にする
  • rebuildする

2-1. app.config.tsを書き換える

ios / android の項目それぞれに設定を追記する

{
  "ios": {
    // 開発中はQueryParameterを追加して "applinks:<bundle-id>?mode=developer" にしたほうがいい
    // apple-app-site-associationをApple CDNではなく、Webサイトから直接参照してくれるので24hまたなくて良くなる
    "associatedDomains": ["applinks:<bundle-id>"]
  }
  "android": {
    // codelabで説明されている内容と同じ
    // see: https://developer.android.com/codelabs/android-app-links-introduction?hl=ja#5
    "intentFilters": [
      {
        "action": "VIEW",
        "autoVerify": true,
        "data": [
          {
            "scheme": "https",
            "host": "<web-domain>",
            pathPattern: '.*', // どのURLをアプリで起動するかを指定する。NOTなpath指定ができないので注意
          }
        ],
        "category": ["BROWSABLE", "DEFAULT"]
      }
    ]
  }
}

2-2. Appleのサイトで、Associate Domainを有効にする

以下の記事が丁寧なので読むといい

3.3 rebuildする

  • app.config.tsの変更内容は、native layerの変更なので、rebuildする
  • iOSは、Provisioning Profileの再生成も必要なため、 --non-intaractive は外して実行する

3. 「WebサイトのURLに対応するアプリの画面を開く」処理を実装する

「WebサイトのURLに対応するアプリの画面を開く」処理を実装する

  • 2.までやると、サイト開こうとするとアプリは起動する
  • expo-routerのdefault: Webサイトのpathに合わせて画面遷移する
  • defaultのnavigationだと、アプリにとって不都合なことが多い
    • webとモバイルアプリのURL設計がズレていることは多分にあるため
  • defaultのnavigationをoverrideすることが必要になる

+native-intent.tsx

Customizing links - Expo Documentation

  • redirectSystemPath():
    • URLでアプリを起動したときに呼ばれる
    • この関数から返されたpathを元にexpo-routerが画面遷移してくれる
import { URL } from 'react-native-url-polyfill';

export function redirectSystemPath({ path, initial }) {
  // initial時に特定画面に遷移させると画面が表示されないことがあった
  // app/index.tsに<Redirect />だけ書いて、とりあえずそこに飛ばすと動いたのでworkaroundとしてやってる
  if (initial) return '/'

  const url = new URL(path, 'myapp://app.home');
  switch (url.pathname) {
    // pathnameをアプリに合わせて返す
    case '/notification':
      return '/home/notification';
    ...
  }

  return path;
}

動作確認

Flutterのドキュメントが丁寧

$ xcrun simctl openurl booted https://<web domain>/details
$ adb shell 'am start -a android.intent.action.VIEW \
    -c android.intent.category.BROWSABLE \
    -d "http://<web-domain>/details"' \
    <package name>

Tips

Tips: initial時に直接画面遷移させると画面が描画されないとき

+native-intent.tsx

import { URL } from 'react-native-url-polyfill';

export function redirectSystemPath({ path, initial }) {
  // app/index.tsに<Redirect />だけ書いて、とりあえずそこに飛ばすと動いたのでworkaroundとしてやってる
  if (initial) return '/'
  ...
}

app/index.tsx

// 初期ルートにredirectするだけの '/' を作ったら動いた
export default function Index() {
  return <Redirect href="/home" />;
}

Tips: Query Parameterのencoder/decoderを実装しといたほうがいい

  • 一般的にQuery Parameterはdecodeして渡すもの
  • Query Parameterをdecodeしてから使うようにしたほうがいい
    • WebView Screenに、外部からURLをQueryParameterで渡したい時などにやらかしがち
const params = useLocalSearchParams() as EncodedScreenParams;
const value = decodeURIComponent(params['value'])

Tips: 特定URLはアプリで開かせたくないときの対応

「全部のURLを起動し、アプリに存在しない画面はWebViewで開く」のが現実的

export const redirectSystemPath = ({ path, initial }: { path: string; initial: boolean }) => {
  return isExpectedPath(path) ? path : `/webview?url=${encodeURIComponent(path)}`;
})

Tips: 認証があるアプリケーションについて

Routingの設計どうしたらいいか

  • 認証フローがExpoのドキュメント通りに組んであるとラク
  • 初手は認証済みのトップページに飛ばして、_layoutで(Web|Deep) Linkのhandlingをすると良さそう
    • ログインしてなかったユーザーが、ログインできたときに、(Web|Deep) Linkで開こうとしてたページを開いてくれて親切

Tips: 認証があるアプリケーション

意図しないApp Linkを雑に /webview で開くと困ることがある

  • 認証済みのRoutingに /webview を設置してあるという前提
  • 意図しないWeb Linkを雑に /webview に投げつけると、未認証時にURLをアプリで開けないということが起こる
    • 「新規会員登録」「問い合わせ」「パスワードリセット」などのURLを開こうとすると、未認証トップに飛んでしまう
    • 一応、URLをブラウザにURLを直貼りするとブラウザで回遊できるが、リテラシーが試される
  • Routing処理と別で「特定のpathが来たら、 WebBrowser で起動する」という処理を入れたほうが良さそう
    • ただし、雑にWebBrowserを使うと、Androidでバグるので次のTipsを参照

Tips: 認証があるアプリケーション

export const redirectSystemPath = ({ path, initial }: { path: string; initial: boolean }) => {
  const isAppUniversalLink = !isDeepLink(path);

  if (isAppUniversalLink) {
    const shouldOpenWebBrowser = await shouldOpenWebBrowserAsync(path);
    if (shouldOpenWebBrowser) {
      await openBrowserAsync(path); // WebBrowserのWraper
    }
  }

  ...
})

Tips: AndroidでWebBrowserを使う時の注意

  • AndroidのWeb Linkを設定し、WebBrowserで開こうとすると、無限ループする
    • Web → +native-intent.tsx → WebBrowserがWeb開く → Webからアプリに飛ばされる → ...
  • そのため「このURLはWebBrowserで開け」と明示的に指定してやる必要がある
export const openBrowserAsync = async (url) => {
  // App Linkを設定していると、自分のアプリでURLを開こうとするため、明示的にブラウザのpackage nameを指定している
  const browserPackageName =
    Platform.OS === 'android'
      ? (await WebBrowser.getCustomTabsSupportingBrowsersAsync()).preferredBrowserPackage
      : undefined;

  await WebBrowser.openBrowserAsync(url.toString(), {
    dismissButtonStyle: 'close',
    showTitle: true,
    toolbarColor: ColorPallets.white,
    browserPackage: browserPackageName,
  });
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment