🙌

【Flutter】go_routerでメニューバーがある状態で画面遷移

2023/01/21に公開

はじめに

Flutterのgo_routerを使って、メニューバーがある状態で画面遷移を行う方法です。

  • 実行環境
    Chrome

  • flutterのバージョン

$ flutter --version

Flutter 3.3.10 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 135454af32 (5 weeks ago)2022-12-15 07:36:55 -0800
Engine • revision 3316dd8728
Tools • Dart 2.18.6 • DevTools 2.15.0
  • go_router
    pubspec.yamlにgo_routerを追加してください
dependencies:
  go_router: ^5.2.1

機能

  • ログイン画面でログインボタンを押すと、メニューバーを表示する画面に遷移し、ログアウトボタンを押すとログイン画面に戻ります。
  • 画面遷移のアニメーションの設定をしていないので、遷移の仕方がおかしなことになっています。

ポイント

メニューバーがある画面のルーティングはShellRouteで定義します

実装手順

  • メニューバー(HomeNavigationRail)を作成。

    • ここでどのタブがアクティブにするか、タブを押したらどこに遷移するかを定義。
  • 各画面を作成。

  • ルートの定義でメーニューバーを表示させる画面の遷移をShellRouteでまとめる(メニューバーを使わないルートはShellRouteで囲まない)

コード

メニューバーの実装

home_navigation_rail.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

class HomeNavigationRail extends StatelessWidget {
  const HomeNavigationRail({
    required this.child,
    Key? key,
  }) : super(key: key);

  final Widget child;
  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          NavigationRail(
              trailing: ElevatedButton(
                onPressed: (() async {
                  context.go('/login');
                }),
                child: const Text(
                  "ログアウト",
                ),
              ),
              backgroundColor: const Color.fromARGB(255, 194, 227, 255),
              destinations: const [
                NavigationRailDestination(
                  icon: Icon(Icons.home),
                  label: Text(
                    "ホーム",
                  ),
                ),
                NavigationRailDestination(
                  icon: Icon(Icons.article),
                  label: Text(
                    "記事一覧",
                  ),
                ),
                NavigationRailDestination(
                  icon: Icon(Icons.face),
                  label: Text(
                    "ユーザー一覧",
                  ),
                ),
                NavigationRailDestination(
                  icon: Icon(Icons.settings),
                  label: Text(
                    "設定",
                  ),
                ),
              ],
              selectedIndex: calculateSelectedIndex(context),
              onDestinationSelected: (int index) =>
                  onItemTapped(index, context)),
          Flexible(
            child: Container(
              padding: const EdgeInsets.all(16),
              child: child,
            ),
          )
        ],
      ),
    );
  }

  static int calculateSelectedIndex(BuildContext context) {
    final String location = GoRouterState.of(context).location;
    if (location.startsWith('/home/article')) {
      return 1;
    }
    if (location.startsWith('/home/user')) {
      return 2;
    }
    if (location.startsWith('/home/setting')) {
      return 3;
    }

    return 0;
  }

  void onItemTapped(int index, BuildContext context) {
    switch (index) {
      case 0:
        GoRouter.of(context).go('/');
        break;
      case 1:
        GoRouter.of(context).go('/home/article');
        break;
      case 2:
        GoRouter.of(context).go('/home/user');
        break;
      case 3:
        GoRouter.of(context).go('/home/setting');
        break;
    }
  }
}

ルーティングの実装

page_router.dart
import 'package:flutter/material.dart';
import 'package:flutter_tab_base/each_screen.dart';
import 'package:flutter_tab_base/home_navigation_rail.dart';
import 'package:go_router/go_router.dart';

class PageRouter {
  static final GoRouter router =
      GoRouter(initialLocation: '/login', routes: <RouteBase>[
    GoRoute(
      path: '/login',
      builder: (BuildContext context, GoRouterState state) {
        return const LoginPage();
      },
    ),
    ShellRoute(
      builder: (BuildContext context, GoRouterState state, Widget child) {
        return HomeNavigationRail(child: child);
      },
      routes: <RouteBase>[
        GoRoute(
          path: '/',
          builder: (context, state) => const HomePage(),
          routes: <RouteBase>[
            GoRoute(
                path: 'home/article',
                builder: (context, state) => const ArticlePage()),
            GoRoute(
                path: 'home/user',
                builder: (context, state) => const UserPage()),
            GoRoute(
                path: 'home/setting',
                builder: (context, state) => const SettingPage()),
          ],
        ),
      ],
    )
  ]);
}

各画面の実装

each_screen.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

class LoginPage extends StatelessWidget {
  const LoginPage({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
          child: ElevatedButton(
              onPressed: () {
                context.go('/');
              },
              child: const Text('ログイン'))),
    );
  }
}

class UserPage extends StatelessWidget {
  const UserPage({super.key});

  
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
        child: Text('ユーザー'),
      ),
    );
  }
}

class ArticlePage extends StatelessWidget {
  const ArticlePage({super.key});

  
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
        child: Text('アーティスト'),
      ),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
        child: Text('ダッシュボード'),
      ),
    );
  }
}

class SettingPage extends StatelessWidget {
  const SettingPage({super.key});

  
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
        child: Text('設定'),
      ),
    );
  }
}

ルーティングの設定

main.dart
import 'package:flutter/material.dart';
import 'package:flutter_tab_base/page_router.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: PageRouter.router,
    );
  }
}


終わりに

これだと画面遷移の仕方がおかしいので、遷移するときのアニメーションを消したら良いと思います。
https://zenn.dev/maropook/articles/d0bea36b1643aa

GitHub

https://github.com/maropook/flutter_tab_base

参考

https://zenn.dev/kiiimii/articles/c2c36b75d31c91

Discussion