はじめに
普段何気なく使っている「GlobalKey」が実際何のために使用されており、
どういう挙動を実現しているのか興味が湧いたので深掘りして調べてみました。
もしどなたかの参考になれば嬉しいと思い、記事にしてみました。
今回の記事では、GlobalKeyを例に、
BottomTabBarの実装を例にKey(GlobalKey)がどのような役割を担っているのか学んでいければと思います。
keyとは何かドキュメントを読んでみる。
Keys must be unique amongst the Elements with the same parent.
Subclasses of Key should either subclass LocalKey or GlobalKey.
keyは同じ親を共有するelements内でユニークな値である必要があり、
LocalKeyとGlobalKeyは必ずKeyクラスを継承しなければいけないみたいですね。
Key自体の役割は、ツリー状に構成されているElementの中から、
その要素の一部である、Element要素にユニークなWidgetを識別するために使用します。
ListWidgetのスクロール位置の保存など、
BottomTabBarで特定のWidgetに遷移する際に使用したりするのが一般的なのではないでしょうか?
LocalKeyとGlobalKeyの違い
LocalKeyは、同じ親を共通とする子のWidget間で子Widget同士を識別するために必要なKeyです。
GlobalKeyは、文字通りLocalKeyとは異なり、
全く別のWidgetTreeのWidgetを識別したいときに使用します。
例えば、以下のColumnの中のText同士を識別させたい場合は、
LocalKeyを使用します。
Rowの中のTextWidgetから全く別のColumnの子WIdgetを識別したい場合に採用するのは、
GlobalKeyといった具合になります。
GlobalKeyでの使用例
今回はGlobalKeyで実際にBottomTabBarで使用されている例を見つつ、
理解を深めていきましょう。
今回は、以下画像のような一般的なBottomTabBarを考えます。
enum型でタブの数を管理していて、
enumの数だけ、_navigatorKeysでGlobalKeyを生成しています。
enum TabItem {
a,
b,
c,
d,
e,
}
final Map<TabItem, GlobalKey<NavigatorState>> _navigatorKeys = {
TabItem.a: GlobalKey<NavigatorState>(),
TabItem.b: GlobalKey<NavigatorState>(),
TabItem.c: GlobalKey<NavigatorState>(),
TabItem.d: GlobalKey<NavigatorState>(),
TabItem.e: GlobalKey<NavigatorState>(),
};
上記で作成した_navigatorKeysをTabNavigatorのnavigationKeyのパラメータで指定しています。
Widget _buildTabItem(
TabItem tabItem,
String root,
) {
return Offstage(
offstage: ref.watch(currentTab) != tabItem,
child: TabNavigator(
navigationKey: _navigatorKeys[tabItem]!,
tabItem: tabItem,
routerName: root,
),
);
}
※全てのコードを記載するとかなりコード量が大きくなってしまうのと、
今回はあくまでKeyの理解が趣旨なので省略させて頂きます。
気になる方は以下のコードも参照してみてください。
MyAppクラスはこちら
class MyApp extends ConsumerStatefulWidget {
const MyApp({super.key});
@override
HomeViewState createState() => HomeViewState();
}
class HomeViewState extends ConsumerState<MyApp> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Stack(
children: <Widget>[
_buildTabItem(
TabItem.a,
'/a',
),
_buildTabItem(
TabItem.b,
'/b',
),
_buildTabItem(
TabItem.c,
'/c',
),
_buildTabItem(
TabItem.d,
'/d',
),
_buildTabItem(
TabItem.e,
'/e',
),
],
),
bottomNavigationBar: BottomNavigation(
currentTab: ref.watch(currentTab),
onSelect: onSelect,
),
)
);
}
Widget _buildTabItem(
TabItem tabItem,
String root,
) {
return Offstage(
offstage: ref.watch(currentTab) != tabItem,
child: TabNavigator(
navigationKey: _navigatorKeys[tabItem]!,
tabItem: tabItem,
routerName: root,
),
);
}
}
TabNavigatorクラスはこちら
class TabNavigator extends StatelessWidget {
const TabNavigator({
super.key,
required this.tabItem,
required this.routerName,
required this.navigationKey,
});
final TabItem tabItem;
final String routerName;
final GlobalKey<NavigatorState> navigationKey;
Map<String, Widget Function(BuildContext)> _routerBuilder(BuildContext context) => {
'/a': (context) => const TopView(),
'/b': (context) => const CalendarViews(),
'/c': (context) => const UserReportMainView(),
'/d': (context) => const AlarmView(),
'/e': (context) => const MyPageView()
};
@override
Widget build(BuildContext context) {
final routerBuilder = _routerBuilder(context);
return Navigator(
key: navigationKey,
initialRoute: '/',
onGenerateRoute: (settings) {
return MaterialPageRoute<Widget>(
builder: (context) {
return routerBuilder[routerName]!(context);
},
);
},
);
}
}
なぜGlobalKeyが必要なのか。
Flutter の BottomNavigationBarは、
各画面上から新しい画面に遷移していくと、
遷移前の画面から遷移後の画面をスタックしていくのですが、
その際にBottomNavigationBarが画面から消えてしまうという挙動になっています。
(気になる方は以下の記事を参照してみてください。)
ですが、各画面のGlobalKeyを、参照させることにより、
その画面からStackされた画面もGlobalKeyからElementから管理することが可能になります。
このように、他のwidgetを操作している際に、
他のWidgetTreeの各要素のデータを参照する必要がある場合などにKeyが必要になるんですね。
参考資料
最後に
最後までご覧いただきありがとうございました。
何かご指摘等あればコメントいただけると幸いです。
ありがとうございました!