Flutter: クラスの外からsetStateで再描画する方法 – ProviderやRiverpodを使わないMVVM

Flutter

Stateful Widgetを再描画したいけど、自分以外の別のクラスから再描画を発動する方法setStateを実行する方法)が分からない。どうしたらいいの?

という方向けの記事です。

 

例えば、AppBarbody(Scaffoldのbodyプロパティ)のWidget別クラスにしたとき

 

AppBar上のボタンを押したら、bodyのWidgetを再描画して更新したい

 

みたいな場合です。

 

このようなケースでは、プログラムの構造を

 

  • View層(UI・見た目)
  • ViewModel層(View層とModel層のつなぎ・View層への更新通知)
  • Model層(ロジック・計算)

 

の3層に分割した「MVVM(Model-View-ViewModel)」モデルにし、ViewModel層経由でView層に更新を通知する方法が、1つの対処法かと思います。

 

しかし、ViewModel層からView層に更新を通知して、Widgetを再描画するには、「Provider」「Riverpod」などの状態管理パッケージを使う必要があり、使い方が初学者には難しい印象です。。。

 

自分自身、リリースしたアプリでは、一応、全て「Provider」か「Riverpod」を使っていますが、今だに難しく感じています。。

 

そこで、以前から、

 

簡単なアプリだったら、Stateful WidgetとsetStateだけで、何とかならないだろうか?

 

と考えていました。

 

ややマニアック、またはアンチパターン(やってはいけないこと)かもしれないのですが、、、ネットを調べると、Flutterの状態管理に関する情報は多くあり、近い情報は見つかったものの、明確に今回の疑問に合致する情報は見つけられなかったので、自分で考察してみました。

 

なお、MVVMを使わず、View層に全てのロジックも持たせsetStateで全体を再描画する手もありますが、前述の例では、AppBarとbodyのWidgetを同一クラスにする必要があり、簡単なアプリとは言え、コードが肥大化して、メンテナンスしづらくなってしまいます。

 

結論として、以下のような方法で検証したら実現できたので、その例(サンプルコード)を共有したいと思います。

 

  • View層は、再描画したい単位でStateful Widgetに分ける(分け方は粗目でも可とする)
  • 各Stateful WidgetのsetStateを、Function型のプロパティとしてViewModel層に渡す
  • ViewModel層で、渡されたsetStateを、再描画したいタイミングで実行する

 

理解が未熟ゆえ、おかしい点が多々あるかと思いますので、お気づきの点があれば、ご指摘くださいm(_ _)m。

 


 

40代からプログラミング(Flutter)を始めて、GooglePlayAppStoreにアプリを公開しているhalzo appdevです。

 

作成したアプリはこちら↓ 全てFlutterで開発したアプリです。

 

暗記用マーカー – シンプル穴埋め問題作成

Google Play で手に入れよう
Download on the App Store

 

超即ToDo –最短2タップで通知登録できるタスク管理アプリ

Google Play で手に入れよう
Download on the App Store

 

かんたんプリント管理:アラート・OCR文字認識・検索機能を搭載

Google Play で手に入れよう
Download on the App Store

 

シンプルメモ帳「BasicMemo」 – 文字カウント、ワンタッチ入力、タグ管理等の機能を搭載

Macのデスクトップ版もリリースしました。

Google Play で手に入れよう
Download on the App Store

 

サンプルコードの前提となるWidgetの構造

以下のような、単純な例を考えました。

 

 

実行時の画面は、「Aを更新」ボタンを押すと、WidgetAのカウンターだけが増え、「Bを更新」ボタンを押すと、WidgetBのカウンターだけが増え、「両方を更新」ボタンを押すと、両方のカウンターが増えるというものです。

 

実行時の画面の挙動

 

