FlutterのInheritedWidgetの使い方のほんの一端がやっっとわかった

#(2021-01-05 追記)
この記事は筆者の理解が浅い段階で書かれたものです！
後にちゃんと書いた記事がありますので、その記事をご覧ください。

[InheritedWidgetの目的と使い方【Flutter】](https://qiita.com/agajo/items/375d5415cb79689a925c)

この記事は近日中に非公開にする予定です。


# はじめに


`InheritedWidget`の使い方の**ほんの一端**が**やっっと**わかったので説明しますが、

まだまだ全然理解不足の状態で書いている記事なので、**全然違うことを言っている可能性があります**!

それでも良いという方はお読みください。

「インスタンス」という言葉を多用するので、「クラス」と「インスタンス」の関係がよくわからない人はまずそこを調べてきてくださいね！

#例として使うアプリの概要

以下の用に`Widget`を3つ並べたものを作ります。

```dart:
Column(children: [
            WidgetA(),
            WidgetB(),
            WidgetC(),
          ]),
```

 - `WidgetA` 変数`x`を+1するボタン
 - `WidgetB` 他の動作と何も関係しない、単なる固定テキスト
 - `WidgetC` 変数`x`の値を表示するテキスト

##目標

`WidgetA`のボタンを押した時に

 - 内容に変更がある`WidgetC`は rebuild される
 - 内容に変更のない`WidgetA`と`WidgetB`は rebuild されない


という状態を目指します。

というのも、`Widget`の rebuild は計算コストがかかるので、内容に変更がないなら rebuild しない方がいいわけです。



#とりあえず書くとこうなる

僕が`InheritedWidget`を勉強する前にしていた書き方です。

各`build()`関数の中で`print();`を呼んで、どれが rebuild されたのかわかるようにしてあります。

`Widget`が rebuild されると`build()`関数が呼ばれるので、`print();`によりコンソールに出力が出て、それでわかるわけですね。

`WidgetA`のボタンが押された時に起こることを番号付きでコメントしてありますので追ってみてください。

##コード

```dart:main.dart
import 'package:flutter/material.dart';

void main() => runApp(MyStatefulWidget());

class MyStatefulWidget extends StatefulWidget {
  @override
  State createState() => _MyState();
}

class _MyState extends State<MyStatefulWidget> {
  int _x = 0;
  int get x => _x;
  void increment() {
    setState(() {// 2. setState()により、Stateがrebuildされる
      _x++;
    });
  }

  static _MyState of(BuildContext context) {
    return context.ancestorStateOfType(TypeMatcher<_MyState>());
  }

  @override
  Widget build(BuildContext context) {// 3. Stateのrebuildのため、build()が呼ばれる
    print("_MyState.build() is called!! x:$x");
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(),
        body: Column(children: [
          WidgetA(),// 4. WidgetA,B,Cの新しいインスタンスが作られる
          WidgetB(),
          WidgetC(),
        ]),
      ),
    );
  }
}

class WidgetA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("AAA WidgetA.build() is called!!!!");
    return RaisedButton(
      child: Text("WidgetA is the incrementer"),
      onPressed: () {
        _MyState.of(context).increment();// 1. ボタンが押されたら、_MyStateのincrement()を呼ぶ
      },
    );
  }
}

class WidgetB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("BBB WidgetB.build() is called!!!!");
    return Text("This is the constant WidgetB");
  }
}

class WidgetC extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final int x = _MyState.of(context).x;
    print("CCC WidgetC.build() is called!!!!");
    return Text("Widget C x: $x");
  }
}

```



##毎回全ての`Widget`が再構築(rebuild)されてしまう

上記のコードを実行して、`WidgetA`のボタンを押すと、画面の`WidgetC`の数値が更新されると同時に、
コンソールに下のような出力がでます。

```
flutter: _MyState.build() is called!! x:3
flutter: AAA WidgetA.build() is called!!!!
flutter: BBB WidgetB.build() is called!!!!
flutter: CCC WidgetC.build() is called!!!!
```

(xの値は押すたびに1ずつ増えていきます)

`WidgetA`のボタンを押す度に、`WidgetA` `WidgetB` `WidgetC`全てが rebuild されていますね。

よく考えると、ボタンを押す度に新しいインスタンスを生成しているのだから当然です。

(Dartでは`new`を省略できるので、コード内で`WidgetA()`と書いたら`new WidgetA()`という意味です。つまり、新しいインスタンスの生成。)

これでは無駄が多いですね。`WidgetA`と`WidgetB`は使いまわしたい所です。


###ちなみに
`Widget`を rebuild しているからと言って、必ずしも全て再レンダリングされているされているわけではないらしいです。
このあたりのことは「エレメント」などを勉強するとわかるはずです。[こちらが参考になると思います](https://medium.com/flutter-jp/dive-into-flutter-4add38741d07)。でも内容は高度です。
なお、再レンダリングされていなくても、`Widget`の rebuild 自体にコストがかかっているので、無駄は無駄。

#先に`Widget`のインスタンスを作って渡してしまう

ボタンを押す度に毎回`WidgetA` `WidgetB` `WidgetC`のインスタンスを生成するのはやめましょう。

最初から`MyStatefulWidget`のコンストラクタに一通りの`Widget`ツリーのインスタンスを渡してしまって、そのまま保持してもらいましょう。

`State`の中で`widget`と記述すると、その親にあたる`StatefulWidget`が取得できることに注意して、コメントの所を見てください。

##コード

```dart:main.dart
import 'package:flutter/material.dart';

void main() => runApp(MyStatefulWidget(
      child: MaterialApp(
        home: Scaffold(
          appBar: AppBar(),
          body: Column(children: [
            WidgetA(), // ここで作ったインスタンスをずっと使う
            WidgetB(),
            WidgetC(),
          ]),
        ),
      ),
    ));

class MyStatefulWidget extends StatefulWidget {
  MyStatefulWidget({Key key, this.child}) : super(key: key);
  final Widget child; // ここに一通りの Widget ツリーを保持し続ける
  @override
  State createState() => _MyState();
}

class _MyState extends State<MyStatefulWidget> {
  int _x = 0;
  int get x => _x;
  void increment() {
    setState(() {
      _x++;
    });
  }

  static _MyState of(BuildContext context) {
    return context.ancestorStateOfType(TypeMatcher<_MyState>());
  }

  @override
  Widget build(BuildContext context) {
    print("_MyAppState.build() is called!! x:$x");
    return widget.child; // setState()によりbuild()が呼ばれると、保持してる Widget ツリーをそのまま返すだけ
  }
}

class WidgetA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("AAA WidgetA.build() is called!!!!");
    return RaisedButton(
      child: Text("WidgetA is the incrementer"),
      onPressed: () {
        _MyState.of(context).increment();
      },
    );
  }
}

class WidgetB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("BBB WidgetB.build() is called!!!!");
    return Text("This is the constant WidgetB");
  }
}

class WidgetC extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final int x = _MyState.of(context).x;
    print("CCC WidgetC.build() is called!!!!");
    return Text("Widget C x: $x");
  }
}
```



##更新されない！

これを実行して`WidgetA`ボタンを押すと、画面の数値は**更新されず**、

コンソールには

```
flutter: _MyAppState.build() is called!! x:4
```

の行だけが出力されます。

`WidgetA` `WidgetB`が更新されないのはいいですが、`WidgetC`も更新されないのでは困ります。

最初に渡したインスタンスをそのまま何も変更せずに使いまわしてるのでこういうことになります。

これではさっきの方がマシですね。

#InheritedWidgetを使おう!

ということで、これを解決するのが`InheritedWidget`です。

まず`InheritedWidget`を継承したクラスを作ります。名前は`_MyInheritedWidget`にしてみます。

それを、`Widget`ツリー内の`State`の直下に挟み込みます。

具体的には、`_MyState`の`build()`関数が`_MyInheritedWidget(...)`を`return`するようにします。

そして、`WidgetC`が数値を参照する先を、`_MyState`ではなく、作成した`_MyInheritedWidget`に変えます。

###ポイント

このようにすると、`_MyState`の`setState()`が呼ばれる度に`build()`が呼ばれ、新しい`_MyInheritedWidget`のインスタンスが返されることになります。

でも、保持してる`WidgetA` `WidgetB` `WidgetC`のインスタンスは、`MyStatefulWidget`が保持してるインスタンスを再利用します。

 - `InheritedWidget`自体は rebuild される(毎回新しくインスタンスを作る)
 - 中身の`Widget`はもともとあったインスタンスを再利用する

というのがポイントですね。

##コード

```dart:main.dart
import 'package:flutter/material.dart';

void main() => runApp(MyStatefulWidget(
      child: MaterialApp(
        home: Scaffold(
          appBar: AppBar(),
          body: Column(children: [
            WidgetA(),
            WidgetB(),
            WidgetC(),
          ]),
        ),
      ),
    ));

class MyStatefulWidget extends StatefulWidget {
  MyStatefulWidget({Key key, this.child}) : super(key: key);
  final Widget child;
  @override
  State createState() => _MyState();
}

// _MyInheritedWidget の定義
class _MyInheritedWidget extends InheritedWidget {
  const _MyInheritedWidget({
    Key key,
    @required Widget child,
    @required this.value,
  }) : super(key: key, child: child);
  final int value; // xの値を受け取って、InheritedWidget 内で保持するための枠
  static _MyInheritedWidget of(BuildContext context) {
    return context.inheritFromWidgetOfExactType(_MyInheritedWidget)
        as _MyInheritedWidget;
  }

  // value値が変更された場合に、傘下のWidgetに更新を通知する。これがfalseの時は更新されない。
  @override
  bool updateShouldNotify(_MyInheritedWidget old) => old.value != value;
}

class _MyState extends State<MyStatefulWidget> {
  int _x = 0;
  int get x => _x;
  void increment() {
    setState(() {
      _x++;
    });
  }

  static _MyState of(BuildContext context) {
    return context.ancestorStateOfType(TypeMatcher<_MyState>());
  }

  @override
  Widget build(BuildContext context) {
    print("_MyAppState.build() is called!! x:$x");
    // _MyInheritedWidget のインスタンスを作り直して return する
    return _MyInheritedWidget(
      value: x, // _MyStateが保持しているxの値を、新しく作るInheritedWidgetインスタンスに渡す
      child: widget.child, // MyStatefulWidgetが保持しているWidgetツリーを使いまわす
    );
  }
}

class WidgetA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("AAA WidgetA.build() is called!!!!");
    return RaisedButton(
      child: Text("WidgetA is the incrementer"),
      onPressed: () {
        _MyState.of(context).increment();
      },
    );
  }
}

class WidgetB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("BBB WidgetB.build() is called!!!!");
    return Text("This is the constant WidgetB");
  }
}

class WidgetC extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 値の参照先を _MyState ではなく _MyInheritedWidget にする
    final int x = _MyInheritedWidget.of(context).value;
    print("CCC WidgetC.build() is called!!!!");
    return Text("Widget C x: $x");
  }
}
```

##`WidgetC`だけが rebuild される!

これを実行して、`WidgetA`ボタンを押すと、ちゃんと画面の`WidgetC`の数値が更新されます。

コンソールには下のような出力がでます。

```
flutter: _MyAppState.build() is called!! x:5
flutter: CCC WidgetC.build() is called!!!!
```

`WidgetA`と`WidgetB`は rebuild されていませんね!


これで目標が達成されました！**やったね！**

`_MyInheritedWidget`を参照しているのは`WidgetC`だけなので、**うまいことやって**それだけ更新してくれます。

#(最後に)`InheritedModel`について

この記事では紹介するに留めますが、`InheritedModel`というものもあります。

`InheritedWidget`が`x`と`y`という複数の値を保持していて、

`WidgetC`が`x`を、`WidgetD`が`y`を参照してる時、

`x`だけが変化したら、`WidgetC`だけを更新して、`WidgetD`は更新したくないですよね。

そういう時は`InheritedWidget`ではなく`InheritedModel`を使うと良いです。

[こちらの公式動画](https://www.youtube.com/watch?v=ml5uefGgkaA)が、概要を映像でわかりやすく説明してくれます。見てみてください。

はい。ということで、終わります。 みなさんもFlutterやりましょうね!