26
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

FlutterAdvent Calendar 2021

Day 7

【Flutter】FirebaseDynamicLinksで招待機能を実装する

Last updated at Posted at 2021-12-06

Firebase Dynamic Linksとは

Firebaseが提供するディープリンクの機能です。ディープリンクとは1つのURLでAndroid/iOS/WEBでの遷移先を振り分けたり、そのあとアプリにパラメータを渡すことも出来ます。
Flutter大学の共同開発で作成したCOUPLE TODOはこのDynamic Linksを使って、夫婦同士の紐づけを行っていますので、そのやり方を紹介します。

このコードの例は多少古い書き方も含んでいますのでご承知おきください。(RaisedButtonなど)

Flutterでの実装

Flutterあるあるですが、公式のドキュメントには実装例が載っていないです。

そしてFlutterFireというFlutterでFirebase使う時の実装例などが書いてあるドキュメントにもDynamic Linksの記載はないです。(ページだけは存在していた。そのうち書かれるかも?)

pub.devの方には一応実装例があるので、そちらを参考にするのも良いかと思います。

利用パッケージ

今回の実装で必要なパッケージは以下です。結構前のプロジェクトから引用してきているので、最新のものをお使いください。

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter

  firebase_dynamic_links: ^0.5.3
  share: ^0.6.5+4

リンクの動的生成

ダイナミックリンクにアプリの中でユーザーのペアを紐づけるcoupleIdというものを共有させるために、動的にリンクを生成しています。
生成処理はリポジトリで行っています。
抽象クラスはこちら。

lib/domain/repository/dynamic_links_repository.dart
abstract class DynamicLinksRepository {
  Future<Uri> createInviteDynamicLink({@required String coupleId});
  Future<String> dynamicLinksHandlerNonInstall();
  Future<String> setOnLink();
}

実装はこちら

lib/infrastructure/dynamic_links_repository_impl.dart
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を生成しています。

lib/presentation/invite/invite_model.dart
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を生成します。

lib/presentation/invite/invite_page.dart
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の中で未インストールだった場合とインストール済みで未連携の場合のメソッドを定義しておきます。

lib/infrastructure/dynamic_links_repository_impl.dart
  /// 未インストールの場合
  @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にて行っています。

lib/main_model.dart
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をつけることで以下のような画面が表示され、デバッグできるようになります。
cook_217JZKygLccJ59upletodpz6_d=1.png

この画面のフローチャートが想定しているものとあっているかを確認し、エラーが出ていれば公式のページで各エラーの対処法を見てみると良いと思います。

まとめ

ざっくりとDynamic Linksを使った実装を紹介しました。
今回は紹介しませんでしたが、Firebaseのコンソールから静的なDynamic Linksを生成することができ、一つのURLでAndroid/iOSでそれぞれGoogle Play/Apple Storeと遷移先を変えることも出来ます。

題材にしたアプリのLPページにはこの静的なDynamic Linksが埋め込んであり、上記のような振り分けを行っています。

参考

26
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?