🔗

[Flutter][FDL]iOSでストアからインストール後の起動にてリンクのパラメータが拾えなかった時の解決方法

2024/02/20に公開

iOSだけがディープリンクからストアに遷移し、そこからアプリインストール後に開いてもリンクのパラメータが取得できず、困ったのでそれを解決する方法を備忘録として記事にします。

注意

前提と提供したいUX

  • Flutter 3系
  • FirebaseDynamicLinksをすでに導入済み
  • iOSはユニバーサルリンクを使用していない(apple-app-site-association未設定)

Flutterバージョン

Flutter 3.10.6 • channel stable • https://github.com/flutter/flutter.git
Framework • revision f468f3366c (7 months ago) • 2023-07-12 15:19:05 -0700
Engine • revision cdbeda788a
Tools • Dart 3.0.6 • DevTools 2.23.1

手元で確認したiOSはiOS16

提供したいUX

ディープリンクをタップ→アプリがインストールされていなければストアに遷移→ストアからアプリをインストール→ストアでそのままアプリを開く→アプリ起動後に招待コードが入った状態にする

FirebaseDynamicLinksの仕組み

そもそもawait FirebaseDynamicLinks.instance.getInitialLink()がiOSだと機能していない(公式サイトに明記)

Android は常に終了状態の FirebaseDynamicLinks.getInitialLink を介してリンクを受け取りますが、iOS ではこの処理は保証されません。そのため、アプリがリンクを確実に受け取るようにするには、両方のメソッドを次の順序で設定することをおすすめします。

この両方のメソッドというのは下記のメソッドでこれはどちらとも実装済みで、

// Check if you received the link via `getInitialLink` first
final PendingDynamicLinkData? initialLink = await FirebaseDynamicLinks.instance.getInitialLink();
FirebaseDynamicLinks.instance.onLink.listen(
  (pendingDynamicLinkData) {
  // Set up the `onLink` event listener next as it may be received here
  if (pendingDynamicLinkData != null) {
    final Uri deepLink = pendingDynamicLinkData.link;
    // Example of using the dynamic link to push the user to a different screen
    Navigator.pushNamed(context, deepLink.path);
    }
  },
);

ただiOSはインストール後にストア経由からアプリを開かず、一旦再度リンクを踏み直すとonLink.listenが動く仕組みになっていて、UX的には良くない(ストアからダウンロード後そのまま開くボタンをしてアプリを起動してパラメータを渡したい)

Issue

だいぶ前のIssueにはなりますが、同じように悩んでるIssueを発見しました。
https://github.com/firebase/flutterfire/issues/7546

また、Zennでも記載されてる方がいて、参考にさせていただきました。
https://zenn.dev/nitaking/articles/5e067010906e67

IssueのコメントやZennではapp_linksパッケージを使うと、解決できたという記載されてたのでそれで試してみたんですが、なぜか効果がなくそのIssue内でも改善できなかった方もいました。

解決方法

結果的にはapp_linksでなく、uni_linksを使ってリンクを取得することで解決できました🙌
https://pub.dev/packages/uni_links

+ import 'package:uni_links/uni_links.dart';

...

- final PendingDynamicLinkData? initialLink = await FirebaseDynamicLinks.instance.getInitialLink();
+ // 戻り値はStringなので適宜パラメータを処理する
+ final initialLink = await getInitialLink();

app_linksとuni_linksの違い

両方のパッケージともやっていることは同じですが、リンクの取得方法がどうやら違うみたいでした。

app_linksの方

https://github.com/llfbandit/app_links/blob/master/ios/Classes/SwiftAppLinksPlugin.swift

ユニバーサルリンクを使用していた場合は、application(_:continue:restorationHandler:)メソッドが呼び出されるが、今回はここが通らない。
また、下記のロジックもiOS13以上では受け取れないので、ここも通らない。

public func application(
    _ application: UIApplication,
    open url: URL,
    options: [UIApplication.OpenURLOptionsKey : Any] = [:]
  ) -> Bool {
    
    handleLink(url: url)
    return false
  }

uni_linksの方

https://github.com/avioli/uni_links/blob/master/uni_links/ios/Classes/UniLinksPlugin.m

UIApplicationLaunchOptionsURLKeyからURLを取得していて、これは前提条件の仕様でも取得できている。

別件

もう一つ考慮しないといけないことがあり、当プロダクトはUX的考慮からFirebaseDeepLinkPasteboardRetrievalEnabledの設定をOFFにしていたのですが、
下記の記事の記載通り、この設定をOFFにするとdeferred deep-linkingが動かなくなってしまい、URLが取得できなかったためOFF設定を仕方なく無くしました。
https://zenn.dev/tattn/articles/40d1e53cc63d381a3ac5#クリップボード通知

この設定をなくす

- <key>FirebaseDeepLinkPasteboardRetrievalEnabled</key>
- <false/>

終わりに

iOSの仕組みが理解できていれば、より早く解決できたかと思いますが、知識不足で結構この解決に時間がかかりました。。。知り合いのエンジニア数名のアドバイスを元に解決できたので、すごく助かりました🙏
FirebaseDynamicLinksが来年終了するので、今年中にはAdjustかAppsFlyerとか代替案を実装したいなと思います。。
(それ以前にユニバーサルリンクの設定を早めに入れたほうがいいのかもしれない)

Discussion