Flutterのroutingライブラリであるgo_routerについてのまとめ

こんにちは、株式会社Pentagonでアプリ開発をしている石渡港です。
https://pentagon.tokyo

今回はFlutterのroutingライブラリであるgo_routerについてまとめました。
その結果、分かりやすくルーティングを宣言できることがわかりました。

【この記事を読むメリット】

  • go_routerについて理解できます。
  • go_routerの実装方法がわかります。

【こんな方に参考にしていただきたい】

  • Flutterのルーティングライブラリをどれにするか悩んでいる人。

【調査の動機】
普段、弊社のプロジェクトではGetXを利用した遷移をしていますが、他に良いライブラリがあれば乗り換えたいと考えたためです。

【調査結果】

  • go_routerは宣言がとても簡潔でした。
  • go_routerはサブルートの設定が容易でした。
  • go_routerはNestedNavigationの設定が非常に簡単でした。
目次

【結論】

今回は、最近話題になっているgo_routerについて、特徴的な実装を3点にまとめました。
また、著名なルーティングライブラリについても記載しています。

  • ルーティング設定方法
  • NestedNavigationを設定する方法について
  • サブルートを設定する方法について

一つずつ解説していきます。

著名なルーティングライブラリ

ライブラリ名 特徴 リンク
go_rotuer 簡潔で、深いルーティングを宣言できます。 https://pub.dev/packages/go_router
auto_route 短いコードから、自動生成したルータクラスを利用したルーティングが可能です。 https://pub.dev/packages/auto_route
routemaster 簡潔なルーティングを宣言できます。 https://pub.dev/packages/routemaster
fluro Queryなども対応できて、簡潔なルーティングを宣言できます。 https://pub.dev/packages/fluro
get すべてをカバーする強力なソリューションです。 https://pub.dev/packages/get

1. ルーティング設定方法

下記のような簡単な設定でルーティングの宣言が可能です。

main.dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:test_go_router/home_screen.dart';
import 'package:test_go_router/tab_screen.dart';

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

class MyApp extends StatelessWidget {
  final _router = GoRouter(
    routes: [
      GoRoute(
        path: '/',
        builder: (context, state) => const HomeScreen(),
      ),
      GoRoute(
        path: '/tab',
        builder: (context, state) => const TabScreen(),
      ),
    ],
  );

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routeInformationParser: _router.routeInformationParser,
      routerDelegate: _router.routerDelegate,
    );
  }
}

home_screen.dart

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

class HomeScreen extends StatelessWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          'Home',
        ),
      ),
      body: SafeArea(
        child: Column(
          children: [
            MaterialButton(
              child: Text('Tabへ'),
              onPressed: () {
                GoRouter.of(context).push('/tab'); // スタックで操作する場合
                // GoRouter.of(context).go('/tab'); // 現ルートと入れ替える場合
                // context.go('/tab'); // 現ルートと入れ替える場合
              },
            )
          ],
        ),
      ),
    );
  }
}

tab_screen.dart

import 'package:flutter/material.dart';

class TabScreen extends StatelessWidget {
  const TabScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          'Tab',
        ),
      ),
    );
  }
}

2.NestedNavigationを設定する方法について

main.dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:test_go_router/home_screen.dart';
import 'package:test_go_router/tab_next_screen.dart';
import 'package:test_go_router/tab_screen.dart';

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

class MyApp extends StatelessWidget {
  final _router = GoRouter(
    routes: [
      GoRoute(
        path: '/',
        builder: (context, state) => const HomeScreen(),
      ),
      GoRoute(
        path: '/tab/:index', // :hoge とすることでパラメタとして値を与えられる
        builder: (context, state) {
          final index = state.params['index'];
          return TabScreen(currentIndex: int.parse(index!));
        },
      ),
      GoRoute(
        path: '/tab/:index/next/:number', // :hoge とすることでパラメタとして値を与えられる
        builder: (context, state) {
          final number = state.params['number'];
          return TabNextScreen(number: number!);
        },
      ),
    ],
  );

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routeInformationParser: _router.routeInformationParser,
      routerDelegate: _router.routerDelegate,
    );
  }
}

