Flutter レイアウトの基本 - ページナビゲーションと値の受け渡し#
ナビゲーションといえば、最も一般的なのは iOS のナビゲーションコントローラーの push と pop の効果で、Flutter にも同様の効果があります。それを使用するのがNavigatorコンポーネントです。
次に、Flutter におけるナビゲーション効果Navigator.pushとNavigator.popの使用方法を見てみましょう。
簡単な使用法#
コードは以下の通りです:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'ナビゲーションデモ1',
home: new FirstScreen(),
);
}
}
class FirstScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text('ナビゲーションページ'),
),
body: Center(
child: ElevatedButton(
child: Text('商品詳細ページを見る'),
onPressed: () {
Navigator.push(context,
new MaterialPageRoute(builder: (context) => new SecondScreen()));
},
)),
);
}
}
class SecondScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('SecondScreen'),
),
body: Center(
child: OutlinedButton(
child: Text('戻る'),
onPressed: () {
Navigator.pop(context);
},
),
),
);
}
}
Navigator.pushを使用する際は、遷移先の画面をMaterialPageRouteで囲む必要があります。
コード内のSecondScreenにはボタンが追加されており、クリック時の実装方法はNavigator.popで、戻るために使用されます。しかし通常は、Navigator.popを特別に実装する必要はありません。なぜなら、iOS ではAppBarを使用すると、自動的に左上に戻るボタンが追加されるからです。また、Android ではシステムの戻るボタンを使用しても直接戻ることができます。
名前付きナビゲーションの使用#
ルーティングのジャンプと同様に、クラス名ではなく名前を使用してジャンプします。
以下のコードでは、3 つの画面を定義しています。MyFirstPageがホームページで、MySecondPageとMyThirdPageが 2 つの異なる画面です。ホームページには 2 つのボタンがあり、それぞれMySecondPageとMyThirdPageにジャンプします。同時に、ジャンプ時にパラメータを渡し、対応するページに表示します。
コードは以下の通りです:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyFirstPage(),
routes: <String, WidgetBuilder>{
'/a': (BuildContext context) => MySecondPage(title: 'Page A'),
'b': (BuildContext context) => MyThirdPage(title: 'Page B'),
},
);
}
}
class MyFirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('ホームページ'),
),
body: Center(
child: Column(
children: [
OutlinedButton(
onPressed: () {
Navigator.pushNamed(context, '/a');
},
child: new Text('PageA'),
),
OutlinedButton(
onPressed: () {
Navigator.pushNamed(context, 'b');
},
child: new Text('PageB'),
),
],
),
),
);
}
}
class MySecondPage extends StatelessWidget {
final String title;
const MySecondPage({Key? key, required this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('セカンドページ'),
),
body: Center(
child: OutlinedButton(
onPressed: () {
Navigator.pop(context);
},
child: new Text(this.title),
),
),
);
}
}
class MyThirdPage extends StatelessWidget {
final String? title;
const MyThirdPage({Key? key, this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('サードページ'),
),
body: Center(
child: new OutlinedButton(
onPressed: () {
Navigator.pop(context);
},
child: new Text(this.title ?? 'デフォルトページ名')),
),
);
}
}
ここで注意すべき点がいくつかあります:
まず、routesの使用で、型は<String, WidgetBuilder>です。前者は遷移先ページの名前で、後者は遷移先の対応ページです。また、遷移先ページの名前に/は必須ではなく、自由に定義できますが、ルーティングとして理解する場合は、統一したルールで命名することをお勧めします。
次に、遷移Navigatorの使用について、前の例では直接遷移する方法はNavigator.pushを使用しましたが、ここではNavigator.pushNamedを使用しています。
最後に、ページ間の値の受け渡しについて注意が必要です。
ページ間の値の受け渡し#
iOS 開発と同様に、ページ間の値の受け渡しは、上位ページから下位ページへの受け渡しと、下位ページから上位ページへのコールバック受け渡しに分かれます。
上位ページから下位ページへの値の受け渡し#
上記のコードは上位ページから下位ページへの値の受け渡しですが、MySecondPageとMyThirdPageの書き方は異なります。以下のように比較できます:

異なる点は 2 つあります:初期宣言が異なること、具体的な使用が異なることです。
MySecondPageで宣言されたtitle属性は、非 null の String で、required修飾子を使用しています(この点に注意が必要です。requiredであり、@requiredではありません。いくつかの文書は更新されていません)。使用する際はそのまま使用します。
MyThirdPageで宣言されたtitle属性は nullable の String で、required修飾子は使用されていませんが、使用する際には??を追加してデフォルト値を提供しています。
もし混同すると、使用時にThe parameter 'title' can't have a value of 'null' because of its type, but the implicit default value is 'null'. Try adding either an explicit non-'null' default value or the 'required' modifier.dart(missing_default_value_for_parameter)というエラーが発生する可能性があります。その理由は nullable と non-nullable の違いであり、修正方法は、1 つは nullable 型として宣言し、使用時に判断を加えること、もう 1 つはrequired修飾子を使用して non-nullable として宣言することです。
下位ページから上位ページへの値の受け渡し#
Navigator.Pushメソッドは戻り値を持つことができ、戻り値はFuture型です。Navigator.Popメソッドを呼び出す際に、第二のオプション引数に内容を渡すと、Navigator.Pushで戻ります。
コードは以下の通りです:
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyFirstPage(),
routes: <String, WidgetBuilder>{
'/a': (BuildContext context) => MySecondPage(title: 'Page A'),
'b': (BuildContext context) => MyThirdPage(title: 'Page B'),
},
);
}
}
class MyFirstPage extends StatelessWidget {
String callbackText = '';
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('ホームページ'),
),
body: Center(
child: Column(
children: [
OutlinedButton(
onPressed: () {
_pagePush(context, '/a');
},
child: new Text('PageA'),
),
OutlinedButton(
onPressed: () {
// Navigator.pushNamed(context, 'b');
_pagePush(context, 'b');
},
child: new Text('PageB'),
),
Text(callbackText),
],
),
),
);
}
}
_pagePush(BuildContext context, String target) async {
final result = await Navigator.pushNamed(context, target);
ScaffoldMessenger.of(context).showSnackBar(
new SnackBar(content: Text("$result")),
);
}
class MySecondPage extends StatelessWidget {
final String title;
const MySecondPage({Key? key, required this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('セカンドページ'),
),
body: Center(
child: OutlinedButton(
onPressed: () {
Navigator.pop(context, 'SecondPage コールバック');
},
child: new Text(this.title),
),
),
);
}
}
class MyThirdPage extends StatelessWidget {
final String? title;
const MyThirdPage({Key? key, this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('サードページ'),
),
body: Center(
child: new OutlinedButton(
onPressed: () {
Navigator.pop(context, 'ThirdPage コールバック');
},
child: new Text(this.title ?? 'デフォルトページ名')),
),
);
}
}
効果は以下の通りです:

上記のボタンのクリック方法に注意してください。_pagePushメソッドを個別に封装し、async修飾子を使用しています。理由は、Navigator.pushの戻り値がFuture型であり、awaitを使用する必要があるからです。awaitはasync修飾子を使用したメソッド内でのみ使用できます。ReactNative を経験したことがある方は、この書き方に馴染みがあるでしょう。
参考#
Navigator Dev Doc
Flutter 無料動画第 4 シーズン - ページナビゲーションとその他
Dart における「The parameter can't have a value of 'null' because of its type」
新しいページからデータを前のページに返す
Future クラス