1.前書き
何気なくcontextを使ってしまうと引き起こしてしまう バグや、挙動の違いなどについて紹介しています。 contextについてあまり分からないと言った方はFlutterのcontextとは何者か。
の記事で解説してます。
2.BottomNavigationを使用した際のモーダル
ここにサンプルアプリがあります。内容は至って単純で
①BottomNavigation機能
②モーダルの表示
のみです。
画面内の「push」ボタンを押すと下からモーダルが表示されます。
モーダルを表示させる際に 「context」を引数としてonPressed関数に渡して
showModalBottomSheetに渡しています。
この際に、useRootNavigatorをfalseにするかtrueにするかで挙動が変わります。
useRootNavigator | true | false |
---|---|---|
挙動 | ||
以下はソースコードになります。
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
void onPressed(BuildContext context) async {
await showModalBottomSheet(
useRootNavigator: false, //←useRootNavigatorがtrueかfalseで挙動が変わる
context: context,
builder: (context) => Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
title: const Text('POP'),
onTap: () => Navigator.of(context).pop('delete'),
),
const Divider(),
ListTile(
title: const Text('POP'),
onTap: () => Navigator.of(context).pop('delete'),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: CupertinoTabScaffold(
tabBar: CupertinoTabBar(items: const [
BottomNavigationBarItem(
icon: Icon(Icons.music_note),
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
),
]),
tabBuilder: (context, index) {
switch (index) {
case 0:
return CupertinoTabView(
builder: (context) {
return CupertinoPageScaffold(
child: Center(
child: ElevatedButton(
child: const Text('push'),
onPressed: () => onPressed(context),
),
),
);
},
);
case 1:
return CupertinoTabView(
builder: (context) {
return const CupertinoPageScaffold(
child: Center(child: Text('tab 2')),
);
},
);
}
return Container();
}));
}
}
userootNavigatorがtrueとfalseの場合では
画面遷移を管理するNavigatorStateの取得先が異なります。
正確に言うと、
userootNavigatorがtrueの場合は
trueの場合はfindRootAncestorStateOfTypeメソッド
falseの場合はfindAncestorStateOfTypeメソッドが呼ばれる。
それぞれのメソッドの違いが親のたどり方になります。
分かりやすく図解で説明します。
(こちらの記事ではコードベースで詳細を追っています。)
useRootNavigator | navigatorState取得の動き |
---|---|
true | |
false |
Flutterではデフォルトで画面遷移を管理するNavigatorStateが用意されており
BottomNavigationなどの機能を追加した際は
NavigatorStateがネストされている状態となります。
デフォルトで画面遷移を管理するNavigatorStateに対して操作したい場合は
useRootNavigatorをtrueにすることで
画面全体に対してモーダルなどを被せることが出来ます。
逆にfalseの場合は、BottomNavigationが管理するNavigationStateを操作しているので
BottomNavigationで管理している内側の画面にのみモーダルが表示されます。
ここは、理解していないと意図しないバグを生み出す可能性があるのでしっかり理解しておきましょう!
3.contextが変わってしまう問題
これは間違った再帰処理の方法によりcontextが変わってしまう問題。ユーザ登録処理でユーザはエラーの失敗により3回までリトライ処理を行うことが出来るとする。
まず、
1.OnTappedResisterButtonにcontextを渡す。
2,3.ユーザ登録処理に失敗
4.ダイアログの【OK】ボタンタップ時などに実行する関数にOnTappedResisterButtonを渡す。
この際に渡すcontextは【context_1】
5.ユーザはダイアログの【OK】ボタンをタップして再度ユーザ登録処理を行う
6.その際に、一回目の登録処理では行われなかった処理がifなどで実行され
stateが更新されpresentation側でリビルドが走る
この際に、context_1の生成元であるwidgetにリビルドがかかり、
widgetが持つcontextがcontext_2になる
7,8.ユーザ登録処理に失敗
9.エラーダイアログ表示
最後に表示されるダイアログがPOPする際に使用するcontextが【context_1】の場合、presentationではそんなcontextのwidgetはどこにもな!と言われのPOP時にエラーが発生してしまう。