【初心者必見!】FlutterのProviderでSelectorについて解説してみたよ

Dart

概要

「Consumer?Selector?どっち使って良いか分からん…。」

この記事では、そんな疑問にお答えすべく書きました!
是非最後までご覧いただけると嬉しいです。

前提知識

FlutterのProviderについて全然分からないという方は
解説記事を書いていますのでこれらの記事をご覧いただけると嬉しいです。

【Flutter】Providerについてサンプル付きで解説してみた
追記情報 lintに対応した書式に変更しました。 (2020.09.29) 概要 「FlutterのProvider?」 「ChangeNotifier?Consumer?何それ美味しいの?」 あなたは、こ...
【Google Chrome】タブをグループ化出来るって知ってた?設定は1分で完了!
概要 Google Chromeみなさん使ってますか? タブめちゃくちゃいっぱい開いてませんか? 開いてますよね、知ってます笑 今回の記事では、そんなタブをグループ化することで 多いタブをまとめる事ができる様にする方法を解...
【Flutter】ProviderにおけるConsumerについて解説してみたよ vol.2
追記情報 lintに対応した形式に修正しました。 2020.09.29 概要 「Consumerってbuilderだけコンストラクタの引数に渡せば良いんじゃないの?」 「第三引数のWidgetって何?」 「Cons...

それでは、解説に参りましょー!

Providerは分かったけど、SelectorとConsumerどっちを使えば良いの?

SelectorとConsumerどちらを使えば良いのか?
基本的には、SelectorもConsumerも監視インスタンスが変更された際にbuilder関数を呼び出してウィジェットを描画します。

その中で、Selectorは

監視しているインスタンスが変更された時に、更に条件にマッチした時だけリビルド(builder関数を実行)する

という特徴があります。

言葉だけだとちょっとイメージが付きづらいと思うので具体例で解説しましょう。

前提コード

>
class Book {
  final String id;
  final String title;
  bool isFavorite = false;

  Book(this.id, this.title);

  void toggleFavorite() {
    isFavorite = !isFavorite;
  }
}

class Books with ChangeNotifier {
  List<Book> books = [
    Book('1', 'Harry Potter'),
    Book('2', 'FACTFULNESS'),
  ];

  // 本の冊数
  int get bookCount {
    return books.length;
  }

  // idを指定して本を返す
  Book findById(String id) {
    for (Book book in books) {
      if (book.id == id) return book;
    }
    return null;
  }

  // お気に入りかどうかを入れ替える
  void toggleFavorite(String id) {
    Book book = findById(id);
    if (book == null) return;

    book.toggleFavorite();
    notifyListeners();
  }

  // 本を追加する
  void addBook(Book book) {
    books.add(book);
    notifyObservers();
  }

  // お気に入り数の取得
  int get favoriteCount {
    int count = 0;
    for (Book book in books) {
      if (book.isFavorite) count++;
    }
    return count;
  }
}

以上の様なBookクラス及びChangeNotifierに適用したBooksクラスがあると思ってください。

Consumerの場合

class BooksCount extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer(
      builder: (ctx, booksData, _) {
        return Text('冊数: ${booksData.bookCount}');
      },
    );
  }
}

本の冊数を表示する簡単なBooksCountウィジェットを用意しました。
Consumerを使うとこんな感じになるのはおわかりいただけるかなと思います。

「分からん…」
って方は前回の記事をご覧いただけるとお分かりいただけると思います。

これをSelectorを使って書き換えてみるとこんな感じです。

Selectorの場合

class BooksCount extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Selector(
      selector: (ctx, books) => books.bookCount,
      shouldRebuild: (oldCount, newCount) => oldCount != newCount,
      builder: (ctx, count, _) {
        return Text('冊数: $count');
      },
    );
  }
}

「selector?shouldRebuild?なんだ君たちは…?」

そんな声が聞こえてきそうです笑

解説していきましょう。

先ほども書いた様に、

Selectorは監視しているインスタンス(今回だとBooksインスタンス)が変更された時に、
条件を記述してリビルド出来る

という特徴があります。

それを制御しているのがselector, shouldRebuildです。

selector

selectorには、
どの値を条件とするか
を返却します。

今回で言えば、本の冊数を表示していて本の冊数が変わった時だけ
リビルドしたいのでbookCountを返します。

shouldRebuild

shouldRebuildにはselectorで返却した値の更新前と更新後の値が引数として渡されます。
そして、リビルドするかどうかを返します。

今回で言えば、本の冊数が変わったかどうか、つまり
oldCount != newCountを返します。

builder

第二引数だけ変更されています。
ConsumerではBooksインスタンスだったのに対し、Selectorではselectorで返した値が渡されます。

今回で言えば、本の冊数bookCountですね。

「なんでSelectorって必要なん?Consumerで良いのでは?」

「なるほど、Selectorの使い方は分かった。
でも、それ使う必要ある?」

ここからはなぜ使う必要があるのかを解説します。

Selectorの有用性

ここで、さらに以下の様なWidgetを追加しました。
bookCountがfavoriteCountになっただけです。

class FavoriteCount extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Selector(
      selector: (ctx, books) => books.favoriteCount,
      shouldRebuild: (oldCount, newCount) => oldCount != newCount,
      builder: (ctx, count, _) {
        return Text('お気に入り数: $count');
      },
    );
  }
}

さて、Booksインスタンスは以下の時に通知を飛ばします。

・本の冊数が変わった時 (addBook)
・お気に入りの数が変わった時 (toggleFavorite)

勘の良い方はもうお分かりになったかもしれませんね。

BookCountウィジェットにとってはお気に入りの数が変わってもリビルドする必要はありません。
逆も然りです。

この様に、

監視しているインスタンスから自信のリビルドに関係ないプロパティの変更まで通知される可能性がある場合
Selectorを使えば、余計なリビルドを防ぎパフォーマンス向上につなげることが出来る
のです。

「全部Selectorでよくね?」

そうすると逆に

「もう、全部Selectorで良いのでは?」

という疑問が湧き上がってくるかもしれません笑

では、以下の場合はどうでしょうか?

class BooksCountAndFavoriteCount extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<Books>(
      builder: (ctx, booksData, _) {
        return Text('${booksData.favoriteCount} / ${booksData.bookCount}');
      },
    );
  }
}

既に上に書いた様に、Selectorでは監視するインスタンスのうち
リビルドの条件となる値を指定するために一つのプロパティに絞ってしまいます。
なので、builderで返却するwidgetが監視するインスタンスの複数のプロパティを用いる場合は
Consumerが最適となります。

最後に

いかがでしたでしょうか?
ConsumerとSelectorを使い分ければパフォーマンスを意識したFlutterアプリを構築できると思います。

誰かのお役に立てば。

Twitterフォローお願いします

「次回以降も記事を読んでみたい!」
「この辺分からなかったから質問したい!」

そんな時は、是非Twitter (@daiki1003)Instagram (@ashdik_flutter)のフォローお願いします♪

Twitterコミュニティ参加お願いします

Twitterコミュニティ「Flutter lovers」を開設しました!
参加お待ちしております😁

☕️ Buy me a coffee

また、記事がとても役に立ったと思う人は
コーヒーを奢っていただけると非常に嬉しいです!

コメント

タイトルとURLをコピーしました