LoginSignup
17
14

More than 3 years have passed since last update.

Flutterでズンドコ (ChangeNotifier)

Posted at

懐かしいネタをDartで書かれている方がいた(こちらの記事)ので、自分もやってみました。

元ネタ

完成品

zundoko.gif

コード全体

最初にコード全体を載せておきます。(import文などは、省略しています。)
どうしてもネストが深くなりがちなFlutterのコードですが、
以下のように、Widgetを細かくクラスに切り出すことで、見通しの良いコードになると思います。


// ViewModel
class Kiyoshi with ChangeNotifier {
  String text = "";

  void sing() {
    if (text.endsWith("ズンズンズンズンドコ")) {
      text = "キ・ヨ・シ!";
    } else {
      if (text == "キ・ヨ・シ!") text = "";
      text += Random().nextBool() ? "ズン" : "ドコ";
    }
    notifyListeners();
  }
}

// ページ
class KiyoshiPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<Kiyoshi>(
      create: (_) => Kiyoshi(),
      child: Scaffold(
        body: Center(child: KiyoshiText()),
        floatingActionButton: KiyoshiButton(),
      ),
    );
  }
}

// ボタン
class KiyoshiButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final kiyoshi = Provider.of<Kiyoshi>(context, listen: false);
    return FloatingActionButton(
      child: Icon(Icons.add),
      onPressed: () => kiyoshi.sing(),
    );
  }
}

// テキスト
class KiyoshiText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final kiyoshi = Provider.of<Kiyoshi>(context);
    return Text(kiyoshi.text);
  }
}

簡単な説明

ViewModelの構成

ChangeNotifiermixinするだけで、ViewModelとして機能します。
ロジック実行後にnotifyListeners()を呼ぶことで、View側に変更を通知できます。
ロジック自体は適当なので、説明は省略します。

class Kiyoshi with ChangeNotifier {
  String text = "";

  void sing() {
    ...省略

    // View側に変更を通知
    notifyListeners();
  }
}

Widgetの構成

1. ViewModelをツリー上で使えるようにする

ChangeNotifierProviderを使って、先ほど作ったKiyoshiを注入します。
これで、KiyoshiPageの下位ツリー全体で同じKiyoshiのインスタンスにアクセスできるようになります。

class KiyoshiPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<Kiyoshi>(
      create: (_) => Kiyoshi(),
      child: ...
    );
  }
}

2. テキスト部分を作る

Provider.of()を使うことで、先ほど注入したKiyoshiのインスタンスをO(1)で取得できます。
この時、このWidgetはリスナーとして登録され、notifyListeners()が呼ばれる度に自動でリビルドされるようになります。

class KiyoshiText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final kiyoshi = Provider.of<Kiyoshi>(context);
    return Text(kiyoshi.text);
  }
}

また、以下のように、Consumerを使って書くこともできます。(こちらのほうが一般的かも)

class KiyoshiText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<Kiyoshi>(
      builder: (context, kiyoshi, child) {
        return Text(kiyoshi.text);
      },
    );
  }
}

3. ボタン部分を作る

やっていることは、テキスト部分と同じです。
ただし、ボタンはテキストと違い毎回リビルドする必要はないので、listen: falseを設定しています。

class KiyoshiButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //                                               ↓ココ重要
    final kiyoshi = Provider.of<Kiyoshi>(context, listen: false);
    return FloatingActionButton(
      child: Icon(Icons.add),
      onPressed: () => kiyoshi.sing(),
    );
  }
}

ChangeNotifierを使った感想

1. かんたん

ReduxやBLoCパターンと比べると、圧倒的にわかりやすいです。
また、ボイラープレートがほとんどなく、サクサク書けます。

2. 使ってる人が多い

Flutterのステートマネジメントの中では、かなり人気の構成です。
検索すれば、たくさん記事やサンプルコードが出てきます。(まだまだ日本語記事は少ないですが)

3. 他のアーキテクチャと併用も良さそう

自分は普段、BLoCパターンを使っているのですが、
基本はBLoCパターンで実装して、Themeの切り替えなど、単純なロジックのみProvider + ChangeNotifierで実装といった構成も有りかなと思いました。

ChangeNotifierを使う際の注意点

公式リファレンスを読むと、

ChangeNotifier is optimized for small numbers (one or two) of listeners. It is O(N) for adding and removing listeners and O(N²) for dispatching notifications (where N is the number of listeners).

と書かれています。
翻訳にかけると、

ChangeNotifierは、少数(1つまたは2つ)のリスナー用に最適化されています。リスナーを追加および削除する場合はO(N)、通知をディスパッチする場合はO(N²)です(Nはリスナーの数です)。

だそうです。
つまり、「listenするWidgetの数が多い場合には適していない」ようなので注意が必要です。

17
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
14