7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Flutter】Firebase Dynamic LinksからApp Links/Universal Linksへの置き換え攻略ガイド

Posted at

Firebase Dynamic Linksの廃止が予定されているため、Flutterアプリでのディープリンク機能を App Links(Android)/Universal Links(iOS) に変更する方法を解説します。
※ここではFlutterのapp_linksパッケージを使用します。

※この記事は2024年9月24日時点での情報をもとに作成されています。

なぜFlutterでDynamicLinkを置き換えるのか?

Firebase Dynamic Linksが2025年8月25日に廃止される予定であるため、代替手段としてApp Links/Universal Links、カスタムURLスキームなどへの移行が必要となります。

カスタム URL スキームはディープ リンクの許容可能な形式ですが、ユニバーサル リンクを強くお勧めします。ユニバーサル リンクの詳細については、「アプリと Web サイトがコンテンツにリンクできるようにする」を参照してください。

カスタムURLスキームについてはAppleのドキュメントにある通り推奨されていないので、App Links/Universal Linksを用いたやり方を説明します。

また、従来使用されていたuni_linksパッケージですが、pub.devでは「discontinued(中止)」と記載されており、代替としてapp_linksパッケージを推奨しているのでこれを使います。

参考: Firebase Dynamic Linksの廃止に関する詳細は、Firebase公式アナウンス

独自ドメインを使用する場合

独自ドメインを使用する場合について

独自のドメインをお持ちで、そのドメイン上に必要なファイルを配置できる場合、Firebase Hostingを使用せずにApp LinksやUniversal Linksを設定することが可能です。その場合、以下の手順は不要になります。

  • Firebase CLIのインストールとプロジェクトの初期化: 不要
  • GitHub ActionsでFirebase Hostingへのデプロイを設定: 不要
  • Firebase Hostingの設定ファイル更新: 不要

本記事では、Firebase Hostingを使用する方法を中心に、使用しない方法でも解説しています。

DynamicLinkからApp Links(Android)/Universal Links(iOS)への移行例

移行例はこちら
項目
ディープリンク https://example.web.app/welcome
Android アプリ com.example.android
Apple アプリ com.example.ios
長いダイナミックリンク https://example.page.link/?link=https://example.web.app/welcome&apn=com.example.android&isi=123456789&ibi=com.example.ios
短いダイナミックリンク https://example.page.link/m9Mm

項目
新しいディープリンク https://your-project-domain.web.app/welcome
Android アプリ com.example.android
Apple アプリ com.example.ios

実現したいこと

App LinksとUniversal Linksを使用して、
アプリがインストールされている場合に特定のURLを踏むとアプリ内の特定の画面に遷移するようにします。

Firebase Dynamic Linksのように、アプリがインストールされていない場合にストアに遷移する機能はありません。そのため、その場合は別途対応が必要です。

アプリ未インストール時の対応方法の例

アプリ未インストール時の対応方法の例はこちら

アプリがインストールされていない場合にユーザーを適切に誘導するための方法として、以下のような対応が考えられます。

  • カスタムウェブページの作成: リンク先をウェブページに設定し、そのページでユーザーの環境を判定して適切なストアへのリンクを表示します。
  • Deferred Deep Linkingの利用: 一部のサードパーティサービスでは、アプリ未インストール時にストアへの遷移とインストール後のディープリンクを実現する機能を提供しています。
  • Firebase Dynamic Linksの代替サービスを利用: Firebase Dynamic Linksが廃止されるため、他のディープリンクサービス(例: Branch.io、Adjustなど)を利用することも検討できます。

前提条件

  • npmのインストール: Firebase CLIのインストールに必要です。
  • Flutter開発環境の構築: Flutter SDK、Dart SDK、各プラットフォームのセットアップ。
  • Firebaseプロジェクトの設定: Firebaseコンソールでプロジェクトを作成済みであること。
  • GitHubリポジトリの用意: コードはGitHubで管理し、Pull Request(プルリクエスト)でプレビューが自動デプロイされる環境。
  • デプロイフローの策定: メインブランチへのマージ後、GitHub Actionsを使用して自動的に本番環境へデプロイするフロー、または手動でデプロイするフローを採用。

動作確認方法

動作確認方法はこちら

iOS

シミュレータで以下のコマンドを実行します。

/usr/bin/xcrun simctl openurl booted "https://your.domain.com/yourpath?param=value"
  • ポイント:

    • Universal Linksをテストするために、シミュレータにリンクを送信します。

    • クエリパラメータ付きのURLもテストし、アプリが正しく遷移するか確認します。

    • アプリがフォアグラウンド、バックグラウンド、未起動の各状態でテストします。

Android

以下のコマンドを実行します。

adb shell am start -a android.intent.action.VIEW -d "https://your.domain.com/yourpath?param=value"
  • ポイント:

    • ADBコマンドでリンクをシミュレートします。

    • クエリパラメータ付きのURLもテストし、アプリが正しく遷移するか確認します。

    • アプリの状態に応じて挙動が異なるため、各状態でテストします。

メモ帳アプリを使用したデバッグ方法(Android)

