React Native(Expo)にUniversal Linksを実装して、Webからアプリに自動遷移させる!【後編】

2022/05/14に公開

前回の記事の続編です。

前回の記事では、AndroidやiOSといったプラットフォーム側でUniversal Links対応することで、URLをスマホで開いたときに対応するアプリに遷移するところまで設定できました。

本記事ではアプリ側でUniversal Linksをハンドリングし、狙った画面に遷移するところまで進めようと思います。
※なお、単にアプリに飛ぶだけで良ければ前回の記事までの内容で問題ないはずです。

目次

  • アプリ側のUniversal Links(Deep Links)ハンドリング
    • React Navigationのlinkingを実装
    • ログアウト時、ログイン時の考慮
    • キル状態から起動されたときの考慮
    • 遷移した画面から戻ろうとしたときにどこに戻るかの考慮
    • 遷移しようとした画面が見つからなかったときの考慮
  • 諸注意
    • アプリ内のURLからブラウザに行ってまた戻ってくることはできない
    • すでにサービスをWebブラウザで開いているときに遷移してもアプリには行かない

アプリ側のUniversal Links(Deep Links)ハンドリング

React Navigationのlinkingを実装

適切な画面に自動遷移させるため、React Navigationが絡んできます。

そこで、React NavigationのNavigationContainerlinkingという設定を加えます。

NavigationProvider.tsx
    <NavigationContainer
      ref={navigationRef}
      fallback={<Text style={{ color: '#ccc' }}>Loading...</Text>}
      linking={{
        prefixes: __DEV__
          ? [Linking.createURL('/')]
          : [getConfig().serviceURL],
        config: universalLinkHandles, // ここが重要!
      }}
      onReady={onReady}
      onStateChange={onStateChange}
    >
      <Hoge>
      {...以下略...}
      </Hoge>
    </NavigationContainer>

linkingconfigキーに指定するのはオブジェクトで、どのURLに対してどの画面に遷移させるのかをマッピングします。

https://reactnavigation.org/docs/configuring-links

マナリンクの場合、Navigatorが割とネストしてしまっているので、正しい記法を見つけるのに苦労しました。

/**
 * https://reactnavigation.org/docs/configuring-links
 */
export const universalLinkHandles = {
  screens: {
    Main: {
      screens: {
        SearchTeachersNavigator: {
          initialRouteName: 'IndexScreen',
          screens: searchTeacherScreens,
        },
      },
    },
  },
} as LinkingOptions<Routes>['config'];

const searchTeacherScreens: Partial<
  Record<keyof SearchTeacherRoutes, PathConfig<SearchTeacherRoutes> | '*'>
> = {
  // TeacherDetailというScreen ComponentはteacherIdというPropsを必要としている
  // Component側ではprops.route.params.teacherIdでアクセス可能
  TeacherDetail: {
    path: 'teacher/:teacherId',
    exact: true,
  },
  SearchResult: {
    path: 'articles/:categoryId',
    exact: true,
    parse: {
      categoryId: (categorySlug: string) => findCategoryFromMaster(categorySlug).id,
    },
    stringify: {
      categoryId: (id: number) => findCategoryById(id).slug,
    },
  },
  // 以下略
  // 見つからなかったらトップに戻す
  IndexScreen: '*',
};

ポイントは何点かありますが以下のとおりです。

  • ネストしているNavigatorはすべて指定していく
  • URLに含まれている文字列が直接Componentで必要としているものではなかった場合、parsestringifyで対応付けしなければならない
  • 頑張ればそれなりに型は当たる
  • エミュレーターでUniversal Linksによるハンドリングもテスト可能。以下のようにLinking.createURLを使う
        prefixes: __DEV__
          ? [Linking.createURL('/')]
          : [getConfig().serviceURL],

ログアウト時、ログイン時の考慮

遷移後の画面がログイン必須の画面だった場合、アプリがログアウト状態でUniversal Linksを使って遷移できてしまうと認証エラーを踏んでしまう点に注意しましょう。マナリンクアプリでは、未認証の場合はそもそもまるっとそれらの画面を含むNavigatorStack.Navigatorに追加しない(&&を使ってConditionalに追加するようにした)ようにして対応しました。

キル状態から起動されたときの考慮

アプリがキルされているときにUniversal Linksから起動されたときに意図した画面に遷移できるかどうかもテスト項目です。もし該当する画面が認証必須で認証チェックに読み込み時間が必要なのであれば、読み込み中はStack.Navigatorですら囲わないシンプルなSplash Screenを返すように実装しておくことで、React Navigationによるハンドリング判定を遅らせることができます。

遷移した画面から戻ろうとしたときにどこに戻るかの考慮

Universal Linksで遷移する画面が、通常の遷移では複数のナビゲーションを経て到達する画面の場合、Universal Linksで遷移後に画面左上から戻るボタンをクリックしたときにどこに戻すべきか判断が難しいです。
細かいことは制御できませんが、一律で特定の画面を初期画面として設定できます。

          initialRouteName: 'IndexScreen',

上記の場合、Universal Linksで遷移する画面の左上から戻るとIndexScreenが開きます。

遷移しようとした画面が見つからなかったときの考慮

ルーティング設定の最後に以下のように指定することで、遷移しようとしたが対象の画面が見つからなかった、すなわちAndroidやiOS側の設定とReact Navigation側の設定に乖離があった際の保険を実装できます。

IndexScreen: '*',

こういうふうにエッジケースを考えていくとキリがなかったのですが、概ね上記のようなことを考慮、テストできていれば問題なかったかと思います。

諸注意

さて、以上でReact NativeアプリにExpoとReact Navigationを使っている場合のUniversal Links(Deep Links)の実装方法の解説を終わります。
最後に諸注意を数点挙げます。

  • アプリ内のURLからブラウザに行ってまた戻ってくることはできない

アプリ内にユーザーの誰かがサービスのURLを投稿しているのをレンダリングしている場合などで、ユーザーがアプリ内URLをクリックしてブラウザが開いたとき、そこからまたアプリに戻ってくることはありませんでした。

  • すでにサービスをWebブラウザで開いているときに遷移してもアプリには行かない

すでにブラウザ上で、サービス内に滞在しているとき、画面遷移ではアプリには飛びませんでした(SPAではもちろんのこと、MPAでも同様)。あくまで、Google検索から流入したときやSlackやLINEなど他アプリからリンクをクリックしたときにアプリに自動遷移するのがUniversal Linksの挙動のようです。


前後編に分かれて大ボリュームの記事でした。振り返ってみると文献が少なく孤独な戦いだったので、同じ経験をした方がいればお話しましょう・・・!

マナリンク Tech Blog

Discussion