一般に、これをStateful WidgetとsetStateだけで実現(状態管理:変数の変更に伴うWidgetの再描画)をしようとすると、以下のようなデメリットがあるかと思います。

 

  • AppBarWidgetOからBodyWidgetAOだけを再描画しようとすると、両クラスは親子関係にないため(Widgetツリーが異なるため)、BodyWidgetAOのsetStateを発動できない
  • 下位のBodyWidgetAOから上位のWidget(例えばSampleScreenO)を再描画しようとすると、上位のWidgetのsetStateを引数で下位のWidgetに渡していく必要があり、コーディングが煩雑になる。
  • SampleScreenO以下を全て一つのクラスにし、再描画を1つのsetStateで処理する方法もあるが、
    • Widgetの階層構造が深くなり、表示(UI)と論理(ロジック)が一体化したコードになるので、メンテナンスしにくくなる。
    • 無駄な再描画が増えるので、処理が重くなる

 

そのため、下記のとおり、MVVMモデルを導入し、変数の更新(カウントアップする計算)はModel層で行い、ViewModel層では、更新したいWidgetに対してのみ、再描画のための更新通知を行う構造としました。

 

 

ここで、一般的には「Provider」「Riverpod」などの状態管理パッケージを導入し、ViewModel層から、「notifyListeners()」や、「state」プロパティの更新を通じて、更新通知を行うと思います。

 

しかし、今回はこれらのパッケージを使わずに、Stateful WidgetとsetStateだけで実現する方法を考えました。

 

Stateful WidgetとsetStateだけで状態管理する方法

全体像は下図のとおりです。

 

 

大まかには、

 

  • View層では、再描画する可能性のあるWidgetは、全てStateful Widgetとし、
  • 各WidgetのsetStateメソッドを、ViewModel層に渡すinitStateメソッドの中で実行)。
  • ViewModel層では、更新したいWidgetから渡されたsetStateメソッドを実行することで、Model層の計算結果を、当該Widgetに反映し、再描画する

 

という感じです。

 

一般に非推奨とされるグローバル変数は、使わないようにしました。

 

この方法で、ProviderやRiverpodを使わずに、実現できました。

 

サンプルコードの全体像

今回作成したサンプルコードの全体像を掲載します。

 

「main.dart」にそのままコピペいただけば、挙動を確認いただけます。

 

本来は、ViewModel層のクラス(SampleViewModelO)やModel層のクラス(SampleModelO)は、別のdartファイルにすべきですが、1回のコピペで済むように、あえて1つのdartファイルに集約しました。

 

// クラス名、メソッド名、プロパティ名(変数名)について、筆者が作成したもの(名前変更可のもの)
// の名前の末尾には、大文字のオー「O」をつけています
// ※ライブラリ(パッケージ)で予め決められているもの(名前の変更不可のもの)と、
//  自分で作成したもの(名前の変更可のもの)の区別をしやすくするため

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "setState and MVVM Demo",
      theme: ThemeData.light(),
      home: SampleScreenO(),
    );
  }
}


/// View層 ///////////////////////////////////////////////////
class SampleScreenO extends StatefulWidget {
  @override
  _SampleScreenOState createState() => _SampleScreenOState();
}

class _SampleScreenOState extends State<SampleScreenO> {

  @override
  Widget build(BuildContext context) {
    debugPrint("全体を描画");
    return  Scaffold(
      appBar: AppBar(title: AppBarWidgetO()),
      body: BodyWidgetO(),
    );
  }
}

// AppBar部分のクラス
// AppBarを再描画しない場合は、StatelessWidgetでも良い
class AppBarWidgetO extends StatefulWidget {
  const AppBarWidgetO({Key? key}) : super(key: key);
  @override
  _AppBarWidgetOState createState() => _AppBarWidgetOState();
}