Android端末でApp Linksをテストする際、メモ帳アプリを使用してリンクをタップする方法があります。

手順

  1. テストしたいリンクをコピー

    • テストしたいURL(例: https://your.domain.com/yourpath?param=value)をコピーします。
  2. メモ帳アプリを開く

    • Android端末にデフォルトでインストールされているメモ帳アプリ(または任意のテキストエディタ)を開きます。
  3. リンクを貼り付け

    • 新しいメモを作成し、コピーしたリンクを貼り付けます。
  4. リンクをタップ

    • 貼り付けたリンクをタップします。
  5. アプリの挙動を確認

    • 正しく設定されていれば、アプリが起動し、指定された画面に遷移します。

ポイント

  • ブラウザを経由しないため、App Linksの動作を正確にテストできる
  • ユーザーが他のアプリからリンクをタップした際の挙動を再現可能

手順

1. Firebase Hostingを使用する場合の設定手順

Firebase Hostingを使用する場合の設定手順はこちら

Firebase CLIのインストール

まず、Firebase CLIをローカルのPCにインストールします。

npm install -g firebase-tools

プロジェクトの初期化

Firebase Hostingを利用するために、プロジェクトディレクトリを作成し、そこでFirebaseの初期化を行います。

mkdir your_project_name
cd your_project_name
firebase init hosting

初めてfirebase init hostingを実行すると、ターミナルにいくつかの英語のプロンプトが表示されます。以下、具体的なプロンプトとその対応方法を説明します。

  1. Firebase CLIへようこそ

    === Hosting Setup
    
  2. 既存のプロジェクトを使用する

    Please select an option:
    > Use an existing project
      Create a new project
      Add Firebase to an existing Google Cloud Platform project
    

    対応: 矢印キーで「Use an existing project」を選択し、Enterキーを押します。

  3. Firebaseプロジェクトを選択

    Select a default Firebase project for this directory:
    > your-firebase-project-id (Your Firebase Project)
    

    対応: 使用したいFirebaseプロジェクトを選択してEnterキーを押します。

    トラブルシューティング: プロジェクトIDが表示されない場合は、後述の「トラブルシューティング」を参照してください。

  4. 公開ディレクトリの設定

    What do you want to use as your public directory? (public)
    

    対応: デフォルトのpublicを使用する場合はEnterキーを押します。カスタムのディレクトリを使用したい場合はディレクトリ名を入力します。

  5. シングルページアプリケーションかどうか

    Configure as a single-page app (rewrite all urls to /index.html)? (y/N)
    

    対応:

    • 判断基準:

      • シングルページアプリケーション(SPA)の場合: React、Vue.js、Angular、Flutter Webなどのフロントエンドフレームワークを使用しており、ルーティングをクライアントサイドで処理している場合。
      • マルチページアプリケーション(MPA)の場合: 各ページが独立しており、サーバーサイドでルーティングを処理している場合。
    • 対応方法:

      • SPAであればyを入力してEnterキーを押します。
      • MPAであればNを入力するか、そのままEnterキーを押します(デフォルトでN)。
  6. 自動的にファイルを上書きするか

    File public/index.html already exists. Overwrite? (y/N)
    

    対応:

    • 上書きしたい場合: yを入力してEnterキーを押します。
    • 上書きしたくない場合: Nを入力するか、そのままEnterキーを押します。

    ※ 初期状態ではpublicディレクトリが存在しないため、このプロンプトは表示されない場合があります。

  7. 完了メッセージ

    ✔  Firebase initialization complete!
    

GitHubへのコードのアップロード

  1. GitHubリポジトリの作成: GitHub上で新しいリポジトリを作成します。

  2. ローカルリポジトリの初期化:

    git init
    git remote add origin https://github.com/your_username/your_repository.git
    
  3. ファイルのコミットとプッシュ:

    git add .
    git commit -m "Initial commit"
    git push -u origin main
    

Firebase認証情報の設定(FIREBASE_TOKEN)

Firebase CLIがFirebaseプロジェクトにアクセスするために**FIREBASE_TOKEN**を設定する必要があります。

FIREBASE_TOKENの取得と設定
  1. Firebase CLIでトークンを取得

    firebase login:ci
    

    このコマンドを実行すると、ブラウザが開き、Firebaseへのログインを求められます。ログイン後、ターミナルにトークンが表示されます。

    ✔  Success! Use this token to login on a CI server:
    
    1//0d-ExampleFirebaseToken
    
  2. GitHubリポジトリのシークレットに追加

    GitHubリポジトリの「Settings」→「Secrets and variables」→「Actions」で、新しいシークレットを追加します。名前はFIREBASE_TOKENとします。

    • シークレット名: FIREBASE_TOKEN
    • 値: 先ほど取得したトークン(例: 1//0d-ExampleFirebaseToken

2. 必要なファイルの配置

※アプリ側ではなくFirebase Hostingの設定です。

必要なファイルの配置はこちら

Firebase Hosting、独自のウェブサーバーやホスティングサービスを使用して、 assetlinks.json (Android用)や apple-app-site-association (iOS用)ファイルを適切な場所に配置します。

Androidの場合

  • assetlinks.jsonを以下のパスに配置します。

    https://your.domain.com/.well-known/assetlinks.json
    

iOSの場合

  • apple-app-site-associationを以下のパスに配置します。

    https://your.domain.com/.well-known/apple-app-site-association
    
  • 注意: apple-app-site-associationファイルには拡張子がありません。

  • また、Content-Typeapplication/jsonに設定する必要があります。

3. ウェブサーバーの設定(Firebase Hostingを使用しない場合)

ウェブサーバーの設定はこちら
  • 正しいContent-Typeの設定: サーバーがassetlinks.jsonapple-app-site-associationを提供する際に、適切なContent-Typeヘッダーを設定する必要があります。

    • assetlinks.json: application/json
    • apple-app-site-association: application/json(ただし、ファイル名に拡張子がないため、サーバーの設定が必要な場合があります)
  • HTTPSでの配信: App LinksとUniversal Linksは、HTTPSプロトコルでの通信が必要です。SSL証明書を設定し、ドメインでHTTPSが有効になっていることを確認してください。

4. GitHub ActionsでFirebase Hostingへのデプロイを設定(Firebase Hostingを使用する場合)

※アプリ側ではなくFirebase Hostingの設定です。

Firebase Hostingへのデプロイ方法はこちら

Firebase Hostingを使用しない場合、このセクションは不要です。
※ただしデプロイする必要はあるので忘れないようにしましょう。

デプロイ方法の選択

Firebase Hostingへのデプロイには、以下の2つの方法があります。プロジェクトの運用フローに合わせて選択してください。

  1. メインブランチへのマージ時に自動デプロイ: 開発フローでdevelopブランチなどを使用し、mainブランチが常に本番環境と同期している場合に適しています。

  2. 手動でデプロイ: 本番環境へのデプロイをより慎重に行いたい場合や、特定のタイミングでのみデプロイしたい場合に適しています。

方法1: メインブランチへのマージ時に自動デプロイ

メインブランチにコードがマージされたとき、自動的にFirebase HostingへデプロイするGitHub Actionsのワークフローを設定します。

  1. firebase-hosting-merge.ymlファイルの作成

    .github/workflows/firebase-hosting-merge.ymlを作成し、以下の内容を記述します。

    name: Deploy to Firebase Hosting on merge
    
    on:
      push:
        branches:
          - main
    
    jobs:
      build_and_deploy:
        runs-on: ubuntu-latest
    
        steps:
        - name: Checkout the repository
          uses: actions/checkout@v2
    
        - name: Install Node.js
          uses: actions/setup-node@v2
          with:
            node-version: '20'
    
        - name: Install Firebase CLI
          run: npm install -g firebase-tools
    
        - name: Deploy to Firebase Hosting
          run: firebase deploy --only hosting --project your-firebase-project-id
          env:
            FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
    
    • ポイント:

      • onセクションでmainブランチへのプッシュをトリガーしています。
      • **your-firebase-project-id**はFirebaseプロジェクトのIDに置き換えてください。
      • **FIREBASE_TOKEN**はGitHubシークレットに設定したFirebaseのトークンです。
  2. ワークフローのコミットとプッシュ

    git add .github/workflows/firebase-hosting-merge.yml
    git commit -m "Add deployment workflow for main branch"
    git push
    

方法2: 手動でデプロイ

必要なタイミングで手動でデプロイを実行するGitHub Actionsのワークフローを設定します。

  1. firebase-hosting-manual.ymlファイルの作成

    .github/workflows/firebase-hosting-manual.ymlを作成し、以下の内容を記述します。

    name: Manual Deploy to Production
    
    on:
      workflow_dispatch:
    
    jobs:
      build_and_deploy:
        runs-on: ubuntu-latest
    
        steps:
        - name: Checkout the repository
          uses: actions/checkout@v2
    
        - name: Install Node.js
          uses: actions/setup-node@v2
          with:
            node-version: '20'
    
        - name: Install Firebase CLI
          run: npm install -g firebase-tools
    
        - name: Deploy to Firebase Hosting
          run: firebase deploy --only hosting --project your-firebase-project-id
          env:
            FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
    
    • ポイント:

      • onセクションでworkflow_dispatchを指定し、手動でワークフローをトリガーできるようにしています。
      • GitHubリポジトリのActionsタブから、このワークフローを選択し、手動で実行できます。
  2. ワークフローのコミットとプッシュ

    git add .github/workflows/firebase-hosting-manual.yml
    git commit -m "Add manual deployment workflow"
    git push
    

5. Androidの設定

※アプリ側ではなくFirebase Hostingの設定です。

Androidの設定はこちら

assetlinks.jsonの作成

assetlinks.jsonを以下の内容で作成します。

[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "your.package.name",
      "sha256_cert_fingerprints": ["YOUR_SHA256_CERT_FINGERPRINT"]
    }
  }
]
  • 注意点:

    • namespaceは必ず"android_app"と記述します。アプリ名ではありません
  • package_name: アプリのパッケージ名(例: com.example.app

  • sha256_cert_fingerprints: アプリのSHA256証明書フィンガープリント

SHA256フィンガープリントの取得方法
  1. デバッグ用キーストアの場所

    デフォルトのデバッグ用キーストアは以下の場所にあります。

    • macOS/Linux: ~/.android/debug.keystore
    • Windows: C:\Users\your_username\.android\debug.keystore
  2. SHA256フィンガープリントの取得コマンド

    keytool -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore -storepass android -keypass android
    
    • -alias: デバッグキーのエイリアスは通常androiddebugkey
    • -storepass-keypass: デフォルトのパスワードはandroid
  3. 本番環境でのSHA256フィンガープリント

    • リリース用キーストアを使用している場合は、リリース用キーストアファイルのパスとパスワードを指定します。

      keytool -list -v -keystore your_release_keystore.jks -alias your_alias -storepass your_storepass
      
    • Google Play App Signingを使用している場合:

      • Google Play ConsoleでSHA256証明書フィンガープリントを取得できます。

      • 手順:

        1. Google Play Consoleにログイン

        2. 対象のアプリを選択

        3. 左側のメニューから「リリース」→「セットアップ」→「アプリの署名」を選択

        4. 「アプリの署名証明書」または「アップロード証明書」のSHA256フィンガープリントを確認

      • 注意: 本番環境では、デバッグ用のSHA256ではなく、リリース用のSHA256を使用してください。

  4. 出力からSHA256フィンガープリントを確認

    Certificate fingerprints:
             SHA1:  ...
             SHA256:  01:23:45:67:89:AB:CD:EF:...
    

    SHA256の値をassetlinks.jsonに使用します。

設定ファイル更新

firebase.jsonに以下を追加し、assetlinks.jsonが正しいContent-Typeで提供されるようにします。

{
  "hosting": {
    // 既存の設定
    "headers": [
      {
        "source": "/.well-known/assetlinks.json",
        "headers": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ]
      }
    ]
  }
}

デプロイ

GitHubにコードをプッシュし、プルリクエストを作成します。マージ後、GitHub Actionsが自動的にデプロイを行います。

6. iOSの設定

※アプリ側ではなくFirebase Hostingの設定です。

iOSの設定はこちら

apple-app-site-associationの作成

apple-app-site-associationを以下の内容で作成します。
※公式には違う形式のものが書かれていましたが、それではうまくいかなかったのでこれでやってみてください。無理なら公式のものでいけるかもしれません。

{
  "applinks": {
      "details": [
           {
             "appIDs": [ "{TeamID}.{package名}" ],
             "components": [
               {
                  "#": "no_universal_links",
                  "exclude": true,
                  "comment": "このフラグメントが 'no_universal_links' のURLは、Universal Linkとして開かれません。"
               },
               {
                  "/": "/abcd/*",
                  "comment": "このパターンは、'/abcd/'で始まるURLにマッチします。"
               }
             ]
           }
       ]
   },
   "webcredentials": {
      "apps": [ "{TeamID}.{package名}" ]
   },
   "appclips": {
        "apps": ["{TeamID}.{package名}"]
    }
}
  • TEAMID: Apple DeveloperのチームID

  • your.bundle.id: アプリのバンドルID(例: com.example.app

公式のやり方はこちら

TEAMID(チームID)の確認方法
  1. Apple Developerのウェブサイトにログイン

  2. 上部メニューの「Membership」をクリック

    • 「Membership Details」ページが表示されます。
  3. 「Team ID」を確認

    • 「Membership Information」セクションに「Team ID」が記載されています。

    • 例: 1A2B3C4D5E

設定ファイル更新

firebase.jsonに以下を追加し、apple-app-site-associationが正しいContent-Typeで提供されるようにします。

{
  "hosting": {
    // 既存の設定
    "headers": [
      {
        "source": "/.well-known/apple-app-site-association",
        "headers": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ]
      }
    ]
  }
}

デプロイ

GitHubにコードをプッシュし、プルリクエストを作成します。マージ後、GitHub Actionsが自動的にデプロイを行います。

7. Flutterアプリ側での受け取り処理の実装(app_linksパッケージ)

Flutterアプリ側での受け取り処理の実装(app_linksパッケージ)はこちら

Flutterアプリでの設定

Flutter側の設定はこちら
1.パッケージのインストール

pubspec.yamlに以下を追加し、app_linksパッケージ(バージョン 6.3.2)をインストールします。

dependencies:
  app_links: ^6.3.2
fvm flutter pub get
2. ディープリンクの受け取り設定

app_linksパッケージを使用して、ディープリンクを受け取るための設定を行います。

import 'package:flutter/material.dart';
import 'package:app_links/app_links.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late final AppLinks _appLinks;
  Uri? _latestUri;

  @override
  void initState() {
    super.initState();
    _appLinks = AppLinks();

    // 初期リンクの取得
    _initDeepLink();

    // リンクストリームのリスナー
    _appLinks.uriLinkStream.listen((Uri? uri) {
      if (!mounted) return;
      setState(() {
        _latestUri = uri;
      });
      // ここでNavigatorを使用して画面遷移などを行う
    });
  }

  Future<void> _initDeepLink() async {
    try {
      final Uri? initialUri = await _appLinks.getInitialAppLink();
      if (initialUri != null) {
        setState(() {
          _latestUri = initialUri;
        });
        // ここでNavigatorを使用して画面遷移などを行う
      }
    } on Exception catch (e) {
      print('Failed to get initial link: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    // _latestUriを使用して適切な画面を表示
    return MaterialApp(
      onGenerateRoute: (settings) {
        final uri = Uri.parse(settings.name ?? '');
        switch (uri.path) {
          case '/yourpath':
            final param = uri.queryParameters['param'];
            return MaterialPageRoute(
              builder: (_) => YourScreen(param: param),
            );
          // 他のパスの処理
          default:
            return MaterialPageRoute(
              builder: (_) => NotFoundScreen(),
            );
        }
      },
      home: Scaffold(
        body: Center(
          child: Text('Deep Link URI: $_latestUri'),
        ),
      ),
    );
  }
}
  • ポイント:

    • AppLinksを使用して、アプリが起動されたときやバックグラウンドから復帰したときにリンクを受け取ります。
    • 受け取ったリンクに応じて、Navigatorやルーティングパッケージを使用して画面遷移を行います。

iOSの設定

iOSの設定はこちら
Flutterのデフォルトのディープリンクを無効化

ios/Runner/Info.plistに以下を追加します。

<key>FlutterDeepLinkingEnabled</key>
<false/>
(必要あれば)Deployment TargetをiOS13以上にする

iOS 13以降では、SceneDelegateが導入され、アプリのライフサイクル管理が変更されました。
最低対応バージョン(Deployment Target)をiOS 13以上であれば分岐を機にする必要がないのでできるだけ新しいDeployment Targetに設定することをおすすめします。

※ユーザーがユニバーサル リンクをクリックした時点ですでにアプリが開いている場合、SceneDelegate 内のユニバーサル リンク コールバックが呼び出されます。

最低対応バージョンの設定方法:

  1. Xcodeでプロジェクトを開く: ios/Runner.xcworkspaceを開きます。

  2. プロジェクト設定の編集: 左側のプロジェクトナビゲーターでRunnerを選択し、TARGETSRunnerを選択します。

  3. 「General」タブの「Deployment Info」セクションで、「Deployment Target」をiOS 13.0以上に設定します。

AppDelegate.swiftとSceneDelegate.swiftの編集

AppDelegate.swift:

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

SceneDelegate.swift:

import UIKit
import Flutter
import app_links

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
  var window: UIWindow?

  func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    guard let windowScene = scene as? UIWindowScene else { return }

    let flutterEngine = (UIApplication.shared.delegate as! FlutterAppDelegate).flutterEngine
    let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)

    window = UIWindow(windowScene: windowScene)
    window?.rootViewController = flutterViewController
    window?.makeKeyAndVisible()

    // 起動時のディープリンクを処理
    if let userActivity = connectionOptions.userActivities.first {
      handleUserActivity(userActivity)
    }
  }

  func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
    // バックグラウンドからのディープリンクを処理
    handleUserActivity(userActivity)
  }

  private func handleUserActivity(_ userActivity: NSUserActivity) {
    if let url = userActivity.webpageURL {
      AppLinks.shared.handleLink(url: url)
    }
  }
}
  • ポイント:

    • SceneDelegateを使用することで、iOS 13以降のライフサイクルイベントに対応します。
    • AppLinks.shared.handleLink(url:)を使用して、受け取ったURLをFlutter側に渡します。
Associated DomainsのCapabilitiesを追加

CapabilitesにAssociated Domainsを追加してください。

iOS 13までは、ルートドメイン(例: example.com)のみをAssociated Domainsに指定していれば、サブドメイン(例: www.example.com)でもUniversal Linksが機能していました。しかし、iOS 14以降では、サブドメインごとに正確に指定する必要があります。

設定手順

  1. Associated DomainsのCapabilitiesを追加

    XcodeのCapabilitiesタブで、Associated Domainsを有効にしてください。

  2. Associated Domainsにサブドメインを追加

    iOS 14以降では、サブドメイン(例: www.example.com)を個別に指定する必要があります。以下のように、各ドメインを正確に追加します。

    applinks:example.com
    applinks:www.example.com
    

    特に、wwwなどのサブドメインを含む場合、iOS 14以降ではこれを忘れるとUniversal Linksが機能しなくなります。

  3. webcredentialsの設定

    Webのログイン認証にwebcredentialsを使用する場合も、同様に正確なドメインを指定します。

    webcredentials:example.com
    webcredentials:www.example.com
    

参考: Apple公式ドキュメント

iOS 14以降のUniversal Linksにおけるapple-app-site-association取得の注意点

iOS 13までは、アプリは直接ドメインの.well-knownパスからapple-app-site-association(AASA)ファイルを取得していました。しかし、iOS 14からは、AppleのCDNを経由して取得するように変更されました。この変更により、特定の条件下でUniversal Linksが機能しない問題が発生することがあります。

  • IP制限があるStaging環境:AppleのCDNがアクセスできないと、AASAファイルを取得できず、Universal Linksが動作しません

この問題を回避するために、Associated Domains設定で**mode=developer**を指定することで、開発端末ではAppleのCDNを経由せずに直接ドメインからAASAファイルを取得することが可能です。

applinks:example.com?mode=developer

mode=developerを使う場合の注意点は以下の通りです。

  • このモードは、開発者の端末でのみ有効です。
  • ドキュメントには、使用するデバイスで開発者モード上でNetworkingのAssociated Domains Developmentの設定をONにする必要があると記載されています。

これにより、Staging環境でIP制限があり、AppleのCDN経由ではUniversal Linksがテストできない場合、この設定を使うことで、Staging環境でも直接ドメインからAASAファイルを取得してテストが可能になります。

Androidの設定

Androidの設定はこちら
  • Flutterのデフォルトのディープリンクを無効化: android/app/src/main/AndroidManifest.xmlで以下を追加します。
<meta-data android:name="flutter_deeplinking_enabled" android:value="false" />
  • MainActivity.javaまたはMainActivity.ktの編集:
import android.content.Intent
import io.flutter.embedding.android.FlutterActivity
import com.llfbandit.app_links.AppLinksPlugin

class MainActivity: FlutterActivity() {
  override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
    // Intentからリンクを取得してハンドリング
    AppLinksPlugin.Companion.getInstance().handleIntent(intent)
  }
}
  • インテントフィルタの追加: AndroidManifest.xml内の<activity>タグに以下を追加します。
<intent-filter android:autoVerify="true">
    <action android:name="android.intent.action.VIEW"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <category android:name="android.intent.category.BROWSABLE"/>
    <data android:scheme="https"
          android:host="your.domain.com"
          android:pathPrefix="/yourpath"/>
</intent-filter>
  • ポイント:

    • **android:autoVerify="true"**を設定することで、リンクの自動検証が有効化します。

    • **android:hostandroid:pathPrefix**で受け取るリンクのパターンを指定します。

    • **onNewIntent**メソッドでリンクをハンドリングします。

  • android:autoVerify="true"がない場合の影響

    • 問題点:

      • 自動的なリンクの検証が行われない: アプリのインストール時にリンクの所有権が検証されず、信頼性が低下します。

      • ユーザーにアプリ選択ダイアログが表示される: リンクをタップした際に、どのアプリで開くかをユーザーに選択させるダイアログが表示されます。

      • アプリがデフォルトで起動しない: 他のアプリ(ブラウザなど)が優先され、期待した動作にならないことがあります。

    • 結論: android:autoVerify="true"を設定することで、アプリリンクの信頼性とユーザーエクスペリエンスを向上させることができます。

  • pathPrefixの指定について

    • 問題点: android:pathPrefixを指定しないと、App Linksが正しく動作しない場合があります。

    • 複数のパスに対応する方法:

      • 同じintent-filter内で複数の<data>タグを使用:

        <intent-filter android:autoVerify="true">
            <action android:name="android.intent.action.VIEW"/>
            <category android:name="android.intent.category.DEFAULT"/>
            <category android:name="android.intent.category.BROWSABLE"/>
            <data android:scheme="https"
                  android:host="your.domain.com"
                  android:pathPrefix="/path1"/>
            <data android:scheme="https"
                  android:host="your.domain.com"
                  android:pathPrefix="/path2"/>
            <data android:scheme="https"
                  android:host="your.domain.com"
                  android:pathPrefix="/path3"/>
        </intent-filter>
        
      • 複数のintent-filterを使用:

        <intent-filter android:autoVerify="true">
            <action android:name="android.intent.action.VIEW"/>
            <category android:name="android.intent.category.DEFAULT"/>
            <category android:name="android.intent.category.BROWSABLE"/>
            <data android:scheme="https"
                  android:host="your.domain.com"
                  android:pathPrefix="/path1"/>
        </intent-filter>
        <intent-filter android:autoVerify="true">
            <action android:name="android.intent.action.VIEW"/>
            <category android:name="android.intent.category.DEFAULT"/>
            <category android:name="android.intent.category.BROWSABLE"/>
            <data android:scheme="https"
                  android:host="your.domain.com"
                  android:pathPrefix="/path2"/>
        </intent-filter>
        
    • ポイント:

      • 同じintent-filter内で複数のパスを指定することで、簡潔に複数のパスに対応できます。

      • 異なるintent-filterを使用すると、各パスごとに異なる設定や処理を行うことが可能です。

    • どちらを使うべきか:

      • 簡単に複数のパスをサポートしたい場合: 同じintent-filter内で複数の<data>タグを使用する方法が便利です。

      • 詳細な制御が必要な場合: 複数のintent-filterを使用して、各パスごとに個別の設定を行います。

  • minSdkVersionの設定

    一部のディープリンク機能は特定のAPIレベル以上でサポートされています。必要に応じて、android/app/build.gradleminSdkVersionを変更します。

    defaultConfig {
        // 他の設定
        minSdkVersion 21 // 必要に応じて変更
    }
    

ProGuardの設定

  • ProGuardを使用している場合proguard-rules.proに以下を追加します。
-keep class com.llfbandit.app_links.** { *; }
  • ポイント:

    • app_linksパッケージのクラスが削除されないようにします。

8. 【任意】 環境ごとの設定(本番環境と開発環境)

環境ごとの設定(本番環境と開発環境)はこちら

本番環境と開発環境を分けて設定することが一般的です。ここでは、環境ごとに設定を分ける方法を説明します。

8-1. Firebaseプロジェクトを分ける

  • 本番環境用開発環境用でFirebaseプロジェクトを別に作成します。これにより、それぞれの環境でデータや設定を分離して管理できます。

8-2. 異なるドメインを使用する

  • 本番環境にはyour.domain.com、開発環境にはdev.your.domain.comといった具合にドメインを分けます。これにより、各環境でURLが異なるため、誤って本番環境にアクセスすることを防げます。

8-3. assetlinks.jsonapple-app-site-associationの内容を環境ごとに変更

  • 環境ごとに異なるアプリのパッケージ名バンドルIDを使用している場合、それに応じてこれらのファイルも変更する必要があるので忘れずに行いましょう。

8-4. GitHub Actionsで環境ごとにデプロイ先を分ける

  • ブランチ名やタグ名を使用して、どの環境にデプロイするかを制御します。例えば、mainブランチは本番環境に、developブランチは開発環境にデプロイするよう設定します。

9. 【実践】メアドログインで確認メールを送って特定の完了ページに遷移

ここまでで移行自体は完了していますが必要あればご覧ください

メアドでログインした時に確認メールを送信するFlutterのコードの1例は以下になります。

// 認証メールを再送する
Future<void> sendVerificationEmail() async {
  User? user = service.auth.currentUser;
  if(user!=null && !user.emailVerified){
    await user.sendEmailVerification();
  }
}

この確認メールではメールに送る文章を変えることができませんが、URLはFirebase管理画面のFirebase Authenticationのページにて変更することができます。

スクリーンショット 2024-09-23 23.03.19.png

それを今回設定したDeepLinkのURLに設定してください。
これにより、メールにそのリンクが送られます。

もしこのコードをタップして、アプリの完了しましたページに遷移できなかった場合は、URLが正しく受け取れていない可能性がありますので、デバッグして確認してみてください。

私の場合、例えばonGenerateRouteのsetting.nameの中にパスだけでなくクエリが入っているURLが渡されていて一致せずにハンドリングできてなかったです。
その辺も見てみるといいかもしれません。

※startWithにすることで対応しました

トラブルシューティング

1. クエリパラメータ付きのURLで正しく遷移しない

クエリパラメータ付きのURLで正しく遷移しない場合の解決策

問題点:

  • パスにクエリパラメータが付いている場合、ディープリンクが正しく処理されず、画面遷移が行われないことがあります。

原因:

  • FlutterのonGenerateRoutesettings.nameを使用してルーティングを行う際、settings.nameにはパスだけでなくクエリパラメータも含まれるため、期待したルートとマッチしない可能性があります。

解決策:

  1. Uriを解析してパスとクエリを分離する

    onGenerateRoute内でsettings.nameUri.parseを使って解析し、パスとクエリパラメータを取得します。

    Route<dynamic>? onGenerateRoute(RouteSettings settings) {
      final uri = Uri.parse(settings.name ?? '');
    
      switch (uri.path) {
        case '/yourpath':
          // クエリパラメータを取得
          final param = uri.queryParameters['param'];
          // 画面遷移を実行
          return MaterialPageRoute(
            builder: (_) => YourScreen(param: param),
          );
        // 他のパスの処理
        default:
          return MaterialPageRoute(
            builder: (_) => NotFoundScreen(),
          );
      }
    }
    
  2. 正規表現を使用してパスをマッチング

    クエリパラメータを無視してパスだけでルーティングを行う場合、正規表現を使用します。

    Route<dynamic>? onGenerateRoute(RouteSettings settings) {
      final uri = Uri.parse(settings.name ?? '');
      final path = uri.path;
    
      if (RegExp(r'^/yourpath$').hasMatch(path)) {
        // クエリパラメータを取得
        final param = uri.queryParameters['param'];
        // 画面遷移を実行
        return MaterialPageRoute(
          builder: (_) => YourScreen(param: param),
        );
      }
    
      // 他のパスの処理
    
      return MaterialPageRoute(
        builder: (_) => NotFoundScreen(),
      );
    }
    

ポイント:

  • Uri.parseを使用することで、パスとクエリパラメータを簡単に取得できます。

  • **uri.queryParameters**でクエリパラメータをマップとして取得できます。

  • パスのみでルーティングを行い、クエリパラメータは別途処理することで、期待した画面遷移が可能になります。

2. ユーザーにアプリ選択ダイアログが表示されたり、デフォルトアプリになってくれない

android:autoVerify="true"がない可能性があります。

詳細

問題点:

  • android:autoVerify="true"を指定しない場合、以下の問題が発生する可能性があります。

    • 自動的なリンクの検証が行われない: アプリのインストール時にリンクの所有権が検証されず、信頼性が低下します。

    • ユーザーにアプリ選択ダイアログが表示される: リンクをタップした際に、どのアプリで開くかをユーザーに選択させるダイアログが表示されます。

    • アプリがデフォルトで起動しない: 他のアプリ(ブラウザなど)が優先され、期待した動作にならないことがあります。

