34
16

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 3 years have passed since last update.

FlutterAdvent Calendar 2021

Day 11

【Flutter】親Widgetから子のメソッドを呼ぶには、GlobalObjectKeyを使う

Last updated at Posted at 2021-12-11

はじめに

こんにちは😀 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とは何か、という小難しい説明はいったん置いといて、実際の使い方について、例を挙げて説明します。

以下のような、よくあるカウンターアプリを作るとします。

Screen Shot 2021-12-05 at 15.23.53.png

子Widget(Stateful)の定義

内部にStatefulなcounterの状態を持ち、表示を行うWidgetを、Counterクラスとして定義します。
CounterクラスはStateful Widgetで、CounterStateを持ちます。
CounterStateには、counterを1増やすincrementCounterというメソッドを持っています。

counter.dart
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クラスを定義します。

counter_page.dart
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を押した際にCounterincrementCounter()を呼び出したいのですが、どのようにすればよいのでしょうか?

答えは、以下のようにGlobalObjectKeyを使って呼び出すことができます。

counter_page.dart
  @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を参照します。

counter_page.dart
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_アンスコを外しています。

counter.dart
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さんのツイート

@mono0926 さんのツイート

34
16
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
34
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?