8
9

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.

FlutterのBuildContextとofメソッドがようやく理解できた

Last updated at Posted at 2021-04-13

:book: Flutterの記事を整理し本にしました :book:

  • 本稿の記事を含む様々な記事を体系的に整理し本にまとめました
  • 今後はこちらを最新化するため、最新情報はこちらをご確認くださ
  • 10万文字を超える超大作になっています(笑)

はじめに

  • 最近、Flutterの3つのツリーをまとめたので、BuildContextについても整理してみました。

まとめ

BuildContext

Flutterで一番良く使うメソッドと言っても過言ではない、buildメソッドは引数にBuildContextを持っています。

Widget build(BuildContext context){ ・・・ }はおなじみだと思います。
また、Theme.of(context)のように、何となくおまじないのようにcontextは登場したりもします。

まず、contextとは文脈という意味です。

近くにやまがあるので登る明日が試験なのでやまをはるといった場合、おなじやまという文字ですが、持っている意味は変わってきます。

contextはその対象が置かれた、前後関係や環境や条件などの文脈のことを指しています。
では、Flutternにおける文脈とは何を指すでしょうか?

答えから言ってしまうと、Widgetツリーの親子関係になります。
contextとは親のElementへの参照となっています。

Elementについては、よくわからないという方は、3つのツリーの記事をご参照ください

まずは、イメージ図です。
picx.png

ビルドメソッドを持つ、WidgetのElementを参照しているというのは、ElementへのAPIが提供されているという意味になります。
実際には、後述するofメソッドなどで利用されています。

その前に、本当にcontextが親のElementを保有しているのか、ソースコードとデバッグで確認していきます。

サンプルにはHelloWorldを使い、「+」ボタンが押された時のイベントにブレイクポイントを張ってみます。

main.dart
import 'package:flutter/material.dart';
import 'package:hello_world/BusinessLogic.dart';

void main() {
  runApp(MyApp());
}
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}
class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, this.title}) : super(key: key);
  final String? title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  void _incrementCounter() {
    setState(() {
      _counter++; // ここにブレイクポイントをはる
    });
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title!),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

ブレイクポイントで停止させた時のイメージ図
pic1.png

デバッグで変数を表示させていますが、contextStateful Element(MyHomePage)となっていることが確認できるかと思います。

またchildにはScaffoldがあり、ツリー構造に従ってchildが連なっています。

つまり、contextがあることで、buildがあるWidgetの根本からWidgetをたどることができるというわけです。

ofメソッド

しかし、逆に根本から祖先をさかのぼりたいときもあります。
そのようなニーズに対応するのが、ScaffoldやThemeのofメソッドになります。
このメソッドは、contextから祖先に向かって(child方向ではなく)最も近い目的のWidgetを探すことができます。
これを応用し、全体の設定の取得や変更を行うことができます。

Scaffold.ofの場合はScaffoldState。Theme.ofの場合はThemeDataが返ります。

main.dart
/* 中略 */
void _incrementCounter() {
+    print(Theme.of(context));
+    print(Theme.of(context).primaryColor);
+    print(Theme.of(context).brightness);
    setState(() {
      _counter++;
    });
  }
/* 中略 */
result.sh
I/flutter (13681): ThemeData#a7f8e
I/flutter (13681): MaterialColor(primary value: Color(0xff2196f3))
I/flutter (13681): Brightness.light

Scaffold.ofの注意点

Scaffold.ofには1つ注意点があります。

Scaffoldの定義と、Scaffold.ofを使う場所が同じbuild関数の中にある場合は、うまく見つけることができません。

それは、ofメソッドがbuildを呼び出すWidgetから祖先をたどっていくためです。
Scaffoldは子孫のため探し出すことができません。
pic2.png

そこで、Builderを使って探すという手段が一般的に行われます。
contextの探索開始点をツリーの下にする目的です。

pic3.png

snackbar.dart
@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text('Demo')
    ),
    body: Builder(
      // Create an inner BuildContext so that the onPressed methods
      // can refer to the Scaffold with Scaffold.of().
      builder: (BuildContext context) {
        return Center(
          child: RaisedButton(
            child: Text('SHOW A SNACKBAR'),
            onPressed: () {
              Scaffold.of(context).showSnackBar(SnackBar(
                content: Text('Hello!'),
              ));
            },
          ),
        );
      },
    ),
  );
}
8
9
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
8
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?