前回、go_routerを使って単純な画面遷移を実装してみました。
前回の記事はこちら
今回はもう少し複雑にして、
画面間で情報の受け渡しを行うことを考えたいと思います。
サンプルコードを用意した上で解説しますので、
どのように画面遷移を扱うことになるか、ぜひ見てってください。
画面遷移の考え方と、状態管理手法##
画面構成は以下のようになっています。

今回実装するのは本のリストページから本の詳細ページへの画面遷移です。
手続的な画面遷移(Navigator 1.0)では、画面遷移時にBook
オブジェクトを手渡していました。
go_routerを使った画面遷移でもそのままBook
オブジェクトを手渡しすることは可能ですが、
この場合、ディープリンク(アドレスバーに直接パスを打ち込んでの画面遷移)はできなくなります。
そのため、以下のような手順を踏んで画面遷移を行います。
1. 遷移のために与えられたパスから、パラメータを取得する
2. パラメータから対応するオブジェクトを取得する
3. オブジェクトをページに渡し画面を遷移(構築)する

1.と3.についてはgo_routerにて対応可能ですが、
2.のパラメータから対応するオブジェクトを取得するためには、
オブジェクトのリストをあらかじめ共有しておき、リストから取得する、といった手順が必要となります。
今回はオブジェクトのリストが追加や削除ができる可変なものだと想定し、
リストの状態を状態管理手法(Provider)を使って管理し実装しました。
サンプルコード##
今回のサンプルコードはこちらになります。
サンプルコードの内容を解説しますので、合わせてご覧ください。
サブルートの構築##
画面間の情報の受け渡しについて説明する前にサブルートについて説明します。
今回の画面遷移は本のリストページからの遷移です。
詳細画面はリストページ以外からは遷移できません。
このことを強調するために、サブルートにて詳細画面を実装します。
コードは以下のようになります。
//画面の情報を定義する
final _router = GoRouter(
initialLocation: '/',
routes: [
//省略
GoRoute(
name: 'list',
path: '/list',
pageBuilder: (context, state) => MaterialPage(
key: state.pageKey,
child: const BookListPage(),
),
//1
routes: [
GoRoute(
name: 'detail',
//2
path: 'detail/:id',
pageBuilder: (context, state) {
//省略
}),
]),
],
//省略
);
//1
GoRoute
のインスタンスの中にroutes
を定義することができます。
今回でいえば、List
のサブルートとしてDetail
が定義されます。
//2
サブルートのパスは'親のパス + サブルートのパス'になります。
今回でいえば、detail
ページのパスは/list/detail/1
となります。
(:id
については後述します。)
パラメータの取得##
サブルートのパスに:id
という記載がありました。(//2)
この部分が、画面遷移時に受け渡すパラメータとなります。
例えば、パラメータとして1
を渡すとしたら、画面遷移のためのコードは以下のようになります。
context.go('/list/detail/1');
:id
と記載していた場所の文字列がパラメータとして渡されるわけです。
また、もっと明確にパラメータを渡す方法として、以下の遷移の仕方もあります。
//こちらでも良い
context.goNamed('detail',params: {'id' : '1'});
渡されたパラメータの利用##
上記で渡されたパラメータは、pageBuilder
のstate
を使用して、
state.params['id']
として取り出すことが可能です。
サンプルコードの使用例を記載します。
//画面の情報を定義する
final _router = GoRouter(
//省略
GoRoute(
//省略
routes: [
GoRoute(
name: 'detail',
path: 'detail/:id',
pageBuilder: (context, state) {
//3
Book? book = Provider.of<BookList>(context)
.bookFromId(state.params['id']!);
//4
//エラーページへ遷移
if (book == null) {
return MaterialPage(
key: state.pageKey,
child: Scaffold(
appBar: AppBar(
title: Text('ページが存在しません'),
),
body: Container()),
);
}
return MaterialPage(
key: state.pageKey,
//5
child: BookDetailPage(
book: book,
),
); }),
]),
],
//省略
);
//3
BookList
をProvider
の状態として管理しているため、少し複雑な書き方となっていますが、やっていることはシンプルです。
渡されたパラメータをstate.params['id']
を使って取得し、
BookList
というオブジェクトからbookFromId
メソッドを用いid
の値を使ってBook
オブジェクトを取り出しています。
bookFromId
メソッドは以下のような実装となります。
class BookList {
final List<Book> books;
BookList._({required this.books});
//省略
Book? bookFromId(String id) {
try {
return books.where((element) => element.id.toString() == id).first;
} catch (e) {
return null;
}
}
}
//4
ディープリンクにて遷移した場合、id
の値に対応したBook
がない場合があります。
これに対応するためnull
かどうかで条件分岐しています。
//5
取得したBook
オブジェクトを詳細ページに手渡しています
上記のようにすることで、go_routerを使って値の受け渡しを実装できました。
サンプルコードをchromeにて実行し、
- 本のリスト画面で本のタイトルをタップすることで、対応した詳細画面に移動すること
- 戻るボタンが存在し、押すと前の画面に戻ること
- 本の
id
がアドレスバーのパスに表示されること - パスを直接打ち込むことで、対応する詳細ページに移動すること
を確認してみてください。
Providerの使い方は今回の内容から外れるため、省略します。
サンプルコードにて実装内容を確認してください。
まとめ##
今回はgo_routerを使った画面遷移にて値を受け渡す方法について解説しました。
ディープリンクを実装する関係から、遷移時に受け渡せるのがString
だけ、
というのがgo_routerの難しいポイントだと思いました。
ただ、ここさえ乗り越えればかなり簡単に実装可能です。
ぜひ使ってみてください。
参考##