こんにちは、株式会社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のほうが変数を簡単に渡せるため、便利そうだと思いました。