1. 導入
こんにちは、YOUTRUSTでFlutterエンジニアをしているオカ ( YOUTRUST / X )です。モバイルアプリ開発を10年ほど、直近2年はFlutterの開発を行っております。
2025年8月25日でサービス終了が予定されているFirebase Dynamic Linksから、新しいディープリンクソリューションとしてAdjust True Linkへの移行を行いました。
この記事の内容
- 移行先の選定理由と実装方法
- iOSの特定OSで発生した問題と解決策
- 段階的移行による安全な切り替え手法
同じような課題に直面している方の参考になれば幸いです。
2. 背景・課題
まず取り組んだのは、現在Firebase Dynamic Linksをどこで利用しているかの棚卸しです。
調査の結果、想像以上に広範囲で利用されていることが判明しました
- マーケティング関連
- メルマガ配信
- LP(ランディングページ)
- SNS投稿のシェア
- プッシュ通知(一般投稿、求人投稿、チャット通知など)
次に技術的な複雑さについて
YOUTRUSTのディープリンクが2つの異なるソースから成り立っていることも確認できました。
- AppLinks:
https://youtrust.jp/*形式 - Firebase Dynamic Links:
youtrust.page.linkドメイン
移行期間などを考えるとFirebase Dynamic Linksを削除するのではなく第3のディープリンクを入れることが必要となりました。
移行先の選定を行い複数の候補を検討した結果、Adjust True Linkを選択しました。
採用の決定的な理由
- 既存インフラの活用:広告効果測定で既にAdjust SDKを導入済み
- 技術的な信頼性:高速なリンク解決処理、iOS/Android両プラットフォームでの安定動作
3. 技術実装
開発を行う際、最も重要だったのは、Firebase Dynamic LinksとAdjust True Linkを同時に動かす仕組みです。
ディープリンクがクリックされた時の処理順序
- 通常のアプリ起動リンク(AppLinks)
- Firebase Dynamic Links(旧システム)
- Adjust True Link(新システム)
いずれかで処理できれば成功です
万が一問題が発生した場合はフォールバック設計でユーザー体験を保護します。
エラー発生時の処理
- Adjustでエラー → Firebaseで再試行
- Firebaseでもエラー → 通常のアプリ起動
結果として:ユーザーは必ずアプリが開く
final links = await Future.wait<Uri?>([ AppLinks().getInitialLink(), // 通常リンク FirebaseDynamicInitialLink(), // Firebase(旧) AdjustDeferredLinkDispatcher.instance.stream // Adjust(新) .timeout(const Duration(seconds: 1)) .cast<Uri?>() .first .catchError((_) => null), ]); final link = links.nonNulls.firstOrNull?.let(codec.decode);
4. iOS固有の問題と解決策
謎の現象:iOS 17限定でアプリに遷移した後にストアに遷移
発生した現象を詳しく
リンクをタップ → Safari が開く → アプリにリダイレクト → アプリ内で画面遷移 → Safari が開く → ストアに遷移!?
原因の特定
結果としてこの問題はiOS 17特有の仕様変更ではなく、Universal Linksとサードパーティ製SDKの相互作用による複合的な問題でした。
主な原因
- Firebase SDKとの競合: Firebase SDKが
continueUserActivityメソッドを干渉 - Universal Linksの設計仕様:アプリが自身のリンクを開こうとするとSafariにリダイレクトされる
- 不適切なリンク処理: 複数のディープリンクSDKが同時に動作することで発生する競合
解決策
Flutter側での対応
// Adjustリンクをブラウザで開くことを防止 // これによりiOS環境でのブラウザ戻り問題を回避 if (uri.host.contains('go.link')) { return true; // 処理済みとして返す(実際にはブラウザを開かない) }
iOS側での安定化処理
// iOS 17以降では遅延処理で安定性を確保 if #available(iOS 17.0, *) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { self.processUniversalLinkDelayed(url) } return true }
Firebase SDKの競合回避
<!-- Info.plistに追加 --> <key>FirebaseAppDelegateProxyEnabled</key> <false/>
iOS 18では共有機能のURL処理APIが変更され、より安全な実装に変更
if #available(iOS 18.0, *) { // 新しいAPI(非推奨警告の回避) application.open(url!, options: [:], completionHandler: nil) } else { // iOS 17以前の方法 _ = responder?.perform(selectorOpenURL, with: url) }
学んだ教訓
- SDK競合の重要性: 複数のSDK(Firebase、Adjust)の相互作用を事前に検証
- Universal Linksの設計理解: Appleの設計思想を理解した実装の重要性
- 段階的な問題切り分け: iOSバージョン特有ではなく、環境全体の問題として捉える
- 予防的対応: OSアップデート前の事前テストと設定確認の徹底
5. 移行戦略
ディープリンクについて段階的な移行を実施しました
段階的移行のメリット
- 問題の影響範囲を限定
- 問題の早期発見・対応が可能
- 既存の処理をそのまま利用しつつ段階的に改善可能
並列処理による安全な移行
既存のFirebase Dynamic Linksを維持しながら、Adjustを並列で動作させることで、リスクを最小化しました。
エラー耐性の確保
- Adjustで該当ページが見つからない場合でも正常動作
- どのリンクソースでもエラーが発生した場合、アプリクラッシュは絶対に回避
- ユーザーには必ずアプリが開く仕組みを保証
Firebase削除を前提とした設計
2025年8月のサービス終了後、Firebaseコードを安全に削除できるよう、意図的に切り分けて実装しました。
// REMOVE THIS LINE WHEN DELETING FIREBASE _getFirebaseDynamicInitialLink(), // TODO(team): Remove these functions and the firebase_dynamic_links // dependency when migrating fully to Adjust.
実装のポイント
- Firebase関連処理を独立したメソッドに分離
- 削除箇所を明確にマーキング
- Adjustとの依存関係を最小化
6. まとめ・読者へのアドバイス
移行を成功させる3つのポイント
プラットフォームの違いを甘く見ない
- 必ず両プラットフォーム(iOS/Android)でテスト
- 各OSの最新版だけでなく、古いバージョンも確認
- 特にiOS 17・iOS 18の新しい制約に注意
段階的な移行を実施
- 新旧システムの並行運用期間を設ける
- 問題発生時でもアプリクラッシュを防ぐ実装
- 既存機能を維持しながら新機能を段階的に改善
チーム全体での取り組み
- エンジニア: 技術的な実装とテスト
- マーケティング: ビジネス要件の整理とリンク管理
- QA: 的確な問題点の発見と報告
Firebase Dynamic Links移行を検討中の方へ
主要な選択肢
- Adjust True Link: 既存のAdjust導入済みなら有力候補
- Branch.io: 豊富な機能と実績
- AppsFlyer OneLink: マーケティング機能に強み
- 自社開発: 完全なコントロールが可能
それぞれにメリット・デメリットがあるため、自社の状況(既存ツール、予算、要件)に合わせて慎重に選択することをお勧めします。
最後に
Firebase Dynamic Linksからの移行は確かに大変な作業でしたが、結果的にユーザー体験とチームの生産性を大幅に向上させることができました。 同じような課題に直面している方は、焦らず・段階的に・チーム全体で取り組んでください。困った時は、技術コミュニティでの相談も有効です。
何かご質問がありましたら、お気軽に X までお声がけください。
YOUTRUSTではエンジニアを募集しております。自分ならもっと良いプロダクトにできると感じている方はぜひご応募ください!