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 におけるパラメータが null の値を持つことができない理由
新しいページからデータを前のページに返す
Futureクラス