Firebase Dynamic Linksとは
Firebaseが提供するディープリンクの機能です。ディープリンクとは1つのURLでAndroid/iOS/WEBでの遷移先を振り分けたり、そのあとアプリにパラメータを渡すことも出来ます。
Flutter大学の共同開発で作成したCOUPLE TODOはこのDynamic Linksを使って、夫婦同士の紐づけを行っていますので、そのやり方を紹介します。
このコードの例は多少古い書き方も含んでいますのでご承知おきください。(RaisedButtonなど)
Flutterでの実装
Flutterあるあるですが、公式のドキュメントには実装例が載っていないです。
そしてFlutterFireというFlutterでFirebase使う時の実装例などが書いてあるドキュメントにもDynamic Linksの記載はないです。(ページだけは存在していた。そのうち書かれるかも?)
pub.devの方には一応実装例があるので、そちらを参考にするのも良いかと思います。
利用パッケージ
今回の実装で必要なパッケージは以下です。結構前のプロジェクトから引用してきているので、最新のものをお使いください。
dependencies:
flutter:
sdk: flutter
firebase_dynamic_links: ^0.5.3
share: ^0.6.5+4
リンクの動的生成
ダイナミックリンクにアプリの中でユーザーのペアを紐づけるcoupleId
というものを共有させるために、動的にリンクを生成しています。
生成処理はリポジトリで行っています。
抽象クラスはこちら。
abstract class DynamicLinksRepository {
Future<Uri> createInviteDynamicLink({@required String coupleId});
Future<String> dynamicLinksHandlerNonInstall();
Future<String> setOnLink();
}
実装はこちら
class DynamicLinksRepositoryImpl implements DynamicLinksRepository {
DynamicLinksRepositoryImpl({@required FirebaseAuthRepository authRepository})
: _authRepository = authRepository;
final FirebaseAuthRepository _authRepository;
/// 招待用のdynamic linksを生成する
@override
Future<Uri> createInviteDynamicLink({@required String coupleId}) async {
final DynamicLinkParameters parameters = DynamicLinkParameters(//ここで各OSのパラメータを設定
uriPrefix: uriPrefix, // URL接頭辞
link: Uri.parse(getDefaultDeepLink(coupleId: coupleId)), //デフォルトのDeep Link
// Android用の設定
androidParameters: AndroidParameters(
packageName: androidPackageName,
minimumVersion: 1,
fallbackUrl: Uri.parse(androidFallbackUrl), //アプリ未インストール時の遷移先
),
// iOS用の設定
iosParameters: IosParameters(
bundleId: iosBundleId,
minimumVersion: '1',
fallbackUrl: Uri.parse(iosFallbackUrl),
),
);
final ShortDynamicLink shortDynamicLink = await parameters.buildShortLink(); // ショートリンクとして作成
final Uri shortUrl = shortDynamicLink.shortUrl;
return shortUrl;
}
そしてViewModelでDynamicLinksRepository.createInviteDynamicLink
を呼び出し、URLを生成しています。
class InviteModel extends ChangeNotifier {
InviteModel({
@required UserRepository userRepository,
@required FirebaseAuthRepository authRepository,
@required DynamicLinksRepository dynamicLinksRepository,
}) : _userRepository = userRepository,
_authRepository = authRepository,
_dynamicLinksRepository = dynamicLinksRepository;
final UserRepository _userRepository;
final FirebaseAuthRepository _authRepository;
final DynamicLinksRepository _dynamicLinksRepository;
String _inviteLink = ''; // URL格納用変数
String get inviteLink => _inviteLink; # getter
String _coupleId;
bool isLoading = true;
Future<void> init() async {
await _fetchInviteLink();
isLoading = false;
notifyListeners();
}
Future<void> _fetchInviteLink() async {
final String uid = _authRepository.getUid();
_coupleId = await _userRepository.getCoupleIdByUid(uid);
final Uri dynamicUrl = await _dynamicLinksRepository
.createInviteDynamicLink(coupleId: _coupleId);
_inviteLink = dynamicUrl.toString();
}
}
UI側では画面描画時に先ほどのモデルのinit()
を呼び出し、URLを生成します。
class InvitePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<InviteModel>(
create: (_) => InviteModel(
userRepository: context.read<UserRepository>(),
authRepository: context.read<FirebaseAuthRepository>(),
dynamicLinksRepository: context.read<DynamicLinksRepository>(),
)..init(),
child: Consumer<InviteModel>(
builder: (BuildContext context, InviteModel model, Widget child) {
return Scaffold(
appBar: AppBar(
title: const Text('パートナーを招待する'),
),
body: Stack(
children: [
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'パートナーと一緒にタスクを管理しよう!',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20,
),
),
const SizedBox(
height: 32,
),
Image.asset(
'assets/icon.jpg',
width: 200,
),
const SizedBox(
height: 32,
),
RaisedButton(
onPressed: () async {
await Share.share(model.inviteLink); // ここでボタンが押されたらURLをShareできるようになる
},
child: const Text('招待する'),
),
],
),
),
if (model.isLoading)
Container(
color: Colors.black.withOpacity(0.3),
child: const Center(
child: CircularProgressIndicator(),
),
)
else
const SizedBox()
],
),
);
}),
);
}
}
これでhttps://coupletodopro.page.link/yg27Jf4f6ac5pL11K
のようなURLを以下の写真のように共有できるようになります。
dynamic linksの検知
共有したら、次はdynamic links受け取った時の挙動です。
repositoryの中で未インストールだった場合とインストール済みで未連携の場合のメソッドを定義しておきます。
/// 未インストールの場合
@override
Future<String> dynamicLinksHandlerNonInstall() async {
// ここで取得
final PendingDynamicLinkData data =
await FirebaseDynamicLinks.instance.getInitialLink();
final Uri deepLink = data?.link;
if (deepLink != null) {
final String coupleId = deepLink.queryParameters['coupleId'];
return coupleId;
} else {
return null;
}
}
// インストール済みの場合でdynamic_linkを受け取った際の挙動
// dynamic linksを取得するかどうかをlistenしておく
@override
Future<String> setOnLink() async {
String coupleId;
FirebaseDynamicLinks.instance.onLink(onSuccess: (
PendingDynamicLinkData dynamicLink,
) async {
final Uri deepLink = dynamicLink?.link;
if (deepLink != null) {
coupleId = deepLink.queryParameters['coupleId'];
await _authRepository.logout();
_authRepository.interruptUserState(UserState.invited);
}
}, onError: (OnLinkErrorException e) async {
print(e.message);
});
return coupleId;
}
}
今回のアーキテクチャ上、dynamic linksの検知や認証しているかどうかなど、アプリに常駐させたい動きはMainModel
にて行っています。
class MainModel {
///長いので省略
Future<void> init() async {
await _authRepository.setAuthStateChangeListener();
// 初めに呼び出し、未インストールの時だけcoupleIdが返ってくる。
final String coupleIdWhenNonInstall =
await _dynamicLinksRepository.dynamicLinksHandlerNonInstall();
// coupleIdを受け取ることが出来たら、ローカルのストレージに保存して、ユーザーの状態を`invited`に変える(独自実装)
if (coupleIdWhenNonInstall != null) {
_coupleId = coupleIdWhenNonInstall;
await _storageRepository.savePersistenceStorage(key_couple_id, _coupleId);
_authRepository.interruptUserState(UserState.invited);
}
// ログイン済みの状態だったらcoupleIdを取得して、prefに保存する
final String uid = _authRepository.getUid();
if (uid != null) {
_uid = uid;
_coupleId = await _userRepository.getCoupleIdByUid(_uid);
await _storageRepository.savePersistenceStorage(key_couple_id, _coupleId);
}
// インストール済みの場合でdynamic_linkを受け取った際の挙動
final String coupleId = await _dynamicLinksRepository.setOnLink();
if (coupleId != null) {
_coupleId = coupleId;
await _authRepository.logout();
_authRepository.interruptUserState(UserState.invited);
}
}
}
このようにして検知しています。
デバッグ方法
生成されたURLの末尾に?d=1
をつけることで以下のような画面が表示され、デバッグできるようになります。
この画面のフローチャートが想定しているものとあっているかを確認し、エラーが出ていれば公式のページで各エラーの対処法を見てみると良いと思います。
まとめ
ざっくりとDynamic Linksを使った実装を紹介しました。
今回は紹介しませんでしたが、Firebaseのコンソールから静的なDynamic Linksを生成することができ、一つのURLでAndroid/iOSでそれぞれGoogle Play/Apple Storeと遷移先を変えることも出来ます。
題材にしたアプリのLPページにはこの静的なDynamic Linksが埋め込んであり、上記のような振り分けを行っています。