class _AppBarWidgetOState extends State<AppBarWidgetO> {
  @override
  Widget build(BuildContext context) {
    debugPrint("AppBarを描画");
    // テキストボタンのスタイルを設定
    ButtonStyle buttonStyleO =
        ButtonStyle(backgroundColor: MaterialStateProperty.all<Color>(Theme.of(context).primaryColorLight));

    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        // WidgetAを更新するボタン
        TextButton(
          child: Text("Aを更新", style: Theme.of(context).textTheme.button),
          style: buttonStyleO,
          onPressed: () {
            // ボタンを押したら、ViewModel内のWidgetAのカウンターを加算表示するメソッドを実行
            // ※ViewModelはシングルトン化しており、staticのインスタンス経由でアクセスするため、
            //  クラス名.インスタンス名.メソッド名で書く
            SampleViewModelO.instanceO.countUpForWidgetAO();
          },
        ),
        // WidgetBを更新するボタン
        TextButton(
          child: Text("Bを更新", style: Theme.of(context).textTheme.button),
          style: buttonStyleO,
          onPressed: () {
            // ボタンを押したら、ViewModel内のWidgetBのカウンターを加算表示するメソッドを実行
            SampleViewModelO.instanceO.countUpForWidgetBO();
          },
        ),
        // bodyのWidget全体(=WidgetAとB両方)を更新するボタン
        TextButton(
          child: Text("両方更新", style: Theme.of(context).textTheme.button),
          style: buttonStyleO,
          onPressed: () {
            // ボタンを押したら、ViewModel内のWidgetAとB両方のカウンターを加算表示するメソッドを実行
            SampleViewModelO.instanceO.countUpForBothO();
          },
        ),
      ],
    );
  }
}

// body部分のウィジェット
class BodyWidgetO extends StatefulWidget {
  const BodyWidgetO({Key? key}) : super(key: key);

  @override
  State<BodyWidgetO> createState() => _BodyWidgetOState();
}

class _BodyWidgetOState extends State<BodyWidgetO> {

  // bodyのWidget全体(=WidgetAとBの両方)を再描画するメソッドを定義
  void rebuildBothWidgetsO(){
    setState(() {});
  }

  @override
  void initState() {
    // ViewModel内に、上記で定義した再描画メソッドを渡す
    SampleViewModelO.instanceO.setRebuildMethodBothO(rebuildBothWidgetsO);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          BodyWidgetAO(),
          BodyWidgetBO(),
        ],
      ),
    );
  }
}

// WidgetAのクラス
class BodyWidgetAO extends StatefulWidget {
  const BodyWidgetAO({Key? key}) : super(key: key);
  @override
  _BodyWidgetAOState createState() => _BodyWidgetAOState();
}

class _BodyWidgetAOState extends State<BodyWidgetAO> {

  // WidgetAを再描画するメソッドを定義
  void rebuildWidgetAO(){
    setState(() {});
  }

  @override
  void initState() {
    // ViewModel内に、上記で定義した再描画メソッドを渡す
    SampleViewModelO.instanceO.setRebuildMethodAO(rebuildWidgetAO);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    debugPrint("WidgetAを描画");
    // ViewModel内のカウンター変数を参照して表示
    int displayNumberO = SampleViewModelO.instanceO.countNumberO;
    return Text("WidgetA\nCount\n$displayNumberO", textAlign: TextAlign.center);
  }
}

// WidgetBのクラス
class BodyWidgetBO extends StatefulWidget {
  const BodyWidgetBO({Key? key}) : super(key: key);
  @override
  _BodyWidgetBOState createState() => _BodyWidgetBOState();
}

class _BodyWidgetBOState extends State<BodyWidgetBO> {

  // WidgetBを再描画するメソッドを定義
  void rebuildWidgetBO(){
    setState(() {});
  }

  @override
  void initState() {
    // ViewModel内に、上記で定義した再描画メソッドを渡す
    SampleViewModelO.instanceO.setRebuildMethodBO(rebuildWidgetBO);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    debugPrint("WidgetBを描画");
    // ViewModel内のカウンター変数を参照して表示
    int displayNumberO = SampleViewModelO.instanceO.countNumberO;
    return Text("WidgetB\nCount\n$displayNumberO", textAlign: TextAlign.center);
  }
}

