【Flutter】DeepLinkで任意の画面に遷移する
DeepLinkとは
ディープリンクとは、Webページやスマートフォンアプリからアプリの特定コンテンツへ移動するリンクのことです。
ディープリンクは元々、あるWebサイトのページから他のWebサイトのページやコンテンツに直接リンクすることを指して使われている言葉でしたが、近年になってスマートフォンやアプリの利用が増加したことに伴い、現在利用されているような意味へと再定義されました。
例えば、あるアプリにディープリンクを設定すると、そのディープリンクから他のアプリの特定のコンテンツに移動することができます。
これまでは、リンクを選択してもApp StoreやGoogle Playなどアプリのダウンロードページに移動するだけでしたが、現在ではアプリ内の特定のページやコンテンツに直接リンクさせることが可能です。
この記事でやること
特定のDeepLinkを処理した際に、単純な画面遷移を行います。
実践
パッケージ導入
以下の2つを導入します。
DeepLink処理をするのに必要なパッケージ
アプリ内でURLを開くのに必要なパッケージ
初期設定
DeepLinkを導入には、Android, iOSともに初期設定が必要となります。
Androidでは、AndroidManifest.xml
, iOSではinfo.plist
内を編集します。
Android
<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
<?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を監視するための処理を書いていきます。
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を開くことにします。
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つほどボタンを並べる予定のため、再利用しやすいように、とりあえず関数として定義しました。
コード全体
必要なコードはできたため、先程のコードを組み合わせて完成させていきます。
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で分離する場合のコードは以下のようになります。
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;
},
),
);
}
}
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'),
),
);
}
}
Discussion