追記情報
lintに対応した形式に修正しました。 2020.09.29
概要
前回の記事で、Providerに関して詳しく説明しました。
今回の記事はこちらの記事での知識を前提に書かれているので、
Providerの基礎についてまだ理解していない方は、先にお読みいただけると嬉しいです。
さて、Providerを勉強しているとConsumerって単語も出て来ます。
…消費者?
この記事を読めば、Consumerの使い所や使い方などを理解していただけると思います♪
また、サンプルに関しては今回の解説内容分を更新しました。
それでは、解説していきましょー!
前回の完成形から問題点を考える
Providerを用いた前回の完成形
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; class Book { Book(this.id, this.title); final String id; final String title; bool isFavorite = false; void toggleFavorite() { isFavorite = !isFavorite; } } class Books with ChangeNotifier { List<Book> books = [ Book('1', 'Harry Potter'), Book('2', 'FACTFULNESS'), ]; Book findById(String id) { return books.firstWhere((book) => book.id == id); } void toggleFavorite(String id) { final book = findById(id); if (book == null) { return; } book.toggleFavorite(); notifyListeners(); } int get favoriteCount { return books.where((book) => book.isFavorite).length; } } class BookshelfScreen extends StatelessWidget { @override Widget build(BuildContext context) { final booksData = Provider.of<Books>(context); return Scaffold( appBar: AppBar( title: const Text('Book List'), ), body: Center( child: SizedBox( height: 200, child: Column( children: <Widget>[ Expanded( child: Bookshelf(booksData.books), ), Center( child: Text('totalFavoriteCount: ${booksData.favoriteCount}'), ), ], ), ), ), ); } } class Bookshelf extends StatelessWidget { const Bookshelf(this.books); final List<Book> books; @override Widget build(BuildContext context) { return ListView.builder( itemCount: books.length, itemBuilder: (ctx, index) => BookItem( books[index].id, ), ); } } class BookItem extends StatelessWidget { const BookItem(this.bookId); final String bookId; @override Widget build(BuildContext context) { final booksData = Provider.of<Books>(context); final book = booksData.findById(bookId); return ListTile( leading: Text(book.id), title: Text(book.title), trailing: IconButton( icon: Icon(book.isFavorite ? Icons.star : Icons.star_border), onPressed: () => booksData.toggleFavorite(book.id), ), ); } }
問題点を考える
前回、これで完成!としたのは良いのですがまだ実は問題点があります。
試しに、Bookshelfのbuildメソッドの先頭に
print("build");
を追加してみます。
この状態で、お気に入りボタンを押すと
flutter: build flutter: build
と押すたびに出力されます。
つまり、
お気に入りボタンを押すたびにBookshelfが再ビルドされてしまっている
と言うことです。
ですが、お気に入りボタンを押したとしてもBookshelfウィジェット自体は何も変わっていないのでビルドされる必要はありません。
ここで、登場するのがお気づきの通りConsumerとなります。
ProviderのConsumerを使って書き換えてみよう
Consumerって何?
Consumerの特徴は
notifyされた際、再ビルドの対象を絞るためのWidget
※notifyされる必要があるので、Provider.ofと同じく
ChangeNotifierProviderが管理しているWidget内でないともちろんダメです。
です。
今回の例で言えば、再ビルドしたい対象とはどれでしょうか?
そうです、favoriteCountの表示をしているCenterだけです。
Consumerでラップする
なので、まずCenterをConsumerでラップします。
Consumer( child: Center( child: Text('totalFavoriteCount: ${booksData.favoriteCount}'), ), ),
Consumerはジェネリック型なのでBooksを指定します。
Consumer<Books>( child: Center( child: Text('totalFavoriteCount: ${booksData.favoriteCount}'), ), ),
引数builderに再ビルド関数を渡す
Consumerの引数として、再ビルドするための関数をbuilder引数に渡します。
Consumer<Books>( builder: (ctx, booksData, _) { return Center( child: Text('totalFavoriteCount: ${booksData.favoriteCount}'), ); }, ),
builder関数は以下の様な定義となっています。
Widget Function(BuildContext, dynamic, Widget)
返り値
返り値のWidgetにはBooksが更新されるたびに再ビルドしたいWidgetを返します。
今回であればBooksが更新されるたびにお気に入りの数が更新されているはずなのでCenterウィジェットを返しています。
引数
次は、引数について考えてみましょう。
BuildContextは良いとして、dynamicとWidgetには何が渡ってくるのでしょうか?
(上記のサンプルでは第三引数は使わないので_として使わないことを明示しています。)
第二引数
-> Booksインスタンス
第三引数
-> Consumerコンストラクタにchildとして渡したインスタンス
がそれぞれ返って来ます。
Consumer<Books>でラップするとBooksが更新されるたびにbuilder関数が実行されます。
その際に一部、Booksの更新に関係ないWidgetを含んでいたとします。
そんな時はConsumerの引数childとして、
Booksの更新に関係ないWidgetを渡すとリビルドすることなく、builder関数実行時に使えると言う流れです。
この様にして少しでも無駄なリビルドを無くしてパフォーマンスを改善しているのです。
ここに関しては、同じサンプルを用いて明日解説記事を書きたいと思います。
BookshelfScreenのProvider.ofにlisten: falseを設定しよう
さて、Consumerでラップしたことで、Booksの中身が変更されたらCenterウィジェットがリビルドされる様になりました。
現在、BookshelfScreenのbuildメソッドで
final booksData = Provider.of<Books>(context);
を呼んでいるため、Booksの更新時にBookshelfScreen全体がリビルドされる様になっています。
が、今回の例で言えばお気に入りが変わってもBookshelfはリビルドする必要はありません。
なので、BookshelfScreen全体としては通知の変更を受け取る必要はないわけです。
これを実現するために、Provider.ofメソッドの第二引数に listen: false を指定します。
class BookshelfScreen extends StatelessWidget { @override Widget build(BuildContext context) { final booksData = Provider.of<Books>(context, listen: false); ...
これで、ホットリスタートをすればお気に入りをオン/オフしてもbuildが表示されることはないですね。
つまり、Booksの更新で再ビルドされるのは
・Centerウィジェット
・BookItemウィジェット
の二つだけになります。
Consumerはパフォーマンス改善のために積極的に使おう
この様に、Consumerはパフォーマンス改善のために積極的に使っていきましょう。
そして、実はまだまだ改善点があります。
それを考えてから次の記事に進んでみてください♪
Twitterフォローお願いします
「次回以降も記事を読んでみたい!」「この辺分からなかったから質問したい!」
そんな時は、是非Twitter (@daiki1003)やInstagram (@ashdik_flutter)のフォローお願いします♪
Twitterコミュニティ参加お願いします
Twitterコミュニティ「Flutter lovers」を開設しました!参加お待ちしております😁
☕️ Buy me a coffee
また、記事がとても役に立ったと思う人はコーヒーを奢っていただけると非常に嬉しいです!
コメント