/// ViewModel層のクラス ///////////////////////////////////////////////////
class SampleViewModelO {

  // インスタンスを1つしか生成しないようにするため
  // シングルトン化する
  SampleViewModelO._();
  static final instanceO = SampleViewModelO._();

  // Model層のインスタンスを定義
  SampleModelO modelO = SampleModelO();

  // カウンター変数を定義(初期値は0)
  int countNumberO = 0;

  // View層から受け取った再描画メソッドを代入するFunction型プロパティ
  late Function rebuildWidgetAFromVMO;
  late Function rebuildWidgetBFromVMO;
  late Function rebuildBothFromVMO;

  // WidgetAを再描画するメソッドをViewModel内のプロパティに代入
  void setRebuildMethodAO(Function methodO) {
    rebuildWidgetAFromVMO = methodO;
  }

  // WidgetBを再描画するメソッドをViewModel内のプロパティに代入
  void setRebuildMethodBO(Function methodO) {
    rebuildWidgetBFromVMO = methodO;
  }

  // bodyのWidget全体(=WidgetAとB両方)を再描画するメソッドをViewModel内のプロパティに代入
  void setRebuildMethodBothO(Function methodO) {
    rebuildBothFromVMO = methodO;
  }

  void countUpForWidgetAO() {
    // カウンター変数をModel層の計算メソッドに渡し、計算結果を返り値で取得
    int calculatedNumberO = modelO.countUpO(countNumberO);
    countNumberO = calculatedNumberO;
    // WidgetAの再描画メソッドを実行
    rebuildWidgetAFromVMO();
  }

  void countUpForWidgetBO() {
    // カウンター変数をModel層の計算メソッドに渡し、計算結果を返り値で取得
    int calculatedNumberO = modelO.countUpO(countNumberO);
    countNumberO = calculatedNumberO;
    // WidgetBの再描画メソッドを実行
    rebuildWidgetBFromVMO();
  }

  void countUpForBothO() {
    // カウンター変数をModel層の計算メソッドに渡し、計算結果を返り値で取得
    int calculatedNumberO = modelO.countUpO(countNumberO);
    countNumberO = calculatedNumberO;
    // bodyのWidget全体(=WidgetAとBの両方)の再描画メソッドを実行
    rebuildBothFromVMO();
  }

}

/// Model層のクラス ///////////////////////////////////////////////////
class SampleModelO {

  // カウントアップ計算を実行し、結果をViewModel層に返す
  int countUpO(int countNumberO) {
    countNumberO = countNumberO + 1;
    return countNumberO;
  }

}

 

実行したときのイメージを再掲します。

 

 

特定のWidgetのみ更新されることが確認できるよう、WidgetAとWidgetBには、あえて同じカウンター変数を参照させています。

 

そのため、WidgetAだけを更新したときは、WidgetBの更新は止まり、その後、WidgetBも更新すると、WidgetAの値まで一気に追いつく挙動になります(AとBが逆の場合も同様です)。

 

また、両方を更新するボタンを押すと、親WidgetであるBodyWidgetOが再描画されるので、WidgetAとBの両方が更新されます。

 

詳細な説明はコード中にも記載していますが、主な要点は以下のとおりです。

 

  • 各Stateful Widget内で、setStateを実行するメソッドを定義(setStateを実行するだけのFunction型のメソッドを定義)する(104〜106行目、139〜141行目、169〜171行目)
  • View層の各Widgetは、ViewModel層のクラス(SampleViewModelO)内の同じメソッド・プロパティにアクセス(取得・更新)できる必要があるため、SampleViewModelOは、インスタンスを1つしか生成できないよう、シングルトン化しておく(194〜195行目)
  • 各Stateful WidgetのinitState内で、上記で定義したメソッドを、ViewModel内のメソッドに、引数で渡す(111、146、176行目)
  • ViewModel内のメソッドでは、引数で渡されたFunction型のプロパティを、ViewModel内で定義したFunction型のプロパティに代入し、ViewModel内から、View層の各Widgetに紐付いたsetStateを利用できるようにする(209〜221行目)

 

