😗

Flutter の BuildContext って何?

2021/06/16に公開
1

こんにちはこんばんわ、すぎっと ٩( ᐛ )و です
今日のテーマは BuildContext です。
BuildContext を知るということは、 Inside Flutter (Flutter の内部の動き) に手を出すということです。

内部でどんなことが起こっているのか、ということをあまり気にせずとも動くアプリを作ることができます。これがフレームワークの凄さです。Flutter 開発陣営がいかにその叡智を結集しているのかがよく分かります。

ですが、より良いパフォーマンスを求めたり、質の高い実装をするため には少し踏み込んだ話についても知っておいて損はないと思います。

今回はその少し踏み込んだ話を事細かに説明するのではなく、さらっとした読み物として流し読みして、雰囲気を感じてもらえるくらいの内容にしようと思っています。そのため、少し厳密さを欠いている部分がありますが、たぶん嘘はついていないのでご了承ください。

よく見かける BuildContext、よく知らない BuildContext

毎日のように目にするのに、実は相手のことをよく知らないってこと、良くありますよね。
一度気になってしまうと、ついつい目で追ってしまって・・・
ああ、夜も眠れない。
そんな相手が BuildContext です。

Flutter でアプリを書いていると幾度となく書くことになるのが BuildContext です。

  • StatelessWidget の build
  • State の build
  • ListView の builder
Widget build(BuildContext context) {
  return (
    // hoge
  );
}

とか

ListView.builder(
  itemBuilder: (BuildContext context, int index) {
    return Container(
      height: 80,
      color: colorList[index % colorList.length],
    );
  },
)

こういうやつですね。

はたまた、 BuildContext の context を指定する of(context) なんてのもありますね。

- Theme.of(context)
- MediaQuery.of(context)
- Navigator.of(context)

いろいろな場所で目にすることと思います。

この BuildContext は何をしているのかについてペラっと理解していきましょう。

本気で理解したい人のために

ふわっと説明する前に、ふわっとでは満足できない人のためにガチコースのご案内だけしておきます。少なくとも私の理解レベル程度にはいけると思いますっ!!

https://flutter.dev/docs/resources/inside-flutter#separation-of-the-element-and-renderobject-trees

  • これ 🖕 を読みながら
  • Flutter の初期アプリをデバッグする
  • 深いところまでデバッグする
  • Flutter の実装をよく読む
  • DevTools で実際の動きを俯瞰する

以上です。

なにはともあれ、デバッグだ!!

デバッグしてみて、どんな値が入っているのかをみながら雰囲気を掴んでいきましょう。デバッグはさくっと VS Code でやってしまいます。使用するアプリは flutter create myapp でできる初期状態のカウンターアプリです。

