Flutterのstate管理を理解する【InheritedWidget編】

この記事ではstate管理の方法としてInheritedWidgetについて記事です。
InheritedWidgetとは下位ツリーからO(1)でアクセスできる特性を持つstate管理ウィジェットです。

今回はシンプルなショッピングアプリを用いて学習する。
<img width="496" alt="スクリーンショット 2020-05-24 12.32.57.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/247084/3f7f9977-691f-1201-7463-56f1b6e56e41.png">

# まずは完成コード全体
```dart
import 'package:flutter/material.dart';

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

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CartWidget(
      child: MaterialApp(
        title: 'Home',
        home:Catalog(),
      ),
    );
  }
}

class Catalog extends StatelessWidget {
  final List<Menu> catalogs = [
    Menu(color: Colors.white, title: "white"),
    Menu(color: Colors.black, title: "black"),
    Menu(color: Colors.red, title: "red"),
    Menu(color: Colors.blue, title: "blue"),
    Menu(color: Colors.green, title: "green"),
    Menu(color: Colors.yellow, title: "yellow"),
    Menu(color: Colors.purple, title: "purple"),
  ];
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Catalog"),
        actions: <Widget>[
          IconButton(
            icon: const Icon(Icons.add_alert),
            tooltip: 'Show Snackbar',
            onPressed: () => Navigator.of(context).push(
              MaterialPageRoute(
                builder: (context) {
                  return Cart();
                },
              ),
            ),
          ),
        ]
      ),
      body: ListView.builder(
        itemCount: catalogs.length,
        itemBuilder: (BuildContext context, int index) {
          final inherited =
                    context.inheritFromWidgetOfExactType(_Inherited)
                        as _Inherited;
          return Container(
            width: MediaQuery.of(context).size.width*0.8,
            height: 100.0,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: <Widget>[
                Container(
                  width: 50.0,
                  height: 50.0,
                  decoration: BoxDecoration(
                    color: catalogs[index].color,
                    border: Border.all(color: Colors.grey),
                  ),
                ),
                Text("${catalogs[index].title}"),
                RaisedButton(
                  child: const Text("ADD"),
                  onPressed: () => inherited.state.addCart(catalogs[index])
                )
              ]
            )
          );
        }
      ),
    );
  }
}

class Cart extends StatelessWidget {  
  @override
  Widget build(BuildContext context) {
    final inherited = context.inheritFromWidgetOfExactType(_Inherited)
                        as _Inherited;
    return Scaffold(
      appBar: AppBar(title: Text("Cart")),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.center,
        children: inherited.state.cart.map((menu) => Text("${menu.title}")).toList()
      )
    );
  }
}

class CartWidget extends StatefulWidget {
  CartWidget({Key key, @required this.child}) : super(key: key);

  final Widget child;

  @override
  _CartState createState() => _CartState();
}

class _CartState extends State<CartWidget> {
  List<Menu> cart = [];
  
  void addCart(Menu menu) {
    setState(() => cart.add(menu));
  }
  
  @override
  Widget build(BuildContext context) {
    return _Inherited(
      state: this,
      child: widget.child,
    );
  }
}

class _Inherited extends InheritedWidget {
  const _Inherited({
    Key key,
    @required this.state,
    @required Widget child,
  }) : super(key: key, child: child);

  final _CartState state;

  @override
  bool updateShouldNotify(_Inherited old) => old.state.cart.length != state.cart.length;
}

class Menu {
  final Color color;
  final String title;
  Menu({ @required this.color, @required this.title });
}
```
サンプルだけ欲しいという方は上記のコードを参照してください。

#雛形の作成
```dart
import 'package:flutter/material.dart';

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

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CartWidget(
      child: MaterialApp(
        title: 'Home',
        home: Catalog(),
      ),
    );
  }
}

class Catalog extends StatelessWidget {
  @override 
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Catalog")),
      body: Container(
        child: Text("Catalog")
      )
    );
  }
}

class Cart extends StatelessWidget {
  @override 
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Cart")),
      body: Container(
        child: Text("Cart")
      )
    );
  }
}
```

#次にこの記事のメイン部分であるState管理ウィジェット
```dart
class CartWidget extends StatefulWidget {
  CartWidget({Key key, @required this.child}) : super(key: key);

  final Widget child;

  @override
  _CartState createState() => _CartState();
}

//statefulウィジェット
class _CartState extends State<CartWidget> {
  List<Menu> cart = [];
  
  void addCart(Menu menu) {
    setState(() => cart.add(menu));
  }
  
  @override
  Widget build(BuildContext context) {
    //InheritedWidgetで囲う
    return _Inherited(
      state: this,
      child: widget.child,
    );
  }
}

class _Inherited extends InheritedWidget {
  const _Inherited({
    Key key,
    @required this.state,
    @required Widget child,
  }) : super(key: key, child: child);

  final _CartState state;

  //ウィジェットを更新する条件を記載
  @override
  bool updateShouldNotify(_Inherited old) => old.state.cart.length != state.cart.length;
}
```

#カタログウィジェット
```dart
class Catalog extends StatelessWidget {
  final List<Menu> catalogs = [
    Menu(color: Colors.white, title: "white"),
    Menu(color: Colors.black, title: "black"),
    Menu(color: Colors.red, title: "red"),
    Menu(color: Colors.blue, title: "blue"),
    Menu(color: Colors.green, title: "green"),
    Menu(color: Colors.yellow, title: "yellow"),
    Menu(color: Colors.purple, title: "purple"),
  ];
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Catalog"),
        actions: <Widget>[
          IconButton(
            icon: const Icon(Icons.add_alert),
            tooltip: 'Show Shopping Cart',
            onPressed: () => Navigator.of(context).push(
              MaterialPageRoute(
                builder: (context) {
                  return Cart();
                },
              ),
            ),
          ),
        ]
      ),
      body: ListView.builder(
        itemCount: catalogs.length,
        itemBuilder: (BuildContext context, int index) {
          final inherited = context.inheritFromWidgetOfExactType(_Inherited) as _Inherited;
          return Container(
            width: MediaQuery.of(context).size.width*0.8,
            height: 100.0,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: <Widget>[
                Container(
                  width: 50.0,
                  height: 50.0,
                  decoration: BoxDecoration(
                    color: catalogs[index].color,
                    border: Border.all(color: Colors.grey),
                  ),
                ),
                Text("${catalogs[index].title}"),
                RaisedButton(
                  child: const Text("ADD"),
                  //inheritedのaddCart()を呼び出す
                  onPressed: () => inherited.state.addCart(catalogs[index])
                )
              ]
            )
          );
        }
      ),
    );
  }
}
```

#カートウィジェット
```dart
class Cart extends StatelessWidget {  
  @override
  Widget build(BuildContext context) {
    //inheritedを取得
    final inherited = context.inheritFromWidgetOfExactType(_Inherited) as _Inherited;
    return Scaffold(
      appBar: AppBar(title: Text("Cart")),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.center,
        //inheritedの値を取得
        children: inherited.state.cart.map((menu) => Text("${menu.title}")).toList()
      )
    );
  }
}
```