🏑
go router builderを使ってみた
Overview
どんなパッケージなのかといいますと、go routerをtype safe(型安全)に扱えるようにしたパッケージですね。
type safe
〔プログラミング言語などが〕型安全 な◆変数の型を区別し型ごとに許される操作を限定することで、ある種の誤りを検出したりメモリーのアクセス違反を防止したりできること。
こんな解説もありました。
動画も作ってみた😅
summary
普通のgo routerと何が違うのか?
パスとか指定して、そこに画面遷移させるコードを書くわけですけど、ルートを定義してコードを自動生成すると、画面遷移の設定されたコードを使うことができるようになります。
まずは、パッケージを追加しよう。
go router builderを使うための環境構築をする。
- riverpodをインストールする
flutter pub add flutter_riverpod
flutter pub add riverpod_annotation
flutter pub add dev:riverpod_generator
flutter pub add dev:build_runner
flutter pub add dev:custom_lint
flutter pub add dev:riverpod_lint
- go router builderをインストールする
flutter pub add go_router
flutter pub add go_router_builder
🍐UIを作る
- 最初に表示するページ
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../router/router.dart';
class HomePage extends ConsumerWidget {
const HomePage({super.key});
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(
title: const Text('Go Router Builder'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
// AppBarの戻るボタンが表示されない画面遷移のコード
const NextRoute().push(context);
},
child: const Text('Next')),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
// Stackが削除されて、AppBarに戻るボタンが表示されないコード
const NextRoute().go(context);
},
child: const Text('go')),
],
),
),
);
}
}
- 🍇画面遷移して表示するページ
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../router/router.dart';
class NextPage extends ConsumerWidget {
const NextPage({super.key});
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: const Text('Next Page'),
),
body: Center(
child: Column(
children: [
ElevatedButton(
onPressed: () {
// pushを使った場合は前のページへ画面遷移できるコード
context.pop();
},
child: const Text('back'),
),
],
),
),
);
}
}
🚄ルートを定義する
riverpod_generatorを使っているので、今までとは書き方が違って戸惑うことがあると思いますが、書き方が関数ぽくなっただけです。コマンドを打つとプロバイダーを定義したファイルが自動生成されます。
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../page/home_page.dart';
import '../page/next_page.dart';
part 'router.g.dart';// ファイル名と同じ名前にして、.g.dartとつける
// flutter pub run build_runner watch --delete-conflicting-outputs
GoRouter router(RouterRef ref) {
return GoRouter(
debugLogDiagnostics: true,
routes: $appRoutes,// 自動生成されたファイルからパスを読み込む
// 404ページを指定
errorPageBuilder: (context, state) {
return const MaterialPage(
child: Scaffold(
body: Center(
child: Text('Page not found'),
),
));
});
}
/// [この位置にルートを定義する]
/* buildメソッドの設定が変わっていた:
https://pub.dev/documentation/go_router_builder/latest/
*/
/// [HomePageのルート]
<HomeRoute>(
path: '/',
)
class HomeRoute extends GoRouteData {
const HomeRoute();
Widget build(BuildContext context, GoRouterState state) {
return const HomePage();
}
}
/// [NextPageのルート]
<NextRoute>(
path: '/next',
)
class NextRoute extends GoRouteData {
const NextRoute();
Widget build(BuildContext context, GoRouterState state) {
return const NextPage();
}
}
🔫ファイルを自動生成するコマンドを打つ
flutter pub run build_runner watch --delete-conflicting-outputs
こんなファイルが自動生成されれば成功です。
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'router.dart';
// **************************************************************************
// GoRouterGenerator
// **************************************************************************
List<RouteBase> get $appRoutes => [
$homeRoute,
$nextRoute,
];
RouteBase get $homeRoute => GoRouteData.$route(
path: '/',
factory: $HomeRouteExtension._fromState,
);
extension $HomeRouteExtension on HomeRoute {
static HomeRoute _fromState(GoRouterState state) => const HomeRoute();
String get location => GoRouteData.$location(
'/',
);
void go(BuildContext context) => context.go(location);
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) =>
context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
}
RouteBase get $nextRoute => GoRouteData.$route(
path: '/next',
factory: $NextRouteExtension._fromState,
);
extension $NextRouteExtension on NextRoute {
static NextRoute _fromState(GoRouterState state) => const NextRoute();
String get location => GoRouteData.$location(
'/next',
);
void go(BuildContext context) => context.go(location);
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) =>
context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
}
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$routerHash() => r'70de5cee48590e74afaedb0dfdcaa912a2148ae6';
/// See also [router].
(router)
final routerProvider = AutoDisposeProvider<GoRouter>.internal(
router,
name: r'routerProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$routerHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef RouterRef = AutoDisposeProviderRef<GoRouter>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
ルートを定義したプロバイダーを読み込んでアプリをビルドして操作してみましょう!
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../router/router.dart';
class MyApp extends ConsumerWidget {
const MyApp({super.key});
Widget build(BuildContext context, WidgetRef ref) {
return MaterialApp.router(
debugShowCheckedModeBanner: false,
routerConfig: ref.watch(routerProvider),
title: 'Flutter Demo',
theme: ThemeData(
),
);
}
}
main.dartはコード分けすぎてこれだけになりました。
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hello/page/my_app.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// await Firebase.initializeApp(
// options: DefaultFirebaseOptions.currentPlatform,
// );
runApp(const ProviderScope(child: MyApp()));
}
これが最初の画面
これがpushを使った例: AppBarに戻るボタンが表示される
これがgoを使った例: AppBarに戻るボタンが表示されなくなる。context.pop
で前のページへ戻ろうとすると、画面遷移のエラーが発生する😱
thoughts
使ってみた感想ですが、go routerってバージョンアップが早いので、go router builderが新しいバージョンだとサポートされていないことがあるらしいので、もし導入する場合は注意が必要かもしれません。
Discussion