はじめに
こんにちは😀 karamage です。
Flutterでアプリを作るのが大好きなアプリエンジニアです。
この投稿は、Flutter Advent Calendar2021 11日目 の記事です。
この記事では、親Widgetから子のメソッドを呼ぶ方法について、書いています。
この記事のサンプルコードについて、以下のリポジトリに上げておきます。
https://github.com/karamage/flutter_global_object_key_example
親から子のメソッドを呼ぶには?
Flutterで、親Widgetから子Widgetのメソッド(関数)を呼び出したり、子の状態を参照したい 場合ってありませんか?
ぼくは、あります。
結論から言うと、親から子のメソッドを呼ぶには、GlobalObjectKey を使います。
GlobalObjectKey
は、親Widget側で使われる機能で、Keyを設定した子Widget(Stateful)の状態変化やプロパティやメソッドを親から参照できるようになります。
このとき、どのようにして呼び出したらよいのか、使い方をよく忘れてしまうので、自分の備忘録のためにも、ここに書き記しておこうと思います。
この記事が、誰かのお役に立てれば幸いです。
GlobalObjectKeyを使って、親から子のメソッドを呼んでみる
GlobalObjectKeyとは何か、という小難しい説明はいったん置いといて、実際の使い方について、例を挙げて説明します。
以下のような、よくあるカウンターアプリを作るとします。
子Widget(Stateful)の定義
内部にStatefulなcounterの状態を持ち、表示を行うWidgetを、Counter
クラスとして定義します。
CounterクラスはStateful Widgetで、CounterState
を持ちます。
CounterState
には、counterを1増やすincrementCounter
というメソッドを持っています。
import 'package:flutter/material.dart';
class Counter extends StatefulWidget {
Counter({Key? key}) : super(key: key);
@override
CounterState createState() => CounterState();
}
class CounterState extends State<Counter> {
int _counter = 0;
void incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
);
}
}
親Widget(Stateless)の定義
続いて、CounterをWrapする親Widgetとして、以下のようなCounterPage
クラスを定義します。
import 'package:flutter/material.dart';
import 'package:flutter_global_object_key_example/counter.dart';
class CounterPage extends StatelessWidget {
final String title;
CounterPage({Key? key, required this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Counter(),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
+ // TODO ここでCounterのincrementCounter()を呼び出したい
},
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
さて、ここで問題になるのが、FABを押した際にCounter
のincrementCounter()
を呼び出したいのですが、どのようにすればよいのでしょうか?
答えは、以下のようにGlobalObjectKeyを使って呼び出すことができます。
@override
Widget build(BuildContext context) {
+ final counterKey = GlobalObjectKey<CounterState>(context);
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
+ child: Counter(key: counterKey),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
+ // ここでCounterのincrementCounter()を呼び出す
+ counterKey.currentState?.incrementCounter();
},
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
counterKeyを定義して、Counterのコンストラクタに渡して登録しています。
counterKey.currentState
を参照することによって、現在のCounterState
の状態にアクセスすることができます。
GlobalObjectKeyは、VueやReactでいうと、ref参照みたいなものだと理解しています。
おまけ:親Widgetから子Widgetのstateを直接参照して書き換えたい
まれにですが、子のメソッド呼び出しではなくて、子のstateを直接参照して書き換えたいこともあるかと思います。
その際は、以下のようにして、stateを参照します。
class CounterPage extends StatelessWidget {
final String title;
CounterPage({Key? key, required this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
final counterKey = GlobalObjectKey<CounterState>(context);
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Counter(key: counterKey),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
+ final count = counterKey.currentState?.counter ?? 0;
+ counterKey.currentState?.setCounter(count + 1);
},
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
Counter
は以下のように書き換えてください。
状態を外からアクセス可能にするため、privateからpublicにするのに_counter
の_
アンスコを外しています。
class CounterState extends State<Counter> {
- int _counter = 0;
+ int counter = 0;
+ void setCounter(int value) {
+ setState(() {
+ counter = value;
+ });
+ }
@override
Widget build(BuildContext context) {
return Text(
'$counter',
style: Theme.of(context).textTheme.headline4,
);
}
}
おまけ2:子から親のメソッドを呼びたい
逆に、子から親のメソッドを呼びたい場合もあるかと思います。
このときは、親から子にコールバック関数を渡してください。
子は必要なときに、親から渡されたコールバック関数を呼んでください。
class Counter extends StatefulWidget {
+ Function callback;
+ Counter({Key? key, required this.callback}) : super(key: key);
@override
CounterState createState() => CounterState();
}
まとめ
親Widgetから子のメソッドを呼ぶには、GlobalObjectKeyを使いましょう!
GlobalObjectKeyとBuildContextを組み合わせることで、StatelessWidget内でGlobalKeyを使用することができます。これにより、グローバル変数の使用を避けることができ、子Widgetの再利用が可能になり、メソッド呼び出しや状態参照ができます。
ところで、そもそも、**GlobalObjectKey(GlobalKey)って、いったいなんぞや!?**という疑問をお持ちの方、いると思います。
GlobalObjectKeyや、GlobalKeyとの違いや、他のKeyについて、詳しく知りたい場合、以下の記事やツイートが参考になります。
(実は、ぼくも理解が不足している部分もあるので、勉強し直します…うごごご)
Flutter Widget Keyの種類と使い方について
最後まで読んでいただいてありがとうございます!
それでは、みなさま、よい年末を!
参考
Remiさんのツイート
Random #Flutter tip:
— Remi Rousselet (@remi_rousselet) January 16, 2020
You can use GlobalKeys inside StatelessWidget by combining GlobalObjectKey and BuildContext (and potentially tuples)
This avoids using global variables, which therefore makes the widget reusable. 🙂 pic.twitter.com/uWs9x8fuIS
@mono0926 さんのツイート
なぜGlobalKey()ではなくGlobalObjectKey(context)にする必要があるかを示すサンプルを書いた( ´・‿・`)https://t.co/4knKlGo6HM https://t.co/MVrkA9gvoU pic.twitter.com/eTMRiC6k9r
— mono (@_mono) January 16, 2020