シングルトンを導入する部分は、こちらの記事が大変参考になりました。ありがとうございます!

 

 

なお、本来、Model層は、データベースやストレージへのアクセス処理を書き、単純な計算レベルは、ViewModel層で処理することも多いと思われますが、今回はケースを単純化するため、データベースやストレージへのアクセスは想定せず、単純な計算のみをModel層で実行する形としました。

 

また、今回はViewModel層のクラスとModel層のクラスが1つずつのため、Model層はシングルトン化していませんが、複数のViewModel層から、同一のModel層を参照する場合は、Model層もシングルトン化する必要があります。

 

Provider、Riverpodと比較した感想

Providerでは、notifyListenersメソッドで再描画を通知しますが、この通知がView層のどの部分にヒットするのか、分かりづらいと感じていました。

 

特に、複数のViewから同じViewModelを参照していると、notifyListenersで想定外のViewが更新されることもあり、影響範囲の確認が大変でした(自分の使い方が下手なだけなのでしょうけどw)。

 

また、Riverpodは、「state」というプロパティを更新したときに、再描画が通知されるので、ピンポイントで更新できるメリットは大きいのですが、stateという名称で統一されているので、どの変数を更新しているのか、分かりづらいと感じていました(もっと上手い方法を知らないだけかもしれませんがw)。

 

モデルクラスを導入してcopyWithで更新すれば、どの変数を更新したか分かりやすくはなりますが、ピンポイントで更新される分、該当の変数がコード内に散在していると、更新箇所を検索するのに手間がかかります

 

一方、今回検証した方法では、ピンポイントでの再描画はできないものの、再描画対象のWidget(クラス)が明確なので、コードを書いていて分かりやすい(再描画の影響範囲が分かりやすい)、というメリットを感じました。

 

ただ、留意点として、上位のWidgetを再描画した場合は、クラスを分けていたとしても、下位のWidgetは全て再描画されます。

 

そのため、再描画の範囲を限定したい場合は、再描画対象のWidgetを、できるだけWidgetツリーの下位のクラスに配置する必要があります。

 

 

追加サンプルコード(AppBarも更新する場合)

追加のサンプルコードとして、カウンターの値が10に達すると、AppBarのボタン色が変わる(暗くなる)、というケースも作成したので、ご参考に掲載します。

 

これも「main.dart」にコピペするだけで確認いただけます。

 

前回のサンプルから追加・修正した部分だけ、「↓追加・修正部分」というコメントを付しています。

 

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "setState and MVVM Demo",
      theme: ThemeData.light(),
      home: SampleScreenO(),
    );
  }
}

/// View層 ///////////////////////////////////////////////////
class SampleScreenO extends StatefulWidget {
  @override
  _SampleScreenOState createState() => _SampleScreenOState();
}

class _SampleScreenOState extends State<SampleScreenO> {

  @override
  Widget build(BuildContext context) {

    debugPrint("全体を描画");

    return  Scaffold(
      appBar: AppBar(title: AppBarWidgetO()),
      body: BodyWidgetO(),
    );
  }
}

class AppBarWidgetO extends StatefulWidget {
  const AppBarWidgetO({Key? key}) : super(key: key);
  @override
  _AppBarWidgetOState createState() => _AppBarWidgetOState();
}

class _AppBarWidgetOState extends State<AppBarWidgetO> {

  // ↓追加・修正部分
  // AppBarのウィジェットを再描画するメソッドを定義
  void rebuildAppBarO(){
    setState(() {});
  }

