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

---
# :book: Flutterの記事を整理し本にしました :book:
https://zenn.dev/kazutxt/books/flutter_practice_introduction

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

---

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

## まとめ
* [Flutter系の記事のまとめ](https://qiita.com/kazutxt/items/524acb1c134015682ccf)

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

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

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

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

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

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

Elementについては、よくわからないという方は、[3つのツリーの記事](https://zenn.dev/kazutxt/books/flutter_practice_introduction/viewer/development_expert1)をご参照ください

まずは、イメージ図です。
![picx.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/7d6f1f72-ce49-bd79-55fa-72f706e23e3d.png)


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

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

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

```dart: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/9ff8efe6-0b29-37b6-7e48-34cf8daeeb8b.png)



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

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

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

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

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

``` dart:main.dart
/* 中略 */
void _incrementCounter() {
+    print(Theme.of(context));
+    print(Theme.of(context).primaryColor);
+    print(Theme.of(context).brightness);
    setState(() {
      _counter++;
    });
  }
/* 中略 */
```

```sh: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/11b68842-8dd7-e505-11a4-575a3c49ca79.png)

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

![pic3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/a008901e-0dce-442a-2175-e70d1b297619.png)

```dart: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!'),
              ));
            },
          ),
        );
      },
    ),
  );
}
```