カウンターアプリはざっくりこんな構成になっていますね。

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) { // <== 1️⃣ ここ〜
    return MaterialApp(
      // いろいろ〜
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;
  
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  // いろいろ〜
  
  Widget build(BuildContext context) { // <== 2️⃣ ここ〜
    return Scaffold(
      // いろいろ〜
    );
  }
}

BuildContext が 2箇所 (1️⃣, 2️⃣) ありますので、それぞれをデバッグしてみます。

まず 1️⃣ つ目の MyApp に対する build メソッドです。

なんと、 MyApp の build(BuildContext context) の context は StatelessElement でした。

Element ??

これが Inside Flutter の第一歩、 Element です。

他も気になりますが、 2️⃣ つ目の _MyHomePageState の build にいきましょう。

_MyHomePageState の build(BuildContext context) の context は StatefulElement でした。

どうやら、 BuildContext とは、 StatelessElement であり、StatefulElement でもある ようです。これはあれですね、オブジェクト指向っていうやつですね。

そしてここを深掘りし始めると ペラッとした理解をするための記事 ではなくなってしまうので、またの機会にしますが、 StatelessElement も StatefulElement もまとめて Element です。

ざっくりとこんな関係にあります。

一部省略です

ここまでで、 build(BuildContext context) の context には Element が入っている ということがわかりました。

この Element の定義ですが、実はこうなっています。

abstract class Element extends DiagnosticableTree implements BuildContext {
  // ~~
}

出ましたね、BuildContext。
すなわち、 Element は BuildContext を実装する抽象クラスです。なお、BuildContext も abstruct class です。

Element は BuildContext を実装していて、 StatefulElement や StatelessElement は Element を継承しています。そのため、 build(BuildContext context) の context は StatelessElement を受け取ったり、 StatefulElement を受け取ったりできていたんですね。

BuildContext を知るということは、 Element を知るということ

BuildContext を実装するのは  Element です。すなわち、 BuildContext を知るには Element を知る必要がありますね。

ペラッと理解するために、前置きはすっ飛ばしていきましょう。

この 3 つをいい感じに取り扱っているのが Flutter です。

いい感じに取り扱ってくれているので、アプリ開発者は Widget だけ心配していればとりあえず動くものは作れるのです。

ではこれらの役割について簡単に触れていきます。

  1. Widget の役割: 平たく言えば 設計図 です。
  2. Element の役割: Widget という設計図から作った 画面要素 です。
  3. RenderObject の役割: Element から作られる実際に 画面の描画を行うための情報(オブジェクト) です。

そして、これら三者はそれぞれがツリー構造で管理されます。

  • Widget ツリー
  • Element ツリー
  • RenderObject ツリー

では、 Flutter はこれらのツリーをどのように作り、使い動いているのかを簡単に紹介します。RenderObject については BuildContext の理解にはいったん不要なので、おいておきます。そんなものがあるんだなぁと覚えておいてください。

では、ものすごくざっくりとした流れについて説明します。

Element - Widget のやりとり (ペラッと)

Flutter の内側では、以下のようなやりとりが行われています。


  1. なにはともあれ、ルートの Element をつくりました。
  2. 次の Element を作りましょう。設計図はこの Widget を使おう!
  3. Widget から Element を作ってルートに繋いで〜
  4. Element をつかって  Widget の build を実行しよう!
  5. 完成!!次だ次だ〜 (繰り返し)

BuildContext はこのやりとりにおける Step 4 です。
ものすごく雑な図を用意しました。雰囲気だけ味わってください。

1. なにはともあれ、ルートの Element をつくりました。

2. 次の Element を作りましょう。設計図はこの Widget を使おう!

3. Widget から Element を作ってルートに繋いで〜

4.  Element をつかって  Widget の build を実行しよう!

5. 完成!!次だ次だ〜 (繰り返し)

図はいらないかな〜ということで割愛。

BuildContext context は結局のところ何に使われているのか?

build メソッドに渡される context はその Widget に対して作られる Element であるということがわかりました。

Element は設計図から作った画面要素です。

すなわち、実際に画面に配置されることを想定した情報がたくさん含まれています。

たとえば、これから作る (build する) Widget は、 Element ツリーの どこに 入る予定だよ、ということが伝わります。これにより、 Widget の build メソッドが生成する Widget がどんなものであるべきか、というのが決まってきます。

このあたりの理解をさらに深めるには、 InheritedWidget に関する理解が必要です。そうすると、 Theme.of(context) や MediaQuery.of(context) で使われている of() がどういったことをしているのかなどが見えてくるようになります。

そこの追求はまた別の機会に。
本記事の結論を書いておきます。


BuildContext は Element であり、 これからつくる(build する) Widget が実際にどんな場所でどんな風に使われるのかを知るためのもの


そんな感じです。

BuildContext というものが おまじない からステップアップし、その役割についての概要がお伝えできていれば幸いです。

もう少し踏み込んだバージョンもいつか書いてみたいなぁと思います。

すぎっと ٩( ᐛ )و

GitHubで編集を提案

Discussion

JboyHashimotoJboyHashimoto

BuildContextがどんなものか分からず使っていましたが、これはElemantで、作るWidgetが例えばプラスボタンだったら、プラスボタンのWidgetを作る情報を渡して、画面にプラスボタンを作り出すことができる。
と思いました😅

  • 私の認識だと
    1. 情報を渡す
    2. 画面に描画する
    3. Widgetが画面に表示される