Flutter go_router_builder v3.0.0 アップデート奮闘記
こんにちは、URBAN HACKSでAndroid/Flutterエンジニアをしている後藤です。
はじめに
Flutterのルーティングを強力にサポートするgo_routerと、そのコード生成ツールgo_router_builder。
今回は、go_router_builderをv3.0.0にアップデートした際の体験をまとめます。
go_router_builder v3.0.0の主な変更点
- 公式リリースノート: CHANGELOG
- 主な変更点
- v3.0.0から各ルートのクラスに
_$RouteName
のmixinを追加する必要があります。これを追加しないとビルド時にエラーとなるため注意が必要です。
- v3.0.0から各ルートのクラスに
アップデート手順
- pubspec.yamlのバージョン更新
-
flutter pub get
の実行- この時点で
dart run build_runner build --delete-conflicting-outputs
を実行すると、各ルートのクラスでmixinが不足している旨の警告が表示されます。[SEVERE] go_router_builder on lib/router/router.dart: Missing mixin clause `with _$HogeRoute` package:sample/router/router.dart:51:7 ╷ 51 │ class HogeRoute extends GoRouteData { │ ^^^^^^^^^
- この時点で
- コード修正
- Before
class HogeRoute extends GoRouteData { const HogeRoute(); Widget build(BuildContext context, GoRouterState state) { return const HogePage(); } }
- After
// _$HogeRouteを追加する class HogeRoute extends GoRouteData with _$HogeRoute { const HogeRoute(); Widget build(BuildContext context, GoRouterState state) { return const HogePage(); } }
- Before
- コード生成の再実行
dart run build_runner build --delete-conflicting-outputs
ハマったポイント
全てのルーティングを1つのファイルで行っている場合は、前述のアップデート手順だけ行えば問題ありません 👍️
しかし、可読性の観点からルーティングを複数ファイルに分割している場合は注意が必要です。
アプリにボトムナビゲーションがあるとルーティングファイルが肥大化してしまうので、ファイルを分割して管理している方も多いと思います。
前提
下記のファイル構成でルーティングを管理していたとします。
router/
├── router.dart # ルート全体のエントリーポイント
└── bottom_nav_router/
├── home_router.dart # ホーム画面用ルート
└── settings_router.dart # 設定画面用ルート
// router/router.dart
import 'package:sample/features/tutorial/presentations/tutorial_page.dart';
part 'router.g.dart';
<BottomNavShellRoute>(
branches: <TypedStatefulShellBranch<StatefulShellBranchData>>[
homeStatefulShellBranch,
settingsStatefulShellBranch,
],
)
class BottomNavShellRoute extends StatefulShellRouteData {
// 略
}
<TutorialRoute>(
path: '/tutorial',
)
class TutorialRoute extends GoRouteData with _$TutorialRoute {
const TutorialRoute();
Widget build(BuildContext context, GoRouterState state) => const TutorialPage();
}
// 他の画面など
// router/bottom_nav_router/home_router.dart
import 'package:sample/features/home/presentations/home_page.dart';
const homeStatefulShellBranch = TypedStatefulShellBranch<HomeBranch>(
// ...
)
class HomeBranch extends StatefulShellBranchData {
// ...
}
class HomeRoute extends GoRouteData with _$HomeRoute {
const HomeRoute();
Widget build(BuildContext context, GoRouterState state) {
return const HomePage();
}
}
// 他の画面など
この状態だと何がまずいのか
この状態のままコード生成を行うと、router/
配下にコードが生成されます。
router/
├── router.dart # ルート全体のエントリーポイント
├── router.g.dart # go_router_builderによって生成されたファイル
└── bottom_nav_router/
├── home_router.dart # ホーム画面用ルート
└── settings_router.dart # 設定画面用ルート
go_router_builderで生成されるコードは、エントリーポイントの自動生成ファイルであるrouter.g.dart
に全て集約されます。
home_router.dart
にあるHomeRoute
関連の定義もrouter.g.dart
に生成されます。
つまり、今回追加したい_$HomeRoute
がrouter.g.dart
に生成されます。
また、_$HomeRoute
はプライベートなmixinなのでhome_router.dart
からは参照できません。
class HomeRoute extends GoRouteData with _$HomeRoute // ← ここが参照できない
・・・。詰んだ😇
でも大丈夫です!解決策を見つけました!
router.dart
に全部まとめる
最初に選択しようとした解決策:ファイル分割を諦めて、router.dart
に全て集約すれば解決します!
ただ、あまりスマートじゃないよなぁと後ろ髪を引かれていました。
せっかくファイル分割して可読性が良かったのに。。。😵💫
part
とpart of
でまとめる
最終的に採用した解決策:他に方法無いかなぁとrouter.g.dart
を眺めていたら、ファイルの先頭に
part of 'router.dart';
と書いてあり、「この書き方ってなんなの?」と思って調べてみたらまさに今回の問題をドンピシャで解決できる書き方でした。
part
とpart of
とは
Dartのpart
とpart of
は、1つのライブラリ(ファイル)を複数のファイルに分割して管理するための仕組みです。
主に大きなクラスや複数の関連クラスをファイル分割したいときに使います。
-
part
で指定したファイルは、親ファイルの一部として扱われます。 -
part of
で親ファイル名を明示することで、親ファイルのプライベートを参照できます。
修正後の実装
// router/router.dart
import 'package:sample/features/home/presentations/home_page.dart'; // 追加
import 'package:sample/features/tutorial/presentations/tutorial_page.dart';
part './bottom_nav_router/home_router.dart'; // 追加
part './bottom_nav_router/settings_router.dart'; // 追加
part 'router.g.dart';
// ...既存実装...
// router/bottom_nav_router/home_router.dart
// part ofを記載する場合はpart of以外を宣言できません
// importは親ファイルであるrouter.dartに書きます
//import 'package:sample/features/home/presentations/home_page.dart';
part of '../router.dart';
// ...既存実装...
// router.dartの一部なので_$HomeRouteを参照できる
class HomeRoute extends GoRouteData with _$HomeRoute {
// ...
}
まとめ・所感
無事アップデートできてよかったです🍻
part
とpart of
については、RiverpodやFreezed等のコード生成でも目にしていたのですが、あまり気に留めずに使っていました。
今回の知見を応用して、肥大化してしまった他のファイルについても適切なサイズにリファクタしていきたいと思います🙌
Discussion