【入門】FlutterでDeepLinkを実装してみよう。
DeepLinkとは
あるリンクをクリックすると特定のアプリを開くようなリンクのことをDeepLinkといいます。他の呼び名として「Universal Links」、「dynamic links」、「App Links」などもあるようですがどれもほぼ同じ意味です。
【追記】DeepLinkの細かい違いについてこの記事がわかりやすかったです。
DeepLinkの身近な例でいうと、Webページでネットサーフィンしている時にSNSの投稿をクリックしたらTwitterやInstagramのアプリに移動してアプリ側で詳細表示されるあれです。あれがDeepLinkです。
イメージを掴むには以下動画が分かりやすくて良いと思います。
従って、メールやSNS、DMから直接アプリを開けるようにするような機能を実現したい場合はDeepLinkを使うのが良さそうです。
またDeepLinkに変数(パラメータ)を与え、それに応じた処理をかませることで招待機能や特定の画面を表示するなど自由な設計が可能です。
この記事に書いてあること
- 簡単なDeepLinkアプリの作成と動作確認
- 任意のclassでDeepLinkを受け取る方法
とりあえずDeeplinkを動かしてみたい方対象です。実装そのものは簡単ですが、設定でミスると沼ってしまうのでご注意ください!
ちなみに今回は以下パッケージを使って実装していこうと思います。
また、基本は公式ページに書いてありますので目を通してみてください。
あとは実装していく上でURLの構造について理解があると楽なので以下記事を参考にしてみてください。
簡単なDeepLinkアプリの前提条件
- そもそもアプリがインストールされている。
- フォアグラウンドでもバックグラウンドでも動作する。
- iOS&Androidで動作確認済み
- 動作確認はエミュレーターのみ
開発環境でのDeepLink動作確認はちと面倒なのでエミュレーターのコマンドを使ってやっていきます。↓参考記事
詳細気になる方は一番最後にGithubレポジトリのリンク貼っているので参考にしてください。
さっそく動かしてみよう
今回はあるリンクをクリックした時にアプリ側でキャッチし、アプリが立ち上がってパラメータ部分が画面に表示されるアプリを作ってみます。
リンクはflutterUniversity://user/?name=matsumaru
でDeepLink検証してみます。
flutterUniversity
の部分はスキームといって、目印になります。なんでもいいですがアプリ名にするのが一般的かなと思います。
user
のところも適当につけたのでなんでもOKです。
?name=matsumaru
の「?」部分はパラメーターといって変数を指定することができます。?name=ma
など任意です。
今回のリンクの場合アプリが立ち上がってパラメータ部分のmatsumaru
が画面に表示されるアプリを作ってみます。
出来上がりとしてはこんな感じです。
初期画面 | リンクキャッチ後 |
---|---|
では設定していきましょう。
iOSの設定
info.plistに以下を追記していください。
<dict>
<!-- Deep Links ここから -->
<key>FlutterDeepLinkingEnabled</key>
<true/>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>flutterUniversity</string> //flutterUniversityは任意
</array>
</dict>
</array>
<!-- Deep Links ここまで追加 -->
//以下略
Androidの設定
AndroidManifest.xmlのactivity以下に追記します。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.flutter_deeplink_demo">
<application
android:label="flutter_deeplink_demo"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Deep Links ここから -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="flutterUniversity" /> //flutterUniversityは任意
</intent-filter>
<!-- Deep Links ここまで追加 -->
//以下略
コード実装
main -> MyApp -> MyHomePageという初期状態の階層になっていてMyHomePageの全文をのせています。linkStream.listen
を実行させておけばあとはパッケージがいい感じに動いてくれるのでめっちゃ簡単です。
class MyHomePage extends StatefulWidget {
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
StreamSubscription? _sub;
String? catchLink;
String? parameter;
void initState() {
super.initState();
initUniLinks();
}
Future<void> initUniLinks() async {
_sub = linkStream.listen((String? link) {
//さっき設定したスキームをキャッチしてここが走る。
catchLink = link;
parameter = getQueryParameter(link);
setState(() {});
}, onError: (err) {
print(err);
});
}
String? getQueryParameter(String? link) {
if (link == null) return null;
final uri = Uri.parse(link);
//flutterUniversity://user/?name=matsumaruのmatsumaru部分を取得
String? name = uri.queryParameters['name'];
return name;
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'リンク:$catchLink',
style: Theme.of(context).textTheme.headline4,
),
Text(
'パラメーター:$parameter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
);
}
}
ここまで来ればあとは動作確認するだけです。
動作確認
今回はエミュレーターのみでの動作確認です。コマンドを実行すれば良いだけですが、iOSとAndroidで異なります。
iOS
xcrun simctl openurl booted flutterUniversity://user/?name=matsumaru
flutterUniversity://user/?name=matsumaru
の部分は適宜置き換えてください。アプリを立ち上げた状態で上記コマンドを実行すれば動くはずです。
Android
adb -e shell 'am start -W -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "flutterUniversity://user/?name=matsumaru"'
Androidの場合は少し長いですが、上記の通りです。
実機確認する場合はadb -e
をadb -d
に変えると良さそうです。
The only USB connected device - adb -d shell '...'
The only emulated device - adb -e shell '...'
これで簡単なDeepLinkアプリの作成と動作確認は完了です。
任意のclassでDeepLinkを受け取る方法
一旦DeepLinkの実装はできましたが、色んなclassでDeepLinkを受け取りたいニーズはあると思います。そこで今回はMixinを使ってもう少しスマートにしてみようかなと思います。Mixinについてはこの記事がわかりやすかったです。
実行コードの重複をさけるためにMixin化して使い回すって感じですかね。今回のケースでいうとライブラリの依存関係も減らせるのでそこも良いかなと思います。
ということで作ってみました。参考にしたのはこちらです。fcm_configというライブラリの実装部分をパクってます。
mixin DeepLinkNotificationMixin<T extends StatefulWidget> on State<T> {
StreamSubscription? _sub;
void initState() {
//DeepLinkの監視
_sub = uriLinkStream.listen(_onNewNotify);
super.initState();
}
void dispose() {
_sub?.cancel();
super.dispose();
}
void onDeepLinkNotify(Uri? uri);
void _onNewNotify(Uri? uri) {
if (mounted) onDeepLinkNotify(uri);
}
}
Mixinを使って先程のMyHomePageを書き換えてみると以下の通りです。
StatefulWidget
にwith DeepLinkNotificationMixin
してやります。MyHomePageはuni_linksライブラリに依存しなくなったのも良さそうです。
class MyHomePage extends StatefulWidget {
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with DeepLinkNotificationMixin {
String? catchLink;
String? parameter;
void initState() {
super.initState();
}
//DeepLinkNotificationMixinのonDeepLinkNotifyを継承している。
void onDeepLinkNotify(Uri? uri) {
final link = uri.toString();
catchLink = link;
parameter = getQueryParameter(link);
setState(() {});
}
String? getQueryParameter(String? link) {
if (link == null) return null;
final uri = Uri.parse(link);
String? name = uri.queryParameters['name'];
return name;
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'リンク:$catchLink',
style: Theme.of(context).textTheme.headline4,
),
Text(
'パラメーター:$parameter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
);
}
}
以上で終わります!
レポジトリ
Githubにレポジトリ作成したので、参考にしてみてください。
Discussion
かなりわかりやすかったです!ありがとうございます。