7
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[flutter] WidgetKeyについて深掘りしてみた

Posted at

はじめに

普段何気なく使っている「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を使用します。

スクリーンショット 2023-10-29 11.53.58 (1).png

Rowの中のTextWidgetから全く別のColumnの子WIdgetを識別したい場合に採用するのは、
GlobalKeyといった具合になります。

スクリーンショット 2023-10-29 11.59.41.png

GlobalKeyでの使用例

今回はGlobalKeyで実際にBottomTabBarで使用されている例を見つつ、
理解を深めていきましょう。

今回は、以下画像のような一般的なBottomTabBarを考えます。

スクリーンショット 2023-10-31 22.01.04.png

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が必要になるんですね。

参考資料

最後に

最後までご覧いただきありがとうございました。
何かご指摘等あればコメントいただけると幸いです。

ありがとうございました!

7
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?