[Flutter] Providerを使ってAndroidのViewModel-LiveDataっぽいのを実装する

どうもFlutter超初心者です(挨拶

久しぶりにFlutterに触れてみたので書いてみる。

あいも変わらずViewの作り方が全くわかってない。。。

で、タイトル。

FlutterってViewの更新をするのにStatefulWidget使って更新する。

ただこいつの何が行けないってStatefulWidget の子View?の1つだけの更新を行うっていうことが出来ないらしい(多分

StatefulWidgetを更新しようとすると子Viewが全てRebuild掛かっちゃって描画に時間かかるらしい。らしい。

詳しいことはちゃんとは調べてないw

Viewの作り方また全然把握してないしね。

というわけで実験。

サンプルのFlutterProjectおなじみの少しだけ改良追加した版。

スポンサーリンク

サンプルのカウンターを3つにしたコード

//
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter1 = 0;
  int _counter2 = 0;
  int _counter3 = 0;

  void _incrementCounter1() {
    setState(() {
      _counter1++;
    });
  }

  void _incrementCounter2() {
    setState(() {
      _counter2++;
    });
  }

  void _incrementCounter3() {
    setState(() {
      _counter3++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              '$_counter1',
              style: Theme.of(context).textTheme.display1,
            ),
            Text(
              '$_counter2',
              style: Theme.of(context).textTheme.display1,
            ),
            Text(
              '$_counter3',
              style: Theme.of(context).textTheme.display1,
            ),
            MaterialButton(
              minWidth: 160,
              onPressed: () {
                _incrementCounter1();
              },
              child: Text("カウント1アップ"),
            ),
            MaterialButton(
              minWidth: 160,
              onPressed: () {
                _incrementCounter2();
              },
              child: Text("カウント2アップ"),
            ),
            MaterialButton(
              minWidth: 160,
              onPressed: () {
                _incrementCounter3();
              },
              child: Text("カウント3アップ"),
            )
          ],
        ),
      ),
    );
  }
}

カウント用のテキストを3つ、各テキストをカウントアップするためのボタン3つって言う簡単なコード。

このどれかのボタンを押すと、、、。

ちょっと分かりづらいけど、全てのWidgetがrebuildされている。

Viewの更新処理が無駄に走って重いってことね。

どう対応するか・・・。

Flutterって○○パターンっていう実装がとっても多い印象だけど、これから書くのは多分Providerパターン(多分

Providerパターンで書き直す

Package

provider: ^4.0.4

コード

//
class HogeViewModel with ChangeNotifier {
  int _counter1 = 0;
  int _counter2 = 0;
  int _counter3 = 0;

  void incrementCounter1() {
    _counter1++;
    notifyListeners();
  }

  void incrementCounter2() {
    _counter2++;
    notifyListeners();
  }

  void incrementCounter3() {
    _counter3++;
    notifyListeners();
  }
}

class MyHomePage extends StatelessWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<HogeViewModel>(
        create: (_) => HogeViewModel(),
        child: Scaffold(
            appBar: AppBar(title: Text(title)), body: MyHomePageView()));
  }
}

class MyHomePageView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final model = Provider.of<HogeViewModel>(context, listen: false);
    return content(context, model);
  }

  Widget content(BuildContext context, HogeViewModel model) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Selector<HogeViewModel, int>(
            selector: (context, model) => model._counter1,
            builder: (context, counter, child) => Text(
              '$counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ),
          Selector<HogeViewModel, int>(
            selector: (context, model) => model._counter2,
            builder: (context, counter, child) => Text(
              '$counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ),
          Selector<HogeViewModel, int>(
            selector: (context, model) => model._counter3,
            builder: (context, counter, child) => Text(
              '$counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ),
          MaterialButton(
            minWidth: 160,
            onPressed: () {
              model.incrementCounter1();
            },
            child: Text("カウント1アップ"),
          ),
          MaterialButton(
            minWidth: 160,
            onPressed: () {
              model.incrementCounter2();
            },
            child: Text("カウント2アップ"),
          ),
          MaterialButton(
            minWidth: 160,
            onPressed: () {
              model.incrementCounter3();
            },
            child: Text("カウント3アップ"),
          )
        ],
      ),
    );
  }
}

ChangeNotifierがProviderで使えるやつ。

Andoridで言うと個人的にはViewModelかなって思う。

で、内部で持っている変数がLiveData

View側でSelectorっていうWidgetで子に作りたいViewのWidgetを置く。

ChangeNotifier内部の変数の値が変わったことをnotifyListeners()で通知すると、値が変わった変数のSelectorだけ動いてくれるって感じ。

頭ではなんとなく理解したけど、文字に起こすのは難しい\(^o^)/

こんな感じにRebuildされている。

Selectorまでは通知が来て動いてしまってるけど、Selectorの子ViewまでにはRebuildの更新が来ていないことがわかる。

多分、処理が軽くなる!多分!

このサンプルだとViewの数が少ないから体感出来ていないけど、大きめのViewがたくさんあるのは体感できるはず!

で、ChangeNotifier-Selectorの何が良いってAndoroid開発者ならViewModel-LiveDataを最近はみんな使ってると思うんだけど、ほぼほぼ同じ感覚で使えるってこと。

欲しい値の変更受け取ってView更新。使いやすい!理解しやすい!

BLocパターンとか意味分かんないもん(ぇ

・・・・。

これからもまったーーーりとFlutterに触れていきますよ!

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