  // ↓追加・修正部分
  @override
  void initState() {
    // ViewModel内に、上記で定義した再描画メソッドを渡す
    SampleViewModelO.instanceO.setRebuildAppBarO(rebuildAppBarO);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {

    debugPrint("AppBarを描画");

    // ↓追加・修正部分
    // カウンターが10以上になったらテキストボタンのスタイルを変更
    ButtonStyle buttonStyleO =
    (SampleViewModelO.instanceO.countNumberO >= 10)
        ? ButtonStyle(backgroundColor: MaterialStateProperty.all<Color>(Theme.of(context).primaryColorDark))
        : ButtonStyle(backgroundColor: MaterialStateProperty.all<Color>(Theme.of(context).primaryColorLight));

    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        TextButton(
          child: Text("Aを更新", style: Theme.of(context).textTheme.button),
          style: buttonStyleO,
          onPressed: () {
            SampleViewModelO.instanceO.countUpForWidgetAO();
          },
        ),
        TextButton(
          child: Text("Bを更新", style: Theme.of(context).textTheme.button),
          style: buttonStyleO,
          onPressed: () {
            SampleViewModelO.instanceO.countUpForWidgetBO();
          },
        ),
        TextButton(
          child: Text("両方更新", style: Theme.of(context).textTheme.button),
          style: buttonStyleO,
          onPressed: () {
            SampleViewModelO.instanceO.countUpForBothO();
          },
        ),
      ],
    );
  }
}

class BodyWidgetO extends StatefulWidget {
  const BodyWidgetO({Key? key}) : super(key: key);

  @override
  State<BodyWidgetO> createState() => _BodyWidgetOState();
}

class _BodyWidgetOState extends State<BodyWidgetO> {

  void rebuildBothWidgetsO(){
    setState(() {});
  }

  @override
  void initState() {
    SampleViewModelO.instanceO.setRebuildMethodBothO(rebuildBothWidgetsO);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          BodyWidgetAO(),
          BodyWidgetBO(),
        ],
      ),
    );
  }
}

class BodyWidgetAO extends StatefulWidget {
  const BodyWidgetAO({Key? key}) : super(key: key);
  @override
  _BodyWidgetAOState createState() => _BodyWidgetAOState();
}

class _BodyWidgetAOState extends State<BodyWidgetAO> {

  void rebuildWidgetAO(){
    setState(() {});
  }

  @override
  void initState() {
    SampleViewModelO.instanceO.setRebuildMethodAO(rebuildWidgetAO);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {

    debugPrint("WidgetAを描画");

    int displayNumberO = SampleViewModelO.instanceO.countNumberO;
    return Text("WidgetA\nCount\n$displayNumberO", textAlign: TextAlign.center);
  }
}

class BodyWidgetBO extends StatefulWidget {
  const BodyWidgetBO({Key? key}) : super(key: key);
  @override
  _BodyWidgetBOState createState() => _BodyWidgetBOState();
}

class _BodyWidgetBOState extends State<BodyWidgetBO> {

  void rebuildWidgetBO(){
    setState(() {});
  }

  @override
  void initState() {
    SampleViewModelO.instanceO.setRebuildMethodBO(rebuildWidgetBO);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {

    debugPrint("WidgetBを描画");

    int displayNumberO = SampleViewModelO.instanceO.countNumberO;
    return Text("WidgetB\nCount\n$displayNumberO", textAlign: TextAlign.center);
  }
}

/// ViewModel層のクラス ///////////////////////////////////////////////////
class SampleViewModelO {

  SampleViewModelO._();
  static final instanceO = SampleViewModelO._();

  SampleModelO modelO = SampleModelO();

  int countNumberO = 0;

  late Function rebuildWidgetAFromVMO;
  late Function rebuildWidgetBFromVMO;
  late Function rebuildBothFromVMO;
  // ↓追加・修正部分
  late Function rebuildAppBarFromVMO;

