DeepLink機能作成に挑戦
モバイルアプリで、以下の流れを経験したこと、ありませんか?
アプリをインストール
↓
アプリを起動して、フォームからメールアドレスを入力して送信
↓
メールが届く
↓
メール本文のリンクをタップしたら、アプリが起動
↓
登録情報の入力画面が表示される
太字で記載した部分がDeepLinkに関係するものです。
今回はこの機能を実現してみたいと思います。
また私自身の端末がAndroid、ということもあり、iOS関連に関する作業は行っていません。
ローカル編とリリース編
ボリュームが少し大きめのため、2本立てで進めます。
まずはローカル編です。
ローカルで起動したアプリケーションを使って、機能するように作業を進めます。
リリース編では、デプロイしたアプリケーション(apkファイル)を端末にインストールして起動したアプリで機能させる作業になります。
(誤解を招くかもしれないので記載しますが、Google Play Storeへのリリースは実施していません。ローカルに対してどういう単語を用いるか考えた結果、リリース、になりました)
作業の前提
以下ご経験のある方が進めやすい内容だと思います。
- Flutterのアプリ作成とFirebaseの利用(個人開発レベルでOK!)
-
AndroidManifest.xml
ファイルを修正
主な作業内容
ローカル環境でDeepLinkを実現するために主な作業は以下の通りです。
- Firebase Hostingのセットアップ(作業量の調整の関係でこちらで実施)
- AndroidManifest.xmlの修正
- アプリケーションのコードで、WebURLを受け取れる状態を作成
Firebase Hostingのセットアップ
ローカルで動かすだけの場合、ここは必要ありません。
後編(リリース編)の内容とのバランスの関係で、プロジェクト作成をこのタイミングで実施します。
詳細は後編で実施しますが、アプリケーションがhttps
のWebURLを受け付けられるよう、Firebase Hostingにデプロイします。
また、アプリケーションはAndroidデバイス上で動かすものなので、資材をビルドしてそれをデプロイするというよりは、
WebURLとAndroidアプリケーションのMappingで必要な情報をデプロイする上で必要になります。
では進めていきます。
Firebaseでプロジェクトを作成の上、Hostingの画面を開きます。
「始める」とあるので、クリックします。
Firebase CLI のインストール
プロジェクトの初期化
firebase init
を実行すると、何点か聞かれるので回答します。
Hostingの設定を行います。
最初のpublicですが、何も入力せずに、Enterを押せば、勝手にpublic
が設定されます。
残り2項目はN
を入力して進みます。
ここまで進めるとpublic
フォルダ生成されています。
このpublic
フォルダはリリース編で動作確認する際に必要です。
AndroidManifest.xmlの修正
上記記事のディープリンクを受け取るために Android アプリとアプリコードを更新するを参考に記述を追加します。
以下内容の追記が必要です。リンクに記載されたコードとほぼ同じです。
<activity
android:name=".MainActivity"
android:exported="true">
<!-- 略 -->
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</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" />
<!-- local -->
<data
android:scheme="http"
android:host="local.com"
android:pathPrefix="/register"
/>
</intent-filter>
intent-filter
が2箇所あります。
- 1つ目
DeepLink特有の話ではありませんが、端末上でHome画面(アプリ一覧)にアプリを表示する上で必要な記述です。
この記述がない場合、Home画面からアプリをタップして起動する、という当たり前のようにやっている方法でアプリ起動が出来なくなってしまいます。 - 2つ目
こちらがDeepLinkに関係する内容です。-
android:autoVerify="true"
:リンク先の文章にもある通りです。また、android:autoVerify="true" 属性も指定する必要があります。これにより、アプリをこの特定の種類のリンクのデフォルト ハンドラに指定できます。
-
android.intent.category.BROWSABLE
- 英単語から推測できますが、ブラウザから起動可能な状態にします。
-
data
タグ- ここもリンク先の文章の通りです。私は値を少々修正しているので、その内容で一部上書きすると、以下の通りです。
このステップでは、local ドメインからのディープリンクの処理先として /register パス プレフィックスを含む MainActivity を指定します。
- ここもリンク先の文章の通りです。私は値を少々修正しているので、その内容で一部上書きすると、以下の通りです。
-
アプリケーション側で、WebURLを受け取れる状態へ
アプリケーションがWeb URLを受け取るとき、
デバイス上のアプリケーション状態として、大きく2パターンに分かれます。
- Cold Start:アプリを終了した状態
- Hot Start / Warm Start:Cold Start以外の状態(※)
詳細は以下に記載されています。
ローカル環境は、Cold Startの動作確認はできません。
(アプリが終了した時点で、接続が切れてしまう(Lost Connection
状態になる)ため)
ローカルの動作確認では、Hot Start / Warm Startについて、想定する挙動になることを確認します。
Flutterのlib/main.dart
でrunAppに設定しているWidgetに対して、WebURLのハンドリングを追加します。(※)
※以下の例でいえば、TestingApp
ウィジェットのことです。
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Text('Hello, world!'),
),
),
);
}
}
実装する前に、以下パッケージを追加します。
アプリケーション起動時に以下の処理を行っています。(一部略)
先ほどの例で記載したMyApp
が、私の場合はTestingApp
になっています。
全体実装は、こちら(app_local.dart)で公開しています。
/// アプリケーションのエントリーポイント
class TestingApp extends ConsumerStatefulWidget {
const TestingApp({super.key});
@override
ConsumerState<TestingApp> createState() => _TestingAppState();
}
class _TestingAppState extends ConsumerState<TestingApp> {
late AppLinks _appLinks;
StreamSubscription<Uri>? _linkSubscription;
final _router = GoRouter(
initialLocation: MenuPage.routeName,
routes: Routes.routeList,
redirect: (context, state) {
return null;
},
);
@override
void initState() {
super.initState();
initDeepLinks();
}
@override
void dispose() {
_linkSubscription?.cancel();
super.dispose();
}
void openAppLink(Uri uri) {
final path = uri.path.isEmpty ? '/' : uri.path;
if (kDebugMode) {
print('Opening deep link path: $path');
}
// 会員登録画面へ(トークン情報を渡す)
if (path == '/register') {
final token = uri.queryParameters['token'] ?? '';
if (token.isEmpty) {
if (kDebugMode) {
print('Register Token is empty');
}
_router.go(SignUpFailed.routeName);
} else {
_router.go(SignUp.routeName, extra: {'token': token});
}
} else {
_router.go('/');
}
}
// ディープリンクの初期化と処理
Future<void> initDeepLinks() async {
_appLinks = AppLinks();
// アプリ起動後のディープリンク処理
_linkSubscription = _appLinks.uriLinkStream.listen((uri) {
if (kDebugMode) {
print('Got deep link while app running: $uri');
}
openAppLink(uri);
});
}
}
_appLinks.uriLinkStream.listen
を呼び出し、WebURLを受け付け状態を整えます。
openAppLink
メソッドには、受け取ったURLに応じた遷移先を決める処理を記述しています。
context.go('/')
はエラーになった
実装に関する気付きみたいな内容なので、気になる方は読んでください。
私が作成しているアプリケーションはGo Routerによる画面遷移を管理しています。
どの画面に遷移するか、最初は以下の実装をしていました。
context.go('/');
GoRouterを使った画面遷移は、context.go
やcontext.push
を用いることが多いです。
-
context.go
:これまでのスタック(※)を削除して、新しいスタックを作成し、そこにウィジェットを載せる -
context.push
:これまでのスタック(※)に遷移先のウィジェットを重ねる
※スタック:スタック/キューのスタックのことですが、それぞれ以下の違いがあります。
アプリ側でリンクを受け取るのは成功しましたが、
実際に画面遷移するための処理で以下エラーが発生しました。(アプリケーションの構造によっては、発生しないエラーかもしれません)
I/flutter (21437): /register
E/flutter (21437): [ERROR:flutter/runtime/dart_vm_initializer.cc(40)] Unhandled Exception: 'package:go_router/src/router.dart': Failed assertion: line 521 pos 12: 'inherited != null': No GoRouter found in context
まだMaterialApp.router
にルーティングに関する定義をしていない状態で、GoRouterを用いた遷移処理を実行しようとしたからです。
ざっくり記載すると、UI制御でGoRouterを使うための初期設定が完了していないのに、遷移する処理を呼び出しているよ、といったエラーです。
エラー回避するため、
GoRouter
インスタンスを定義した変数を直接利用して、遷移処理を実装しました。
_router.go('/');
動作確認
adb
コマンドを使って、挙動を確認します。
指定されたAndroidアプリ(com.sample.package)を起動し、特定のURL(http://local.com/register?token=ABCDE
)をそのアプリで開くよう指示しています。
adb shell am start -W -a android.intent.action.VIEW -d "http://local.com/register?token=ABCDE" com.sample.package
アプリが起動して、
実装したハンドリング通りの画面が表示できていれば成功です。
まとめと今後
Android App Links(Deep Link)の機能実装について、まずはローカルで起動したアプリに適用する方法について説明してきました。
この後、リリース編でデプロイしたapkファイルでも機能するように作業を進めていきます。
予定作業は以下の通りです。
- WebURLとAndroidアプリケーションをMapping
- AndroidManifest.xmlの修正
- App Distributionを用いたアプリケーションのデプロイ
参考記事