tab_screen.dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:test_go_router/tab_detail_screen.dart';

class TabScreen extends StatefulWidget {
  late final int index;
  TabScreen({required int currentIndex, Key? key}) : super(key: key) {
    assert(currentIndex != -1);
    index = currentIndex;
  }

  @override
  _TabScreenState createState() => _TabScreenState();
}

class _TabScreenState extends State<TabScreen> with TickerProviderStateMixin {
  late final TabController _controller;
  static constlength= 3;

  @override
  void initState() {
    _controller = TabController(
      length:length,
      vsync: this,
      initialIndex: widget.index,
    );
    super.initState();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  void didUpdateWidget(covariant TabScreen oldWidget) {
    _controller.index = widget.index;
    super.didUpdateWidget(oldWidget);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('タブ'),
        bottom: TabBar(
          controller: _controller,
          tabs: [
            for (final f in [0, 1, 2]) Tab(text: f.toString())
          ],
          onTap: (index) => _tap(context, index),
        ),
      ),
      body: TabBarView(
        controller: _controller,
        children: [
          for (final f in [0, 1, 2])
            TabDetailScreen(
              index: f,
            )
        ],
      ),
    );
  }

  void _tap(BuildContext context, int index) => context.go('/tab/$index'); // 画面遷移させることでタブとして機能させる
}

tab_deatil_screen.dart

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

class TabDetailScreen extends StatefulWidget {
  const TabDetailScreen({required this.index, Key? key}) : super(key: key);
  final int index;

  @override
  _TabDetailScreenState createState() => _TabDetailScreenState();
}

class _TabDetailScreenState extends State<TabDetailScreen>
    with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return ListView(
      children: [
        for (var i = 0; i < 20; i++)
          ListTile(
            title: Text(i.toString()),
            onTap: () => context.push('/tab/${widget.index}/next/$i'),
          ),
      ],
    );
  }
}

tab_next_screen.dart

import 'package:flutter/material.dart';

class TabNextScreen extends StatefulWidget {
  const TabNextScreen({required this.number, Key? key}) : super(key: key);

  final String number;

  @override
  _TabNextScreenState createState() => _TabNextScreenState();
}

class _TabNextScreenState extends State<TabNextScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('次の画面'),
      ),
      body: SafeArea(
        child: Center(
          child: Text(widget.number),
        ),
      ),
    );
  }
}

go_routerを利用することで簡単にパラメタを受け渡しできました。

もし、モデルなどを渡したいときはextra を使うと便利に渡せます。

3. サブルートを設定する方法について

main.dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:test_go_router/home_screen.dart';
import 'package:test_go_router/tab_next_screen.dart';
import 'package:test_go_router/tab_screen.dart';

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

class MyApp extends StatelessWidget {
  final _router = GoRouter(
    routes: [
      GoRoute(
        path: '/',
        builder: (context, state) => const HomeScreen(),
        routes: [
          GoRoute(
            path: 'tab/:index',
            builder: (context, state) {
              final index = state.params['index'];
              return TabScreen(currentIndex: int.parse(index!));
            },
            routes: [
              GoRoute(
                path: 'next/:number',
                builder: (context, state) {
                  final number = state.params['number'];
                  return TabNextScreen(number: number!);
                },
              ),
            ],
          ),
        ],
      ),
    ],
  );

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routeInformationParser: _router.routeInformationParser,
      routerDelegate: _router.routerDelegate,
    );
  }
}

こちらの機能を利用することで context.go()GoRouter.of(context).go などで遷移した際にスタックで利用できるようになります。

【まとめ】

go_routerは宣言がとても簡潔でした。また、サブルートの設定やNestedNavigationの設定も非常に簡単です。
利用してみてわかったのが、popに関してはGetXのほうが変数を簡単に渡せるため、便利そうだと思いました。

採用情報はこちら
目次