From 63df00b62682f7bcf5a4e9031b6ae763f8a97688 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 21 May 2026 05:10:21 +0000 Subject: [PATCH 1/3] =?UTF-8?q?Go=20Router=E7=A7=BB=E8=A1=8C=E6=96=B9?= =?UTF-8?q?=E9=87=9D=E3=83=89=E3=82=AD=E3=83=A5=E3=83=A1=E3=83=B3=E3=83=88?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Kanta Oikawa --- docs/go_router_migration_plan.md | 221 +++++++++++++++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 docs/go_router_migration_plan.md diff --git a/docs/go_router_migration_plan.md b/docs/go_router_migration_plan.md new file mode 100644 index 00000000..ff09b6ed --- /dev/null +++ b/docs/go_router_migration_plan.md @@ -0,0 +1,221 @@ +# Go Router 移行方針 + +## 概要 + +現在 `Navigator`(命令型ナビゲーション)で実装されているルーティングを、`go_router`(宣言型ナビゲーション)に置き換える。 + +## 現状分析 + +### 現在のルーティング構成 + +| 要素 | 現在の実装 | +|------|-----------| +| エントリポイント | `MaterialApp.home` → `RootScreen` | +| タブナビゲーション | `IndexedStack` + タブごとの `Navigator` ウィジェット | +| タブ管理 | `NavigationBar` + `RootViewModel`(Riverpod Notifier) | +| 画面遷移 | `Navigator.of(context).push(MaterialPageRoute(...))` | +| 戻る制御 | `PopScope` + `maybePop()` / `popUntil()` | +| ディープリンク | `app_links` パッケージ(現在は listen のみ、ルーティング未連携) | +| Analytics | `FirebaseAnalyticsObserver` を `MaterialApp.navigatorObservers` に設定 | + +### 画面一覧とルート構造 + +#### タブ(`TabItem` enum / `NavigationBar`) + +| タブ | 画面 | 現在のルート名 | +|------|------|---------------| +| 講義 | `CourseScreen` | `/course` | +| 学食 | `FunchScreen` | `/funch` | +| マップ | `MapScreen` | `/map` | +| バス | `BusScreen` | `/bus` | +| 設定 | `SettingsScreen` | `/setting` | +| 科目検索 | `SearchSubjectScreen` | `/subject`(学食無効時に学食タブと差し替え) | + +#### 講義タブ配下 + +| 画面 | 現在のルート名 | パラメータ | +|------|---------------|-----------| +| `SearchSubjectScreen` | `/course/subjects` | なし | +| `CourseCancellationScreen` | `/course/irregular_classes` | なし | +| `WebPdfViewer` | `/course/web_pdf_viewer?url=...` | `url`, `filename` | +| `CourseCustomizeScreen` | `/course/preferences` | なし | +| `SubjectDetailScreen` | `/course/subjects/:id` | `id` | +| `CourseRegistrationScreen` | `/course/registration` | なし | + +#### バスタブ配下 + +| 画面 | 現在のルート名 | パラメータ | +|------|---------------|-----------| +| `BusStopSelectScreen` | `/bus/select_stop` | なし | +| `BusTimetableScreen` | `/bus/timetable?route=...` | `busTrip`(オブジェクト) | + +#### 科目詳細配下 + +| 画面 | 現在のルート名 | パラメータ | +|------|---------------|-----------| +| `SubjectDetailScreen` | `/subjects/:id` | `id` | +| 過去問の PDF ビューア | `/subjects/:id/past_exam/:pastExamId` | `pastExamId`, `pastExamUrl` | + +#### 設定タブ配下 + +| 画面 | 現在のルート名 | パラメータ | +|------|---------------|-----------| +| `AnnouncementScreen` | `/setting/announcements` | なし | +| `GitHubContributorScreen` | `/setting/developers` | なし | +| `OnboardingScreen` | `/setting/onboarding` | なし | +| `SettingsLicenseScreen` | `/setting/licenses` | なし | +| `DebugScreen` | `/setting/debug` | なし | + +#### その他 + +| 画面 | 条件 | +|------|------| +| `OnboardingScreen` | 初回起動時(チュートリアル未完了) | +| `InvalidAppVersionScreen` | アプリバージョン無効時 | + +## 移行方針 + +### 1. パッケージ導入 + +`pubspec.yaml` に追加: + +```yaml +dependencies: + go_router: ^15.1.2 +``` + +### 2. ルート定義(`StatefulShellRoute` によるタブナビゲーション) + +Go Router の `StatefulShellRoute.indexedStack` を使用し、現在の `IndexedStack` + ネスト `Navigator` 構成をそのまま置き換える。 + +``` +/ → RootScreen(シェル) +├── /course → CourseScreen +│ ├── /course/subjects → SearchSubjectScreen +│ │ └── /course/subjects/:id → SubjectDetailScreen +│ ├── /course/irregular_classes → CourseCancellationScreen +│ ├── /course/registration → CourseRegistrationScreen +│ ├── /course/preferences → CourseCustomizeScreen +│ └── /course/pdf?url=...&filename=... → WebPdfViewer +├── /funch → FunchScreen +├── /map → MapScreen +├── /bus → BusScreen +│ ├── /bus/select_stop → BusStopSelectScreen +│ └── /bus/timetable/:route → BusTimetableScreen +├── /setting → SettingsScreen +│ ├── /setting/announcements → AnnouncementScreen +│ ├── /setting/developers → GitHubContributorScreen +│ ├── /setting/onboarding → OnboardingScreen +│ ├── /setting/licenses → SettingsLicenseScreen +│ └── /setting/debug → DebugScreen +└── /subject → SearchSubjectScreen(学食無効時) + └── /subject/:id → SubjectDetailScreen +``` + +### 3. ルーター定義ファイルの新設 + +`lib/routing/` ディレクトリを新設し、以下のファイルを配置する: + +| ファイル | 責務 | +|---------|------| +| `app_router.dart` | `GoRouter` インスタンスの生成(Riverpod Provider) | +| `routes.dart` | ルート定義(`StatefulShellRoute` + 各 `GoRoute`) | +| `route_names.dart` | ルート名の定数定義 | + +`GoRouter` は Riverpod Provider として定義し、`ref` 経由で `redirect` やガードに必要な状態(認証状態、チュートリアル完了フラグ、アプリバージョン等)を参照できるようにする。 + +### 4. `MaterialApp` → `MaterialApp.router` への変更 + +`app.dart` を以下のように変更: + +```dart +MaterialApp.router( + routerConfig: ref.watch(appRouterProvider), + // ...既存のテーマ・ローカライゼーション設定 +) +``` + +### 5. リダイレクトによるガード処理 + +現在 `RootScreen.build()` 内で `if` 分岐している以下のガード処理を、`GoRouter.redirect` に移行する: + +| 条件 | 現在の処理 | Go Router での処理 | +|------|-----------|-------------------| +| チュートリアル未完了 | `OnboardingScreen` を直接表示 | `/onboarding` にリダイレクト | +| アプリバージョン無効 | `InvalidAppVersionScreen` を直接表示 | `/invalid_version` にリダイレクト | + +### 6. ダイアログ・BottomSheet + +`showDialog` / `showModalBottomSheet` はルーティング対象外とし、現在の命令型呼び出しをそのまま維持する。Go Router のルートとしては定義しない。 + +### 7. `Navigator.of(context).pop()` の扱い + +- ダイアログ内: `Navigator.of(context).pop()` をそのまま維持(ダイアログは Go Router 管理外) +- 画面遷移の戻り: `context.pop()` に置き換え +- 結果を返す `pop(result)`: `context.pop(result)` に置き換え + +### 8. 同一タブ再タップでルートまで戻る挙動 + +現在 `RootViewModel.onTabItemTapped` で `NavigatorState.popUntil` を使っている。Go Router では `StatefulShellRoute` の `NavigatorState` にアクセスして同等の処理を実装するか、各タブブランチの初期ルートにリダイレクトする方式を採用する。 + +### 9. Android の戻るボタン制御 + +現在 `PopScope` + `maybePop()` で実装している Android バックボタン制御は、Go Router のネスト `Navigator` が自動的に処理するため、明示的な `PopScope` は不要になる可能性がある。動作確認の上で削除を判断する。 + +### 10. Firebase Analytics 連携 + +`FirebaseAnalyticsObserver` を `GoRouter.observers` に設定する。Go Router は `RouteSettings.name` ではなく `GoRoute.name` / `GoRoute.path` をもとにスクリーン名を報告するため、既存の Analytics データとの整合性を確認する。 + +### 11. ディープリンク対応 + +現在 `app_links` パッケージで `uriLinkStream` を listen しているが、ルーティングとの連携はされていない。Go Router はディープリンクをネイティブサポートしているため、`app_links` の直接利用は不要になる。ルート定義にパスを定義するだけでディープリンクが機能する。 + +### 12. オブジェクトパラメータの受け渡し + +`BusTimetableScreen` のように画面遷移時にオブジェクト(`BusTrip`)を渡すケースは、Go Router の `extra` パラメータを使用する。ただし `extra` はディープリンクで復元できないため、将来的には ID ベースのパラメータに変更することを推奨する。 + +### 13. 動的タブ切り替え(学食 ↔ 科目検索) + +`isFunchEnabled` フラグによるタブの動的切り替えは、`StatefulShellRoute` のブランチ定義を条件分岐で構築するか、`redirect` でハンドリングする。 + +## 移行手順 + +### Phase 1: 基盤構築 + +1. `go_router` パッケージの追加 +2. `lib/routing/` にルート定義ファイルを作成 +3. `MaterialApp.router` への切り替え +4. `StatefulShellRoute.indexedStack` でタブナビゲーションを実装 +5. 各タブのルートスクリーンのみ定義(サブルートは Phase 2) + +### Phase 2: 画面遷移の移行 + +1. 各 feature の `Navigator.of(context).push(MaterialPageRoute(...))` を `context.go()` / `context.push()` に逐次置換 +2. `Navigator.of(context).pop()` を `context.pop()` に置換(ダイアログ内を除く) +3. `RouteSettings` の削除 + +### Phase 3: ガード・リダイレクトの移行 + +1. `redirect` でオンボーディング / バージョンチェックのガード実装 +2. `RootScreen` からガードロジックを削除 +3. `RootViewModel` の `navigatorKeys` 関連コードを削除 + +### Phase 4: ディープリンク対応 + +1. Go Router のディープリンク設定 +2. `app_links` パッケージの利用箇所を Go Router に統合 +3. 不要になった `app_links` の listen 処理を削除 + +### Phase 5: クリーンアップ + +1. 未使用の `Navigator` 関連コード・import の削除 +2. `RootViewModelState` から `navigatorKeys` フィールドを削除 +3. Firebase Analytics の動作確認 +4. 全画面遷移の動作確認・回帰テスト + +## 注意事項 + +- 移行は Phase ごとに PR を分けて段階的に行い、大規模一括リファクタは避ける(AGENTS.md の方針に準拠)。 +- 各 Phase で全プラットフォーム(iOS / Android / Web)の動作確認を行う。 +- Analytics のスクリーン名が変わる可能性があるため、移行前後のデータ比較を行う。 +- `extra` でオブジェクトを渡しているルートは、ディープリンク対応時に ID ベースに変更する。 From dcd9b584bf8e19e11e44fc5e8f0064d510f7d275 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 21 May 2026 06:45:54 +0000 Subject: [PATCH 2/3] =?UTF-8?q?=E3=83=AC=E3=83=93=E3=83=A5=E3=83=BC?= =?UTF-8?q?=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88=E3=81=AB=E5=9F=BA=E3=81=A5?= =?UTF-8?q?=E3=81=8D=E3=83=89=E3=82=AD=E3=83=A5=E3=83=A1=E3=83=B3=E3=83=88?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Kanta Oikawa --- docs/go_router_migration_plan.md | 77 +++++++++++++++++++++++++------- 1 file changed, 62 insertions(+), 15 deletions(-) diff --git a/docs/go_router_migration_plan.md b/docs/go_router_migration_plan.md index ff09b6ed..50089756 100644 --- a/docs/go_router_migration_plan.md +++ b/docs/go_router_migration_plan.md @@ -1,5 +1,7 @@ # Go Router 移行方針 +> **注記**: 本ドキュメント内のファイルパスは、特に断りがない限り `apps/dotto/` を基準とする(melos ワークスペース構成のため)。 + ## 概要 現在 `Navigator`(命令型ナビゲーション)で実装されているルーティングを、`go_router`(宣言型ナビゲーション)に置き換える。 @@ -20,6 +22,8 @@ ### 画面一覧とルート構造 +タブのルート名は明示的に定義されたものではなく、`'/${tab.name}'` で動的生成されている(`root_screen.dart:286`)。以下の表は生成結果を記載している。 + #### タブ(`TabItem` enum / `NavigationBar`) | タブ | 画面 | 現在のルート名 | @@ -51,10 +55,10 @@ #### 科目詳細配下 -| 画面 | 現在のルート名 | パラメータ | -|------|---------------|-----------| -| `SubjectDetailScreen` | `/subjects/:id` | `id` | -| 過去問の PDF ビューア | `/subjects/:id/past_exam/:pastExamId` | `pastExamId`, `pastExamUrl` | +| 画面 | 現在のルート名 | パラメータ | 備考 | +|------|---------------|-----------|------| +| `SubjectDetailScreen` | `/subjects/:id` | `id` | | +| 過去問の PDF ビューア | `/subjects/$pastExamId/past_exams/$filename` | `pastExamId`, `filename` | 実コードにも TODO あり。移行後は `/subjects/:subjectId/past_exams/:pastExamId` に修正する(後述) | #### 設定タブ配下 @@ -77,17 +81,26 @@ ### 1. パッケージ導入 -`pubspec.yaml` に追加: +`apps/dotto/pubspec.yaml` に追加: ```yaml dependencies: go_router: ^15.1.2 ``` +> **注意**: 並行 PR #592 で Flutter 3.44.0 / Dart 3.12.0 へのアップグレードが進行中。go_router のバージョンは、マージ後の Flutter / Dart バージョンとの互換性を確認した上で決定する。 + ### 2. ルート定義(`StatefulShellRoute` によるタブナビゲーション) Go Router の `StatefulShellRoute.indexedStack` を使用し、現在の `IndexedStack` + ネスト `Navigator` 構成をそのまま置き換える。 +移行後のルートパス設計では、現状のパス表記ゆれを以下のように統一する: + +- 科目検索タブ: `/subjects`(複数形に統一。実コードの `/subjects/...` に合わせる) +- 講義タブからの科目検索: `/course/subjects`(タブ内サブルートとして維持) +- PDF ビューア: `/course/web_pdf_viewer`(現在のルート名を維持。構造図での `/course/pdf` は採用しない) +- 過去問 PDF: `/subjects/:subjectId/past_exams/:pastExamId`(既存の TODO に従い正しいパス構造に修正) + ``` / → RootScreen(シェル) ├── /course → CourseScreen @@ -96,7 +109,7 @@ Go Router の `StatefulShellRoute.indexedStack` を使用し、現在の `Indexe │ ├── /course/irregular_classes → CourseCancellationScreen │ ├── /course/registration → CourseRegistrationScreen │ ├── /course/preferences → CourseCustomizeScreen -│ └── /course/pdf?url=...&filename=... → WebPdfViewer +│ └── /course/web_pdf_viewer?url=...&filename=... → WebPdfViewer ├── /funch → FunchScreen ├── /map → MapScreen ├── /bus → BusScreen @@ -108,8 +121,9 @@ Go Router の `StatefulShellRoute.indexedStack` を使用し、現在の `Indexe │ ├── /setting/onboarding → OnboardingScreen │ ├── /setting/licenses → SettingsLicenseScreen │ └── /setting/debug → DebugScreen -└── /subject → SearchSubjectScreen(学食無効時) - └── /subject/:id → SubjectDetailScreen +└── /subjects → SearchSubjectScreen(学食無効時) + └── /subjects/:id → SubjectDetailScreen + └── /subjects/:subjectId/past_exams/:pastExamId → CloudflarePdfViewer ``` ### 3. ルーター定義ファイルの新設 @@ -124,9 +138,17 @@ Go Router の `StatefulShellRoute.indexedStack` を使用し、現在の `Indexe `GoRouter` は Riverpod Provider として定義し、`ref` 経由で `redirect` やガードに必要な状態(認証状態、チュートリアル完了フラグ、アプリバージョン等)を参照できるようにする。 +> **重要: `GoRouter` インスタンスの安定性** +> +> `GoRouter` を Riverpod Provider で管理する際、`ref.watch` した状態が変わるたびに Provider が再評価されると `GoRouter` インスタンスが再生成され、ナビゲーションスタックがリセットされる。これを避けるため、以下の方針を採る: +> +> - `GoRouter` インスタンス自体は一度だけ生成し、Provider 内で安定させる +> - 認証状態やフラグの変化は `GoRouter.refreshListenable` に `Listenable` 化した Notifier を渡すことで検知し、`redirect` を再評価させる +> - `ref.watch` ではなく `ref.read` + `refreshListenable` パターンを採用する + ### 4. `MaterialApp` → `MaterialApp.router` への変更 -`app.dart` を以下のように変更: +`lib/app.dart` を以下のように変更: ```dart MaterialApp.router( @@ -166,9 +188,22 @@ MaterialApp.router( `FirebaseAnalyticsObserver` を `GoRouter.observers` に設定する。Go Router は `RouteSettings.name` ではなく `GoRoute.name` / `GoRoute.path` をもとにスクリーン名を報告するため、既存の Analytics データとの整合性を確認する。 +> **注意: `StatefulShellRoute` との組み合わせ** +> +> `StatefulShellRoute` ではタブ内の遷移は各ブランチの `Navigator` を通るため、トップレベルの `GoRouter.observers` だけではタブ配下の画面遷移が Analytics に記録されない可能性がある。各 `StatefulShellBranch` の `observers` にも `FirebaseAnalyticsObserver` を設定する必要がある。 + ### 11. ディープリンク対応 -現在 `app_links` パッケージで `uriLinkStream` を listen しているが、ルーティングとの連携はされていない。Go Router はディープリンクをネイティブサポートしているため、`app_links` の直接利用は不要になる。ルート定義にパスを定義するだけでディープリンクが機能する。 +現在 `app_links` パッケージで `uriLinkStream` を listen しているが、ルーティングとの連携はされていない(`root_viewmodel.dart:43-48` では listen のみで実質未使用)。Go Router はディープリンクをネイティブサポートしているため、`app_links` の直接利用は不要になる。 + +> **注意: ネイティブ設定は別途必要** +> +> Go Router のルート定義にパスを追加するだけではディープリンクは機能しない。以下のネイティブ側設定が別途必要: +> +> - **iOS**: Associated Domains の設定(`apple-app-site-association` ファイル + Xcode の Entitlements) +> - **Android**: `AndroidManifest.xml` への `intent-filter`(`autoVerify` 含む)+ `.well-known/assetlinks.json` +> +> これらの設定は Phase 4 で対応する。 ### 12. オブジェクトパラメータの受け渡し @@ -176,7 +211,16 @@ MaterialApp.router( ### 13. 動的タブ切り替え(学食 ↔ 科目検索) -`isFunchEnabled` フラグによるタブの動的切り替えは、`StatefulShellRoute` のブランチ定義を条件分岐で構築するか、`redirect` でハンドリングする。 +`isFunchEnabled` フラグによるタブの動的切り替えは、本移行の最大の難所である。`StatefulShellRoute.indexedStack` は構築時にブランチ数が固定され、実行時にブランチを追加・削除できない。現状は `isFunchEnabled` で同一スロットの `TabItem.funch` ↔ `TabItem.subject` を入れ替えている(`root_viewmodel.dart:22-29`)。 + +以下の2案を検討し、**Phase 1 でプロトタイプ検証を行った上で決定する**: + +| 案 | 概要 | メリット | デメリット | +|----|------|---------|-----------| +| A | `/funch` と `/subjects` の両ブランチを常に定義し、`NavigationBar` 側で表示/非表示を制御。無効なタブへのアクセスは `redirect` でガード | ナビゲーション状態が保持される。router の再生成が不要 | 非表示ブランチが内部的に存在し続ける | +| B | `isFunchEnabled` 変化時に `GoRouter` インスタンスを再生成 | ブランチ構造がフラグと完全一致 | ナビゲーションスタックがリセットされる。状態ロスのリスク | + +**推奨: 案A**(状態保持の安定性を優先) ## 移行手順 @@ -187,12 +231,14 @@ MaterialApp.router( 3. `MaterialApp.router` への切り替え 4. `StatefulShellRoute.indexedStack` でタブナビゲーションを実装 5. 各タブのルートスクリーンのみ定義(サブルートは Phase 2) +6. **動的タブ切り替え(案A / 案B)のプロトタイプ検証** ### Phase 2: 画面遷移の移行 1. 各 feature の `Navigator.of(context).push(MaterialPageRoute(...))` を `context.go()` / `context.push()` に逐次置換 2. `Navigator.of(context).pop()` を `context.pop()` に置換(ダイアログ内を除く) 3. `RouteSettings` の削除 +4. 過去問 PDF のルートパスを `/subjects/:subjectId/past_exams/:pastExamId` に修正 ### Phase 3: ガード・リダイレクトの移行 @@ -202,15 +248,16 @@ MaterialApp.router( ### Phase 4: ディープリンク対応 -1. Go Router のディープリンク設定 -2. `app_links` パッケージの利用箇所を Go Router に統合 -3. 不要になった `app_links` の listen 処理を削除 +1. iOS / Android のネイティブ設定(Associated Domains、intent-filter 等) +2. Go Router のディープリンク設定 +3. `app_links` パッケージの利用箇所を Go Router に統合 +4. 不要になった `app_links` の listen 処理を削除 ### Phase 5: クリーンアップ 1. 未使用の `Navigator` 関連コード・import の削除 2. `RootViewModelState` から `navigatorKeys` フィールドを削除 -3. Firebase Analytics の動作確認 +3. Firebase Analytics の動作確認(トップレベル + 各 `StatefulShellBranch` の observer 動作検証) 4. 全画面遷移の動作確認・回帰テスト ## 注意事項 From 60d2fdff22d8092c005aa0a26531758549a3dadc Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Fri, 22 May 2026 04:52:59 +0000 Subject: [PATCH 3/3] =?UTF-8?q?app=5Flinks=E5=BB=83=E6=AD=A2=E3=83=BBgo=5F?= =?UTF-8?q?router=E3=81=AE=E3=81=BF=E3=81=A7=E3=83=87=E3=82=A3=E3=83=BC?= =?UTF-8?q?=E3=83=97=E3=83=AA=E3=83=B3=E3=82=AF=E5=AE=8C=E7=B5=90=E3=81=AE?= =?UTF-8?q?=E6=96=B9=E9=87=9D=E3=82=92=E8=BF=BD=E8=A8=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Kanta Oikawa --- docs/go_router_migration_plan.md | 47 ++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/docs/go_router_migration_plan.md b/docs/go_router_migration_plan.md index 50089756..8c741dc4 100644 --- a/docs/go_router_migration_plan.md +++ b/docs/go_router_migration_plan.md @@ -192,18 +192,34 @@ MaterialApp.router( > > `StatefulShellRoute` ではタブ内の遷移は各ブランチの `Navigator` を通るため、トップレベルの `GoRouter.observers` だけではタブ配下の画面遷移が Analytics に記録されない可能性がある。各 `StatefulShellBranch` の `observers` にも `FirebaseAnalyticsObserver` を設定する必要がある。 -### 11. ディープリンク対応 +### 11. ディープリンク対応 — `app_links` の廃止 -現在 `app_links` パッケージで `uriLinkStream` を listen しているが、ルーティングとの連携はされていない(`root_viewmodel.dart:43-48` では listen のみで実質未使用)。Go Router はディープリンクをネイティブサポートしているため、`app_links` の直接利用は不要になる。 +現在 `app_links` パッケージで `uriLinkStream` を listen しているが、ルーティングとの連携はされていない(`root_viewmodel.dart:43-48` では listen のみで実質未使用)。 -> **注意: ネイティブ設定は別途必要** -> -> Go Router のルート定義にパスを追加するだけではディープリンクは機能しない。以下のネイティブ側設定が別途必要: -> -> - **iOS**: Associated Domains の設定(`apple-app-site-association` ファイル + Xcode の Entitlements) -> - **Android**: `AndroidManifest.xml` への `intent-filter`(`autoVerify` 含む)+ `.well-known/assetlinks.json` -> -> これらの設定は Phase 4 で対応する。 +**`app_links` パッケージは完全に廃止し、ディープリンク処理は `go_router` のみで完結させる。** + +Go Router は内部で `PlatformDispatcher` 経由の初回リンク取得とストリームリスニングを自動的に行うため、`app_links` が担っていた役割をすべて代替できる。具体的には: + +- **初回起動リンク(コールドスタート)**: Go Router が `initialLocation` 解決時にプラットフォームから取得 +- **バックグラウンド復帰リンク(ホットスタート)**: Go Router が内部の `WidgetsBindingObserver` で `didPushRouteInformation` を検知し、自動的にルーティング +- **URL → 画面のマッピング**: ルート定義(`GoRoute.path`)に基づいて自動解決。手動パースは不要 + +#### 廃止手順 + +1. `root_viewmodel.dart` から `AppLinks().uriLinkStream.listen(...)` を削除 +2. `import 'package:app_links/app_links.dart'` を削除 +3. `pubspec.yaml` から `app_links: 7.0.0` を削除 +4. `flutter pub get` で依存を更新 +5. Go Router のルート定義が正しく設定されていることを確認(Phase 1〜2 完了後) + +#### ネイティブ設定(Go Router でも別途必要) + +Go Router に移行しても、以下のネイティブ側設定は別途必要(これは `app_links` 利用時と同様): + +- **iOS**: Associated Domains の設定(`apple-app-site-association` ファイル + Xcode の Entitlements) +- **Android**: `AndroidManifest.xml` への `intent-filter`(`autoVerify` 含む)+ `.well-known/assetlinks.json` + +これらの設定は Phase 4 で対応する。Go Router 側では `GoRouter(initialLocation: ..., routes: [...])` の定義のみで、リンク受信からルーティングまでがフレームワーク内で完結する。 ### 12. オブジェクトパラメータの受け渡し @@ -246,12 +262,12 @@ MaterialApp.router( 2. `RootScreen` からガードロジックを削除 3. `RootViewModel` の `navigatorKeys` 関連コードを削除 -### Phase 4: ディープリンク対応 +### Phase 4: ディープリンク対応・`app_links` 廃止 -1. iOS / Android のネイティブ設定(Associated Domains、intent-filter 等) -2. Go Router のディープリンク設定 -3. `app_links` パッケージの利用箇所を Go Router に統合 -4. 不要になった `app_links` の listen 処理を削除 +1. `app_links` パッケージの完全削除(`pubspec.yaml`、import、`uriLinkStream.listen` の削除) +2. iOS / Android のネイティブ設定(Associated Domains、intent-filter 等) +3. Go Router のルート定義によるディープリンク動作確認 +4. コールドスタート・ホットスタート両方でのリンク受信テスト ### Phase 5: クリーンアップ @@ -266,3 +282,4 @@ MaterialApp.router( - 各 Phase で全プラットフォーム(iOS / Android / Web)の動作確認を行う。 - Analytics のスクリーン名が変わる可能性があるため、移行前後のデータ比較を行う。 - `extra` でオブジェクトを渡しているルートは、ディープリンク対応時に ID ベースに変更する。 +- `app_links` パッケージは Go Router 移行に伴い完全に廃止する。ディープリンク処理は Go Router のみで完結させる。