🔗

【入門】FlutterでDeepLinkを実装してみよう。

2022/03/12に公開
1

DeepLinkとは

あるリンクをクリックすると特定のアプリを開くようなリンクのことをDeepLinkといいます。他の呼び名として「Universal Links」、「dynamic links」、「App Links」などもあるようですがどれもほぼ同じ意味です。

【追記】DeepLinkの細かい違いについてこの記事がわかりやすかったです。
https://zenn.dev/msorz/articles/72a282ff71a350

DeepLinkの身近な例でいうと、Webページでネットサーフィンしている時にSNSの投稿をクリックしたらTwitterやInstagramのアプリに移動してアプリ側で詳細表示されるあれです。あれがDeepLinkです。

イメージを掴むには以下動画が分かりやすくて良いと思います。
https://www.youtube.com/watch?v=LvY1JMcrPF8&list=PLl-K7zZEsYLmOF_07IayrTntevxtbUxDL

従って、メールやSNS、DMから直接アプリを開けるようにするような機能を実現したい場合はDeepLinkを使うのが良さそうです。
またDeepLinkに変数(パラメータ)を与え、それに応じた処理をかませることで招待機能や特定の画面を表示するなど自由な設計が可能です。

この記事に書いてあること

  • 簡単なDeepLinkアプリの作成と動作確認
  • 任意のclassでDeepLinkを受け取る方法

とりあえずDeeplinkを動かしてみたい方対象です。実装そのものは簡単ですが、設定でミスると沼ってしまうのでご注意ください!

ちなみに今回は以下パッケージを使って実装していこうと思います。
https://pub.dev/packages/uni_links

また、基本は公式ページに書いてありますので目を通してみてください。
https://docs.flutter.dev/development/ui/navigation/deep-linking

あとは実装していく上でURLの構造について理解があると楽なので以下記事を参考にしてみてください。
https://8vivid.net/whats-url-scheme/

簡単なDeepLinkアプリの前提条件

  • そもそもアプリがインストールされている。
  • フォアグラウンドでもバックグラウンドでも動作する。
  • iOS&Androidで動作確認済み
  • 動作確認はエミュレーターのみ

開発環境でのDeepLink動作確認はちと面倒なのでエミュレーターのコマンドを使ってやっていきます。↓参考記事
https://qiita.com/Frog_kt/items/5f9e4dd1d2128173f60c

詳細気になる方は一番最後に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 -eadb -dに変えると良さそうです。

The only USB connected device - adb -d shell '...'
The only emulated device - adb -e shell '...'

参考

これで簡単なDeepLinkアプリの作成と動作確認は完了です。

任意のclassでDeepLinkを受け取る方法

一旦DeepLinkの実装はできましたが、色んなclassでDeepLinkを受け取りたいニーズはあると思います。そこで今回はMixinを使ってもう少しスマートにしてみようかなと思います。Mixinについてはこの記事がわかりやすかったです。
https://ntaoo.hatenablog.com/entry/2018/12/02/192827

実行コードの重複をさけるために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を書き換えてみると以下の通りです。
StatefulWidgetwith 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にレポジトリ作成したので、参考にしてみてください。

https://github.com/MatsumaruTsuyoshi/flutter_deeplink_demo

GitHubで編集を提案

Discussion