🧑‍💻

【Flutter】DeepLinkで任意の画面に遷移する

2022/12/31に公開

DeepLinkとは

ディープリンクとは、Webページやスマートフォンアプリからアプリの特定コンテンツへ移動するリンクのことです。

ディープリンクは元々、あるWebサイトのページから他のWebサイトのページやコンテンツに直接リンクすることを指して使われている言葉でしたが、近年になってスマートフォンやアプリの利用が増加したことに伴い、現在利用されているような意味へと再定義されました。

例えば、あるアプリにディープリンクを設定すると、そのディープリンクから他のアプリの特定のコンテンツに移動することができます。
これまでは、リンクを選択してもApp StoreやGoogle Playなどアプリのダウンロードページに移動するだけでしたが、現在ではアプリ内の特定のページやコンテンツに直接リンクさせることが可能です。

https://wacul-ai.com/blog/site-improvement/method/deeplink/

この記事でやること

特定のDeepLinkを処理した際に、単純な画面遷移を行います。
https://github.com/f-nakahara/flutter-deeplink-example

実践

パッケージ導入

以下の2つを導入します。

DeepLink処理をするのに必要なパッケージ
https://pub.dev/packages/uni_links

アプリ内でURLを開くのに必要なパッケージ
https://pub.dev/packages/url_launcher

初期設定

DeepLinkを導入には、Android, iOSともに初期設定が必要となります。
Androidでは、AndroidManifest.xml, iOSではinfo.plist内を編集します。

Android

AndroidManifest.xml
<manifest ...>
  <application ...>
    <activity ...>

      <!-- DeepLink -->
      <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="[YOUR_SCHEME]" />
      <!-- ここまで -->
	      
      </intent-filter>
    </activity>
  </application>
</manifest>

iOS

info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>

  <!-- DeepLink -->
  <key>CFBundleURLTypes</key>
  <array>
    <dict>
      <key>CFBundleTypeRole</key>
      <string>Editor</string>
      <key>CFBundleURLSchemes</key>
      <array>
        <string>[YOUR_SCHEME]</string>
      </array>
    </dict>
  </array>
  <!-- ここまで -->
  
</dict>
</plist>

Android, iOSともに[YOUR_SCHEMA]の部分は任意の値です。
たとえば、myappとしたときは、myapp://で始まるURLがDeepLinkとして認識されます。

コード実装

DeepLinkを監視する

アプリ内でDeepLinkを監視するための処理を書いていきます。

main.dart
class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  StreamSubscription? _sub;

  
  void initState() {
    init();
    super.initState();
  }

  
  void dispose() {
    _sub?.cancel();
    super.dispose();
  }

  Future<void> init() async {
    _sub = uriLinkStream.listen((Uri? uri) {
      // ここにDeepLinkを認識したあとの処理を書く
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(),
    );
  }
}

URLを開くボタンを作成する

今回は、単純なリンクタップ処理を記述するので、見た目は適当にボタンにして、ボタン押下時の処理内で、URLを開くことにします。

main.dart
Widget hyperlinkButton(String url) {
    final uri = Uri.parse(url);
    return ElevatedButton(
      onPressed: () async {
        if (await canLaunchUrl(uri)) {
          await launchUrl(uri); // URLを開く
        }
      },
      child: Text(url),
    );
  }

2つほどボタンを並べる予定のため、再利用しやすいように、とりあえず関数として定義しました。

コード全体

必要なコードはできたため、先程のコードを組み合わせて完成させていきます。

main.dart
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:uni_links/uni_links.dart';
import 'package:url_launcher/url_launcher.dart';

void main() {
  runApp(const App());
}

class App extends StatelessWidget {
  const App({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  StreamSubscription? _sub;

  
  void initState() {
    init();
    super.initState();
  }

  
  void dispose() {
    _sub?.cancel();
    super.dispose();
  }

  Future<void> init() async {
    /// DeepLinkを監視する
    _sub = uriLinkStream.listen((Uri? uri) {
      if (uri != null) {
        final host = uri.host; // myapp
        switch (host) {
          case "page1": // myapp://page1
            pushPage(const Page1());
            break;
          case "page2": // myapp://page2
            pushPage(const Page2());
            break;
        }
      }
    });
  }

  /// [page]に画面遷移する
  Future<void> pushPage(Widget page) async {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) {
          return page;
        },
      ),
    );
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            hyperlinkButton('myapp://page1'), // myapp://page1 を開くボタン
            hyperlinkButton('myapp://page2'), // myapp://page2 を開くボタン
          ],
        ),
      ),
    );
  }

  Widget hyperlinkButton(String url) {
    final uri = Uri.parse(url);
    return ElevatedButton(
      onPressed: () async {
        if (await canLaunchUrl(uri)) {
          await launchUrl(uri); // URLを開く
        }
      },
      child: Text(url),
    );
  }
}

class Page1 extends StatelessWidget {
  const Page1({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: const Center(
        child: Text('Page1'),
      ),
    );
  }
}

class Page2 extends StatelessWidget {
  const Page2({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: const Center(
        child: Text('Page2'),
      ),
    );
  }
}

動作確認

初期画面はこんなかんじです。
上のボタンを押すと、Page1に遷移し、下のボタンを押すとPage2に遷移します。

とりあえず、myapp://page1ボタンを押して見る。

無事画面遷移することができました!
画像だけで分かりづらくてすみません。。

ちなみに、以下のコマンドをターミナルで使用しても画面遷移されます。

adb -e shell 'am start -W -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "myapp://page1"'`

おまけ

DeepLink処理をMixinで分離する場合のコードは以下のようになります。

deeplink_mixin.dart
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_deeplink_example/main.dart';
import 'package:uni_links/uni_links.dart';

mixin DeepLinkMixin<T extends StatefulWidget> on State<T> {
  StreamSubscription? _sub;

  
  void initState() {
    init();
    super.initState();
  }

  
  void dispose() {
    _sub?.cancel();
    super.dispose();
  }

  Future<void> init() async {
    /// DeepLinkを監視する
    _sub = uriLinkStream.listen((Uri? uri) {
      if (uri != null) {
        final host = uri.host; // myapp
        switch (host) {
          case "page1": // myapp://page1
            pushPage(const Page1());
            break;
          case "page2": // myapp://page2
            pushPage(const Page2());
            break;
        }
      }
    });
  }

  /// [page]に画面遷移する
  Future<void> pushPage(Widget page) async {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) {
          return page;
        },
      ),
    );
  }
}
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_deeplink_example/deeplink_mixin.dart';
import 'package:url_launcher/url_launcher.dart';

void main() {
  runApp(const App());
}

class App extends StatelessWidget {
  const App({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> with DeepLinkMixin {
  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            hyperlinkButton('myapp://page1'), // myapp://page1 を開くボタン
            hyperlinkButton('myapp://page2'), // myapp://page2 を開くボタン
          ],
        ),
      ),
    );
  }

  Widget hyperlinkButton(String url) {
    final uri = Uri.parse(url);
    return ElevatedButton(
      onPressed: () async {
        if (await canLaunchUrl(uri)) {
          await launchUrl(uri); // URLを開く
        }
      },
      child: Text(url),
    );
  }
}

class Page1 extends StatelessWidget {
  const Page1({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: const Center(
        child: Text('Page1'),
      ),
    );
  }
}

class Page2 extends StatelessWidget {
  const Page2({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: const Center(
        child: Text('Page2'),
      ),
    );
  }
}

https://github.com/f-nakahara/flutter-deeplink-example

Discussion