解決策:

  • android:autoVerify="true"を設定する

    <intent-filter android:autoVerify="true">
        <!-- インテントフィルタの内容 -->
    </intent-filter>
    

ポイント:

  • 信頼性とユーザーエクスペリエンスの向上: android:autoVerify="true"を設定することで、アプリリンクの検証が自動的に行われ、ユーザーはシームレスにアプリへ遷移できます。

3. URLを叩いてもapplication/jsonが返ってこず正しく遷移しない

URLを叩いても`application/json`が返ってこず正しく遷移しない場合の解決策

assetlinks.jsonapple-app-site-associationにアクセスしたときに、Content-Typeapplication/jsonになっていない場合があります。これは、ファイルの拡張子がないためにサーバーが適切なMIMEタイプを設定できないことが原因です。

解決策

Firebase Hostingでは、firebase.jsonの設定でheadersを追加して対応できます。

{
  "hosting": {
    // 既存の設定
    "headers": [
      {
        "source": "/.well-known/assetlinks.json",
        "headers": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ]
      },
      {
        "source": "/.well-known/apple-app-site-association",
        "headers": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ]
      }
    ]
  }
}

4. firebase init時にプロジェクトIDが表示されない

`firebase init`時にプロジェクトIDが表示されない場合の解決策

原因と解決策

  1. Firebaseプロジェクトにfirebase:enabledラベルが付いていない

    • 対策:

      • Google Cloudコンソールのプロジェクトのラベルページで、プロジェクトにfirebase:enabledというラベルが付いていることを確認します。

      • ラベルがない場合は、キーにfirebase、値にenabledを設定してラベルを追加します。

  2. 間違ったアカウントでfirebase loginしている

    • 対策:

      • firebase logoutを実行してログアウトします。

      • 正しいアカウントでfirebase loginを実行し、再度ログインします。

5. アプリを起動すると黒い画面になる

アプリを起動すると黒い画面になる場合の解決策

原因:

  • AppDelegateSceneDelegateのコードに問題がある場合、アプリが正しく起動せず、黒い画面のままになることがあります。

対策:

  • コードの確認:

    • AppDelegate.swiftSceneDelegate.swiftのコードが正しく実装されているか確認します。

    • 特に、windowrootViewControllerの設定が正しいかをチェックします。

  • iOSの最低対応バージョンをiOS 13以上に設定

    • iOS 13未満のバージョンをサポートしない場合、SceneDelegateで分岐する必要がないので、コードの簡素化が可能です。

    • 前述の「iOSの設定」のセクションで、最低対応バージョンの設定方法を参照してください。

  • apple-app-site-associationをそれぞれ1つ1つ個別に設定すると直るかもしれない.

当初、公式に載ってるものをそのまま使っていましたが、以下の形式にしたら直ったので試してみてください。

{
  "applinks": {
      "details": [
           {
             "appIDs": [ "{TeamID}.{package名}" ],
             "components": [
               {
                  "#": "no_universal_links",
                  "exclude": true,
                  "comment": "このフラグメントが 'no_universal_links' のURLは、Universal Linkとして開かれません。"
               },
               {
                  "/": "/abcd/*",
                  "comment": "このパターンは、'/abcd/'で始まるURLにマッチします。"
               }
             ]
           }
       ]
   },
   "webcredentials": {
      "apps": [ "{TeamID}.{package名}" ]
   },
   "appclips": {
        "apps": ["{TeamID}.{package名}"]
    }
}

注意点

  • バックグラウンドと未起動状態での挙動の違い: アプリがバックグラウンドにある場合と、完全に終了している場合でリンクの受け取り方が異なります。両方のケースでテストを行い、適切にハンドリングできているか確認してください。

  • Flutterのデフォルトのディープリンクを無効化する理由: app_linksパッケージを使用する際、Flutterのデフォルトのディープリンクハンドラが干渉するため、無効化が必要です。

  • アプリがインストールされていない場合の対応: App LinksとUniversal Linksでは、アプリがインストールされていない場合にストアへの遷移は行われません。そのため、前述の「アプリ未インストール時の対応方法」を参考に、適切な対応を検討してください。

  • プレビューURLへの対応: プレビュー用のURLでもApp LinksとUniversal Linksが機能するように、アプリとウェブサーバーの設定を調整する必要があります。

まとめ

カスタムURLスキーマの問題点を踏まえ、より安全で信頼性の高いApp Links(Android)とUniversal Links(iOS)を使用して、Firebase Dynamic Linksからの移行手順を解説しました。

Firebase Hostingを使用する方法使用しない方法の両方を紹介し、独自ドメインをお持ちの場合はFirebase Hostingを使用せずに設定できることを説明しました。

また、app_linksパッケージを使用することで、Flutterアプリでのディープリンク処理が可能になります。

Dynamic Linksの廃止に伴い、早めの移行を検討することをおすすめします。

参考資料


この記事は2024年9月24日時点の情報をもとに作成されています。

間違えや補足など、ご指摘ありましたらお気軽にコメントください。

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?