【Flutter】コールバック関数を別のWidgetで呼び出す
サンプルを用いて、タイトルの通り別の Widget でコールバック関数を呼び出しています。
おまけでコールバック関数について少し触れています。
コールバックを寿司屋で表現
SushiShop
というStatefulWidget
内で個別のSushiWidget
をいくつも表示させたいとします。
また寿司は作ったり食べたりできるので、makeSushi
とeatSushi
という関数も用意します。
makeSushi
は新しく寿司を作り一覧に追加し、eatSushi
は選んだ(タップした)寿司を消す挙動にします。
SushiWidget
を一覧化するのはかんたんで、SushiShop
内のビルドの通りWidget
のchildren
としてリストを渡してあげればよいです。リスト内のオブジェクトの型はSushiクラス
として、インスタンスを格納します。
Sushiクラス
のプロパティには、ネタとしてneta
(サンプルコードは"鮪"確定ですが…)と、あとはどの寿司を食べたか識別するための id を持っています。
SushiShop
内で寿司をつくるmakeSushi
関数を作成し、id
とneta
を初期化します。あとは寿司を食べるeatSushi
関数を…とここで問題がでてきます。
void eatSushi(int netaId) {sushiList.removeAt(netaId);}
で一見よさそうにみえますが、eatSushi
を発動させるのは寿司がタップされたとき、すなわちSushiShop
クラスとは違うSushiクラス
のウィジェット(TextButton
)がタップされたときになります。
そのためSushiWidget
内でonPressed: _eatSushi()
みたいなことはできないんです。
これを解決するために、Sushiクラス
ではFunction(int) sushiCallback(引数にint型を必要とする関数)
をプロバティとして持たせています。
寿司自身がタップされたときにはonPressed: () => sushiCallback(id)
としてid(=識別するためのもの)
を引数に渡してあげることで、SushiShop
でどの寿司がタップされたのかを判断することができるのです。
class SushiShop extends StatefulWidget {
...
}
class SushiShopState extends State<SushiShop> {
List<Sushi> sushiList = [];
int _sushiCounter = 0;
void _makeSushi() {
Sushi sushi = Sushi(
id: _sushiCounter,
neta: "鮪",
sushiCallback: (sushiId) => _eatSushi(sushiId),
);
_sushiCounter++;
setState(() {
sushiList.add(sushi);
});
}
void _eatSushi(int netaId) {
_sushiCounter--;
setState(() {
// 指定した添字のオブジェクトを削除
sushiList.removeAt(netaId);
});
}
Widget build(BuildContext context) {
return Column(
children: [
IconButton(
...,
onPressed: () => _makeSushi(),
),
Column(
...
children: sushiList,
),
],
),
}
class Sushi extends StatelessWidget {
Sushi({
required this.id,
required this.neta,
required this.sushiCallback,
Key? key
}) : super(key: key);
int id;
String neta;
Function(int) sushiCallback;
Widget build(BuildContext context) {
return Center(
child: TextButton(
onPressed: () => sushiCallback(id),
child: Text(
"$neta",
),
),
);
}
}
寿司を並べるくらいならText
とかを羅列すれば良かったのですが、任意にタップした寿司を消すという動的な仕様を実装しようと思ったらここまでややこしくなりました。
※sushiCounter
のインクリメントなどはもうちょっといじらないとうまく動作しません。
おまけ
寿司クラスも、JS でかんたんにまとめると少しわかりやすいように思えます。
// 寿司関数を定義(高階関数)
function sushi(id, neta, func) {
func(id)
}
// コールバック関数
const eatSushi = function(id) {
... // [id]の寿司をたべる処理
}
sushi(1, "サーモン", eatSushi);
コールバック関数は直接引数にできるので…
function sushi(id, neta, func) {
func(id)
}
sushi(1, "サーモン", function(id){
...
});
これにアロー関数を用いると…
function sushi(id, neta, func) {
func(id)
}
sushi(1, "サーモン", (id) => {...});
サンプルコードのこれも
// 高階関数
Sushi({
required this.id,
required this.neta,
required this.sushiCallback,
Key? key
}) : super(key: key);
// コールバック関数
void _eatSushi(int netaId) {
_sushiCounter--;
setState(() {
sushiList.removeAt(netaId);
});
}
Sushi sushi = Sushi(
id: _sushiCounter,
neta: "鮪",
sushiCallback: (sushiId) => _eatSushi(sushiId),
);
つまりはこういうこと(一部省略)
Sushi(id, neta, sushiCallback)
void _eatSushi(int netaId) {
sushiList.removeAt(netaId);
}
Sushi sushi = Sushi(_sushiCounter, "鮪", (sushiId) => _eatSushi(sushiId));
Widget
で見かけるコロンや名前付き引数の波括弧がいい感じにややこしくしてますね…。
書き方や考え方にいろいろと甘い部分はあると思います。
Flutter
は楽しいのでもっと勉強してパワーアップしたいです 🦋
Discussion