  void setRebuildMethodAO(Function methodO) {
    rebuildWidgetAFromVMO = methodO;
  }

  void setRebuildMethodBO(Function methodO) {
    rebuildWidgetBFromVMO = methodO;
  }

  void setRebuildMethodBothO(Function methodO) {
    rebuildBothFromVMO = methodO;
  }

  // ↓追加・修正部分
  // AppBarを再描画するメソッドをViewModel内のプロパティに代入
  void setRebuildAppBarO(Function() methodO) {
    rebuildAppBarFromVMO = methodO;
  }

  void countUpForWidgetAO() {
    int calculatedNumberO = modelO.countUpO(countNumberO);
    countNumberO = calculatedNumberO;
    rebuildWidgetAFromVMO();
    // ↓追加・修正部分
    // カウンターが10になったらAppBarを再描画するメソッドを実行
    if (countNumberO == 10) rebuildAppBarFromVMO();
  }

  void countUpForWidgetBO() {
    int calculatedNumberO = modelO.countUpO(countNumberO);
    countNumberO = calculatedNumberO;
    rebuildWidgetBFromVMO();
    // ↓追加・修正部分
    if (countNumberO == 10) rebuildAppBarFromVMO();
  }

  void countUpForBothO() {
    int calculatedNumberO = modelO.countUpO(countNumberO);
    countNumberO = calculatedNumberO;
    rebuildBothFromVMO();
    // ↓追加・修正部分
    if (countNumberO == 10) rebuildAppBarFromVMO();
  }

}

/// Model層のクラス ///////////////////////////////////////////////////
class SampleModelO {
  int countUpO(int countNumberO) {
    countNumberO = countNumberO + 1;
    return countNumberO;
  }

}

 

その他補足(Widgetのクラスをどの程度分けるか)

今回、検証した方法では、再描画の範囲を限定したい場合、Widgetを別クラスに分ける必要があり、Stateful Widgetの数が多くなります。

 

しかし、大規模なアプリでなければ、Widgetのクラス分けは大まかで良く、多少、クラス内に再描画不要なWidgetが含まれていても問題ないと思われます。

 

実際、クラス分けを細かくした場合と、大まかにした場合とで再描画速度の違いを比べましたが、Flutterの処理速度は早いので、速度差は体感できませんでした

 

こちらの記事でも、大変分かりやすく解説くださっており、再描画によるコスト(負荷)はさほど重要ではなく、むしろシッカリconstをつけておくことの方が重要とされています(大変勉強になりました!)。

 

 

最後に

今回はシンプルなコードだったので、MVVMを用いることで、他クラスからのsetStateによる再描画を実現できましたが、複雑化すると不具合も出そうなので、引き続き検証したいと思います。

 

どなたかのご参考になれば幸いです。

 

最後までお読みいただき、ありがとうございました。

 

個人アプリ開発で役立ったもの

おすすめの学習教材

超初心者向けでオススメな元Udemyの講座/

 

\Gitの基礎について無料で学べる/

 

おすすめの学習書籍

実用的image_pickerに関してかなり助けられた/

 

Dartの基礎文法を素早くインプットできる/


Dart入門 – Dartの要点をつかむためのクイックツアー

 

おすすめのソフトウェア

安くて高機能。アプリの独自ドメイン・紹介サイト構築に最適/

 

\アイコン作成・画面設計・クラウド保存…何でもできて超必須

 

おすすめのハードウェア

\リーズナブルな価格で検証端末を確保できる/

 

\目線の高さを調節しやすく、疲れにくい

 

\キータッチが超静音で心地よい/

 

おすすめのサポートアイテム

\部屋の中を仕切って、集中できる開発環境を作れる/

 

\部屋の中でも大き過ぎず、長時間座っても疲れない

 

\バグと格闘した後の肩こりを解消してくれる/

コメント

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