StatefulWidgetとStatelessWidgetの違いを記事にしましたが、ビジネスロジックと表示関係を分けてソースコードを管理したい場合に、StatefulWidgetを使うと難しくなってしまいます。

そこで、Providerパターンを使うと良いらしいです。

ちなみに◯◯パターンというのはたくさんあって、オールドプログラマーである私は、つい最近まで知らなかったのですが、21世紀の今はロジックを一から考えるのではなく、ある程度パターン化されてきているので、それを◯◯パターンという1つの型にして、定義したようなものです。

これをデザインパターンと言います。

デザインパターンを覚えておくと、「このロジックは、この◯◯パターンを使えるな」とか技術者同士での話が早くなるのだそうです。キン肉マンで言うところの、プリンスカメハメに教えてもらった48の殺人技みたいなもんかなw

Providerパッケージの導入と、Modelの分離の仕方もKboyさんの動画で勉強になりました。

実行結果の画面はこちら

Flutterの新規プロジェクト作成時に生成されるデフォルトのサンプルコードに少し手を加えて、main.dartをmain_modelと分離しています。

デフォルトでは、FloatingActionButtonはScaffoldの引数として用意されていましたが、modelのメソッドを呼び出すため、Customer内に移動させ、インクリメント用のボタンに加えて、デクリメント用のボタンを用意してみました。

パッケージ導入方法は、もう腐るほどやっているので割愛して、ソースだけ掲載します。

※「◆◆◆」がついているコメントは、ProviderでModel化に関わる部分です。

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'main_model.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      // ◆◆◆変更通知提供者で包括し、変更監視対象はMainModelとする
      home: ChangeNotifierProvider<MainModel>(  // from Widget
        // ◆◆◆変更監視対象のインスタンスを作成する
        create: (_) => MainModel(),
        child: Scaffold(
          appBar: AppBar(
            title: const Text('Flutter Demo Provider'),
          ),
          // ◆◆◆変更通知先である顧客を用意し、変更監視対象はMainModelとする
          body: Consumer<MainModel>(  // from StreamBuilder
            // ◆◆◆modelのインスタンスを受け取って使用する
            builder: (context, model, child) {
              return Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    const Text(
                      'You have pushed the button this many times:',
                    ),
                    Text(
                      // ◆◆◆変更監視対象のmodelのカウンタを表示する
                      model.pushCounter.toString(),
                      style: Theme.of(context).textTheme.headline4,
                    ),
                    Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        FloatingActionButton(
                          // ◆◆◆押下時、変更監視対象のmodelのメソッドを実行する
                          onPressed: model.decrementCounter,
                          tooltip: 'Decrement',
                          child: const Icon(Icons.remove),
                          backgroundColor: Colors.red,
                        ),
                        FloatingActionButton(
                          // ◆◆◆押下時、変更監視対象のmodelのメソッドを実行する
                          onPressed: model.incrementCounter,
                          tooltip: 'Increment',
                          child: const Icon(Icons.add),
                        ),
                      ],
                    ),
                  ],
                ),
              );
            }
          ),
        ),
      ),
    );
  }
}
import 'package:flutter/material.dart';

/// ◆◆◆ChangeNotifierを継承したモデルクラス
class MainModel extends ChangeNotifier {
  /// コンストラクタ
  MainModel() {
    // NOP
  }

  int pushCounter = 0;

  /// カウンタをインクリメントするメソッド
  void incrementCounter() {
    pushCounter++;
    // ◆◆◆変更を通知する
    notifyListeners();
  }

  /// カウンタをデクリメントするメソッド
  void decrementCounter() {
    pushCounter--;
    // ◆◆◆変更を通知する
    notifyListeners();
  }
}

ProviderパターンによるModel化のポイント

model側では、ChangeNotifierを継承して、明示的に通知メソッドであるnotifyListenersを呼び出しているので、標準のstatefulWidgetのsetStateメソッドを使うよりも分かりやすい気がする。

対してmain側では、Scaffoldを包み込む形で、ChangeNotifierProviderが用意されている。そしてScaffoldのbody引数に、Consumerを用意してmodelのインスタンスを扱っている。

main側が少々ややこしく見えるけど、こう考えると分かりやすいかな?

デパートなどのお店がChangeNotifierProviderで、館内放送でズボンの裾上げなど商品が仕上がるのを通知する。この「商品の仕上がり」がMainModelに相当する。

Consumerはそのお店に来ているお客さんで、商品の出来上がり(MainModel)を待っている。

お客さん(Consumer)は、お店(ChangeNotifierProvider)の中に居ないと館内放送で通知を受け取れない。

館内放送は何にでも対応していて、今回はズボンの裾上げで商品が仕上がるの(MainModel)を通知してるが、迷子やタイムセール、イベントなどをお知らせすることもできる。なのでジェネリック型で<MainModel>としている。

オールドプログラマーとしては、先進的なオブジェクト指向を理解しにくいですが、こんな感じで一つ一つ紐解いて理解に努めますw

About The Author

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA