はじめに
Flutter公式が推奨する宣言的ルーティングパッケージ go_router。
「Navigator 1.0(命令的ルーティング)がつらい」「WebのURL同期やディープリンクで消耗している」という方も多いのではないでしょうか。
本記事では、go_router の基本概念から、実務で必ず直面する「画面遷移の使い分け」「タブ画面のスタック保持」「認証ガード」「型安全な実装」まで、実戦的なコード例とともに徹底解説します!
1. 快速入門:なぜ go_router を選ぶのか?
1.1 命令的(Navigator 1.0) vs 宣言的(Router 2.0)
従来の Navigator.push() は、画面の上に新しい画面を「命令的」に積み重ねるスタック方式でした。この方式は、Webのブラウザバックや、通知から特定の画面へ直接遷移する**ディープリンク(Deep Linking)**の実装において、状態の同期が非常に困難でした。
go_router は、「URL = アプリの画面状態」 とする宣言的ルーティングを採用しています。
- URL駆動: アプリの状態が明示的なURLパスと紐付きます。
- マルチプラットフォーム対応: Webのブラウザアドレス欄や「進む/戻る」ボタンとネイティブに同期します。
- 強力なリダイレクト: 「未ログインならログイン画面へ強制遷移」といったガード処理がルーティング層で完結します。
1.2 パッケージの導入
pubspec.yaml に最新の安定版を追加します。
dependencies:
flutter:
sdk: flutter
go_router: ^14.0.0 # プロジェクトに応じて最新バージョンを使用してください
1.3 最もシンプルなルーティング設定
まず、アプリ全体のルート(Route)を定義するグローバルな GoRouter インスタンスを作成します。
// 1. ルーターの配置設定
final GoRouter _router = GoRouter(
initialLocation: '/', // 初期表示パス
debugLogDiagnostics: true, // コンソールに遷移ログを出力(デバッグ用)
routes: <RouteBase>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) => const HomeScreen(),
),
GoRoute(
path: '/details',
builder: (BuildContext context, GoRouterState state) => const DetailsScreen(),
),
],
);
// 2. MaterialApp.router に注入
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: _router,
title: 'go_router デモ',
);
}
}
2. 画面遷移の基本とパラメータ伝達
2.1 命運を分ける使い分け:go() vs push()
go_router で最も初学者を悩ませるのが、この2つのメソッドの違いです。これらはルーティングスタック(履歴)の再構築ロジックが根本的に異なります。
-
context.go('/details')ルーターツリーの構造を書き換えます。 定義した画面の親子関係に従ってスタックを再生成します。例えば、深い階層からgo('/details')を呼ぶと、それまでのスタックはクリアされ、[HomeScreen, DetailsScreen]という綺麗な状態に再構築されます。 -
context.push('/details')現在の画面の上に、物理的に新しい画面を重ねます。 ツリーの構造を無視して、ただ現在の画面の上にアタッチされるため、戻る(pop)と必ず遷移前の画面に戻ります。
選定基準: アプリのメインフロー(タブ切り替え、購入完了、マイページへの移動など)は
go()、一時的な確認画面(商品詳細の閲覧、設定、コメント一覧など)はpush()を使いましょう。
2.2 パラメータの取得方法(Path vs Query vs Extra)
① パスパラメータ(Path Parameters)
/user/123 のように、特定のリソースを識別するために使用します。
// 設定
GoRoute(
path: '/user/:id',
builder: (context, state) {
final userId = state.pathParameters['id']; // '123' が取得できる
return UserScreen(id: userId);
},
)
// 遷移時
context.go('/user/123');
② クエリパラメータ(Query Parameters)
/search?keyword=flutter のように、検索条件やフィルタリングなどの任意パラメータに使用します。
// 設定
GoRoute(
path: '/search',
builder: (context, state) {
final keyword = state.uri.queryParameters['keyword']; // 'flutter' が取得できる
return SearchScreen(keyword: keyword);
},
)
// 遷移時
context.go('/search?keyword=flutter');
③ 独自オブジェクトの伝達(Extra Object)
複雑な Dart のクラスオブジェクトをそのまま渡したい場合は extra を使用します。
// 遷移時にオブジェクトを添付
context.go('/profile', extra: UserModel(name: '太郎', age: 20));
// 設定での受け取り
GoRoute(
path: '/profile',
builder: (context, state) {
final user = state.extra as UserModel;
return ProfileScreen(user: user);
},
)
⚠️ 注意点:
extraは Web環境においてブラウザをリロードしたり、ディープリンクから直接アクセスしたりすると データが消失(null) します。Web対応アプリやディープリンクを重視する場合は、IDなどを極力パスパラメータに乗せる設計にしましょう。
3. 高度なルーティング:ネストとリダイレクトガード
3.1 タブ状態を維持する StatefulShellRoute
ボトムナビゲーション(BottomNavigationBar)を持つアプリで、「タブを切り替えても、各タブの画面スタックやスクロール位置を維持したい」ケースは多々あります。これを提供するのが StatefulShellRoute です。
final GoRouter _advancedRouter = GoRouter(
initialLocation: '/home',
routes: [
StatefulShellRoute.indexedStack(
builder: (context, state, navigationShell) {
// 共通のボトムナビゲーションを持つシェル(骨組み)を返す
return ScaffoldWithNavBar(navigationShell: navigationShell);
},
branches: [
// タブ1: ホーム分岐
StatefulShellBranch(
routes: [
GoRoute(
path: '/home',
builder: (c, s) => const HomeTab(),
routes: [
GoRoute(path: 'details', builder: (c, s) => const HomeDetailsScreen()),
],
),
],
),
// タブ2: 設定分岐
StatefulShellBranch(
routes: [
GoRoute(path: '/settings', builder: (c, s) => const SettingsTab()),
],
),
],
),
],
);
3.2 認証状態による動的リダイレクト(ガード)
ユーザーのログイン状態に応じて、自動で表示画面を制限・誘導する「ルート守衛」機能です。
final GoRouter _authRouter = GoRouter(
initialLocation: '/dashboard',
redirect: (BuildContext context, GoRouterState state) {
final bool loggedIn = AuthService.isLoggedIn; // 状態管理から取得
final bool isLoggingIn = state.matchedLocation == '/login';
if (!loggedIn) {
// 未ログイン、かつログイン画面にいない場合は強制遷移
return isLoggingIn ? null : '/login';
}
if (loggedIn && isLoggingIn) {
// ログイン済みでおかしいのにログイン画面を開こうとしたらダッシュボードへ戻す
return '/dashboard';
}
return null; // パス(リダイレクトなし)
},
routes: [
GoRoute(path: '/login', builder: (c, s) => const LoginScreen()),
GoRoute(path: '/dashboard', builder: (c, s) => const DashboardScreen()),
],
);
4. UI/UXの最適化:カスタムアニメーションとエラー処理
4.1 ページ遷移アニメーションのカスタマイズ
builder の代わりに pageBuilder と CustomTransitionPage を使用することで、遷移時のアニメーションを自由に上書きできます。
GoRoute(
path: '/fade-page',
pageBuilder: (context, state) => CustomTransitionPage<void>(
key: state.pageKey,
child: const TargetScreen(),
transitionDuration: const Duration(milliseconds: 300),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
// フェード & スケールを組み合わせたアニメーション
return FadeTransition(
opacity: animation,
child: ScaleTransition(
scale: Tween<double>(begin: 0.95, end: 1.0).animate(animation),
child: child,
),
);
},
),
);
Web向けの最適化: デスクトップやWebでアニメーションを完全にオフにしたい場合は、
NoTransitionPageをそのままリターンすればOKです。
4.2 404エラーページのハンドリング
不正なURLが入力された際、アプリがクラッシュするのを防ぎ、専用のエラー画面へ誘導します。
final GoRouter _router = GoRouter(
routes: [...],
errorBuilder: (context, state) => Scaffold(
body: Center(
child: Text('お探しのページ( ${state.uri} )は見つかりませんでした。'),
),
),
);
5. 実戦的な拡張:go_router_builder による型安全化
大規模開発において、文字列によるURL指定(例: context.go('/user/123/profile'))はタイポやリファクタリングの障壁になります。公式提供のコード生成ツールを使って、これを型安全(Type-safe)に記述しましょう。
5.1 パッケージの追加
dev_dependencies:
build_runner: ^2.4.0
go_router_builder: ^2.8.0
5.2 ルーティングデータの定義
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
part 'routes.g.dart'; // ジェネレートされるファイル
@TypedGoRoute<HomeRoute>(
path: '/',
routes: [
TypedGoRoute<UserRoute>(path: 'user/:id'),
],
)
class HomeRoute extends GoRouteData {
@override
Widget build(BuildContext context, GoRouterState state) => const HomeScreen();
}
class UserRoute extends GoRouteData {
UserRoute({required this.id});
final String id; // 型安全なパラメータ定義
@override
Widget build(BuildContext context, GoRouterState state) => UserScreen(id: id);
}
以下のコマンドを実行してビルドします。
dart run build_runner build
5.3 型安全な遷移の実行
生成された拡張メソッドにより、引数の渡し忘れやタイポがコンパイルエラーで検知できるようになります。
// これだけで安全に遷移可能!引数 id が必須になります
UserRoute(id: '123').go(context);
6. まとめ&ベストプラクティス
go_router をプロダクションへ導入する際のまとめです。
-
メインフローは
go、サブはpush: 画面スタックの再構築挙動を理解して使い分ける。 -
extraの依存度は低く: Webでのリロードやディープリンクで消えて困るデータはパスパラメータに載せる。 -
ボトムナビには
StatefulShellRoute: 各タブの状態を綺麗に保つ。 -
型安全の導入: チーム開発では初期段階から
go_router_builderを導入し、文字列ハードコードを撲滅する。
これらを意識することで、堅牢でメンテナンス性の高い Flutter アプリの画面遷移基盤を構築できます。ぜひ試してみてください!