{"componentChunkName":"component---src-templates-blog-post-js","path":"/2020-10/2020-10-27-1547-Provider/","result":{"data":{"site":{"siteMetadata":{"title":"sasaki takeru Blog"}},"markdownRemark":{"id":"3b6878e4-f637-5e27-a014-90614ec6ac4c","excerpt":"Providerってなんですか？ Providerは色々機能(各種Providerの種類)はありつつも、主に以下の用途で使え、状態管理をこれだけに頼ってアプリを組むことも可能です。 (不変な)インスタンスを受け渡す(DI・サービスロケーター的な用途) 状態の変更を伝える (https://medium.com…","html":"<h3>Providerってなんですか？</h3>\n<blockquote>\n<p>Providerは色々機能(各種Providerの種類)はありつつも、主に以下の用途で使え、状態管理をこれだけに頼ってアプリを組むことも可能です。</p>\n<ul>\n<li>(不変な)インスタンスを受け渡す(DI・サービスロケーター的な用途)</li>\n<li>状態の変更を伝える</li>\n</ul>\n<p>(<a href=\"https://medium.com/flutter-jp/state-1daa7fd66b94\">https://medium.com/flutter-jp/state-1daa7fd66b94</a>)</p>\n</blockquote>\n<blockquote>\n<p>InheritedWidget を使いやすくしてミスを防ぐためのシンタックスシュガー\nDI の仕組みを提供\nインスタンスの生成と破棄を助ける（StatelessWidget でも dispose() が可能になる等）\nその他何でも（Scoped Model、BLoC 等による状態管理、ValueNotifier 等による Widget 更新など）\n(<a href=\"https://qiita.com/kabochapo/items/a90d8438243c27e2f6d9\">https://qiita.com/kabochapo/items/a90d8438243c27e2f6d9</a>)</p>\n</blockquote>\n<p><a href=\"https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple\">https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple</a>\n<a href=\"https://github.com/flutter/samples/blob/c6f6b5b757/provider_shopper/lib/main.dart\">https://github.com/flutter/samples/blob/c6f6b5b757/provider_shopper/lib/main.dart</a></p>\n<p><a href=\"https://github.com/flutter/samples/blob/c6f6b5b757752da94daee570ce28a8724c94b92c/provider_shopper/lib/models/catalog.dart#L14\">https://github.com/flutter/samples/blob/c6f6b5b757752da94daee570ce28a8724c94b92c/provider_shopper/lib/models/catalog.dart#L14</a></p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">class CatalogModel {\n  static const _itemNames = [\n    &#39;Code Smell&#39;,\n    ...\n    ...</code></pre></div>\n<p>CatalogModelはChangeNotifierを実装していない。</p>\n<p><a href=\"https://github.com/flutter/samples/blob/c6f6b5b757752da94daee570ce28a8724c94b92c/provider_shopper/lib/models/cart.dart#L8\">https://github.com/flutter/samples/blob/c6f6b5b757752da94daee570ce28a8724c94b92c/provider_shopper/lib/models/cart.dart#L8</a></p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">class CartModel extends ChangeNotifier { //★★★\n  /// The private field backing [catalog].\n  CatalogModel _catalog;\n\n  /// Internal, private state of the cart. Stores the ids of each item.\n  final List&lt;int&gt; _itemIds = [];\n\n  /// The current catalog. Used to construct items from numeric ids.\n  CatalogModel get catalog =&gt; _catalog;\n\n  set catalog(CatalogModel newCatalog) {\n    assert(newCatalog != null);\n    assert(_itemIds.every((id) =&gt; newCatalog.getById(id) != null),\n        &#39;The catalog $newCatalog does not have one of $_itemIds in it.&#39;);\n    _catalog = newCatalog;\n    // Notify listeners, in case the new catalog provides information\n    // different from the previous one. For example, availability of an item\n    // might have changed.\n    notifyListeners();\n  }\n\n  /// List of items in the cart.\n  List&lt;Item&gt; get items =&gt; _itemIds.map((id) =&gt; _catalog.getById(id)).toList();\n\n  /// The current total price of all items.\n  int get totalPrice =&gt;\n      items.fold(0, (total, current) =&gt; total + current.price);\n\n  /// Adds [item] to cart. This is the only way to modify the cart from outside.\n  void add(Item item) {\n    _itemIds.add(item.id);\n    // This line tells [Model] that it should rebuild the widgets that\n    // depend on it.\n    notifyListeners(); //★★★\n  }\n}</code></pre></div>\n<p>CartModelはChangeNotifierを実装している。\n内容が変わったときにnotifyListenersを呼ぶことになっている(おやくそく)。</p>\n<p><a href=\"https://github.com/flutter/samples/blob/c6f6b5b757752da94daee570ce28a8724c94b92c/provider_shopper/lib/main.dart\">https://github.com/flutter/samples/blob/c6f6b5b757752da94daee570ce28a8724c94b92c/provider_shopper/lib/main.dart</a></p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">void main() {\n  runApp(MyApp());\n}\n\nclass MyApp extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    // Using MultiProvider is convenient when providing multiple objects.\n    return MultiProvider(\n      providers: [\n        // In this sample app, CatalogModel never changes, so a simple Provider\n        // is sufficient.\n        Provider(create: (context) =&gt; CatalogModel()), //★★★\n        // CartModel is implemented as a ChangeNotifier, which calls for the use\n        // of ChangeNotifierProvider. Moreover, CartModel depends\n        // on CatalogModel, so a ProxyProvider is needed.\n        ChangeNotifierProxyProvider&lt;CatalogModel, CartModel&gt;( //★★★\n          create: (context) =&gt; CartModel(),\n          update: (context, catalog, cart) {\n            cart.catalog = catalog;\n            return cart;\n          },\n        ),\n      ],\n      child: MaterialApp(\n        title: &#39;Provider Demo&#39;,\n        theme: appTheme,\n        initialRoute: &#39;/&#39;,\n        routes: {\n          &#39;/&#39;: (context) =&gt; MyLogin(),\n          &#39;/catalog&#39;: (context) =&gt; MyCatalog(),\n          &#39;/cart&#39;: (context) =&gt; MyCart(),\n        },\n      ),\n    );\n  }\n}</code></pre></div>\n<p>Widgetツリーの一番上あたりでCatalogModelとCartModelのインスタンスを生成している。</p>\n<p>CartModelはCatalogModelに依存しているので、上で生成したCatalogModelを取ってきて(内部でProvider.of<CatalogModel>(context))「cart.catalog = catalog;」として注入ってことだろうたぶん。</p>\n<h3>Provider.of</h3>\n<p>Provider.of&#x3C;ほしいクラス>(context)で、contextを上に登って見つかった『ほしいクラス』のインタンスを取れる。</p>\n<p><a href=\"https://github.com/flutter/samples/blob/c6f6b5b757752da94daee570ce28a8724c94b92c/provider_shopper/lib/screens/cart.dart#L40\">https://github.com/flutter/samples/blob/c6f6b5b757752da94daee570ce28a8724c94b92c/provider_shopper/lib/screens/cart.dart#L40</a></p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">class _CartList extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    var itemNameStyle = Theme.of(context).textTheme.title;\n    var cart = Provider.of&lt;CartModel&gt;(context); //★★★\n\n    return ListView.builder(\n      itemCount: cart.items.length,\n      itemBuilder: (context, index) =&gt; ListTile(\n        leading: Icon(Icons.done),\n        title: Text(\n          cart.items[index].name,\n          style: itemNameStyle,\n        ),\n      ),\n    );\n  }\n}</code></pre></div>\n<p>cart.itemsを使ってリスト表示を作っている。</p>\n<p><a href=\"https://github.com/flutter/samples/blob/c6f6b5b757752da94daee570ce28a8724c94b92c/provider_shopper/lib/screens/catalog.dart#L35\">https://github.com/flutter/samples/blob/c6f6b5b757752da94daee570ce28a8724c94b92c/provider_shopper/lib/screens/catalog.dart#L35</a></p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">class _AddButton extends StatelessWidget {\n  final Item item;\n\n  const _AddButton({Key key, @required this.item}) : super(key: key);\n\n  @override\n  Widget build(BuildContext context) {\n    var cart = Provider.of&lt;CartModel&gt;(context); //★★★\n\n    return FlatButton(\n      onPressed: cart.items.contains(item) ? null : () =&gt; cart.add(item),\n      splashColor: Theme.of(context).primaryColor,\n      child: cart.items.contains(item)\n          ? Icon(Icons.check, semanticLabel: &#39;ADDED&#39;)\n          : Text(&#39;ADD&#39;),\n    );\n  }\n}</code></pre></div>\n<ul>\n<li>cart.itemsに含まれているか(contains)を見てボタンの切り替えをしている。</li>\n<li>cart.addでボタンが押されたときの「カートに追加」の処理を行っている。</li>\n</ul>\n<p><a href=\"https://github.com/flutter/samples/blob/c6f6b5b757752da94daee570ce28a8724c94b92c/provider_shopper/lib/screens/catalog.dart#L70\">https://github.com/flutter/samples/blob/c6f6b5b757752da94daee570ce28a8724c94b92c/provider_shopper/lib/screens/catalog.dart#L70</a></p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">class _MyListItem extends StatelessWidget {\n  final int index;\n\n  _MyListItem(this.index, {Key key}) : super(key: key);\n\n  @override\n  Widget build(BuildContext context) {\n    var catalog = Provider.of&lt;CatalogModel&gt;(context); //★★★\n    var item = catalog.getByPosition(index);\n    var textTheme = Theme.of(context).textTheme.title;\n\n    return Padding(\n      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),\n      child: LimitedBox(\n        maxHeight: 48,\n        child: Row(\n          children: [\n            AspectRatio(\n              aspectRatio: 1,\n              child: Container(\n                color: item.color,\n              ),\n            ),\n            SizedBox(width: 24),\n            Expanded(\n              child: Text(item.name, style: textTheme),\n            ),\n            SizedBox(width: 24),\n            _AddButton(item: item),\n          ],\n        ),\n      ),\n    );\n  }\n}</code></pre></div>\n<p>「model=Provider.ofを呼んだbuildが属するWidgetはmodelが更新されたらリビルドされます」\nむずかしい。。。</p>\n<p>具体的には。。。</p>\n<ul>\n<li>\n<p><em>CartList,</em>AddButtonのbuildの中で『var cart = Provider.of<CartModel>(context);』としている。</p>\n<ul>\n<li>cartの内容を使って画面表示を作っている。(カート内リストの内容や数,ボタンのラベルの切り替えなど)</li>\n<li>このWidget(<em>CartList,</em>AddButton)はcartが更新されたらリビルド(=表示を最新に更新)してほしい。</li>\n<li>→ Providerさん「Provider.of(listen=true)したのでデータが更新されたらリビルドしますね！！」</li>\n</ul>\n</li>\n<li>\n<p>CartModel(extends ChangeNotifier)のaddでnotifyListenersが呼ばれる。</p>\n<ul>\n<li>ChangeNotifierProviderが変更を受け取る。</li>\n<li>→ 「データが更新されたそうなのでさっきのProvider.ofしたところのWidget、リビルドします！」</li>\n</ul>\n</li>\n<li>\n<p>TODO</p>\n<ul>\n<li>Provider.ofしたリビルド対象のWidgetを覚えている仕組みが謎。</li>\n<li>ProviderがWidget更新する仕組みが謎。特にStatelessWidget。</li>\n</ul>\n</li>\n</ul>\n<h3>Consumer</h3>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">class _CartTotal extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    var hugeStyle = Theme.of(context).textTheme.display4.copyWith(fontSize: 48);\n\n    return SizedBox(\n      height: 200,\n      child: Center(\n        child: Row(\n          mainAxisAlignment: MainAxisAlignment.center,\n          children: [\n            Consumer&lt;CartModel&gt;(\n                builder: (context, cart, child) =&gt;\n                    Text(&#39;\\$${cart.totalPrice}&#39;, style: hugeStyle)),\n            SizedBox(width: 24),\n            FlatButton(\n              onPressed: () {\n                Scaffold.of(context).showSnackBar(\n                    SnackBar(content: Text(&#39;Buying not supported yet.&#39;)));\n              },\n              color: Colors.white,\n              child: Text(&#39;BUY&#39;),\n            ),\n          ],\n        ),\n      ),\n    );\n  }\n}</code></pre></div>\n<p>contextが無い場合にConsumer&#x3C;ほしいクラス>でProvider.ofのようにインスタンスを取れるWidget。\nBuilder Widget使うかWidgetを切り出すかすれば同じこと、かな？\n複数取りたいときはどうするのかな？</p>\n<p>と思ってたら、</p>\n<ul>\n<li>childを使うとリビルドを最適化できる</li>\n<li>Consumer2() から Consumer6()まである </li>\n<li>Consumer をより便利にした Selector がある\n(<a href=\"https://qiita.com/kabochapo/items/a90d8438243c27e2f6d9\">https://qiita.com/kabochapo/items/a90d8438243c27e2f6d9</a>)</li>\n</ul>\n<h3>read,watchという新しいのがある</h3>\n<ul>\n<li>context.read() ≒ Provider.of(context, listen: false)</li>\n<li>context.watch() ≒ Provider.of(context, listen: true)</li>\n<li>\n<p>context.select()</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">final value = context.select((Foo foo) =&gt; foo.value);\nText(value.toString());</code></pre></div>\n</li>\n</ul>\n<p>大きなクラスのある一部だけの変化だけを検知→リビルドできる。これは良さそう。\nChangeNotifierProviderに大きなモデルを乗っけてselectで指定したところが変わったときだけrebuildされる。\n「モデルの『どこ』が変わったら〜」の『どこ』をWidget側に寄せることができる。</p>\n<h3>まとめ</h3>\n<p>いろんななんとかProviderがあるけど、データソースによっていろいろ使えるよってことみたい。\n基本はChangeNotifierってことでいいのかな。</p>\n<p>Widgetと変更検知範囲の粒度をうまくやって、コードの可読性と変更検知＆リビルドのパフォーマンスのバランスを取るってことかな。\nはじめは荒く、問題になったら最適化、で。</p>","frontmatter":{"title":"Providerってなんですか？","date":"October 27, 2020","description":null}},"previous":{"fields":{"slug":"/2020-10/2020-10-26-1600/"},"frontmatter":{"title":"ブログ更新!!"}},"next":{"fields":{"slug":"/2020-11/2020-11-08-0448-Flutter状態管理/"},"frontmatter":{"title":"『状態管理』ってなんですか？"}}},"pageContext":{"id":"3b6878e4-f637-5e27-a014-90614ec6ac4c","previousPostId":"32a5dfcc-e5e2-5074-a19f-2ee5505094c7","nextPostId":"0f43bcdf-5a15-5086-a2e7-26ff8c0bb30e"}},"staticQueryHashes":["2841359383","3257411868"]}