Flutter MVVM 实用框架

基于Provider实现MVVM框架,常用的方式是 ViewModel 继承 ChangeNotifier ,再通过 ChangeNotifierProvider 提供给子Widget,ViewModel数据刷新通过调用 notifyListeners() 来通知Widget进行刷新,Widget 通过 Provider.of 、Consumer、Selector 来监听数据变化重新 build 更新UI。这种方式存在的问题有:

  • ViewModel数据刷新需要每次调用 notifyListeners()容易被遗漏
  • notifyListeners()作用在整个ViewModel,不方便进行局部UI刷新控制
  • Selector 虽然可以控制局部刷新,但需要需要自定义 shouldRebuild 要去了解Provider原理
  • 缺少 ViewModel 和 Widget 生命周期的管理

ViewModelProvider 在兼容现有功能基础刷,实现最小改动、不需要每次调用notifyListeners()、支持局部刷新UI和生命周期管理的框架

先给出源码后续有空再详细介绍 view_model_provider

局部刷新控制

1. 通过ValueNotifier创建可观察对象

class ViewModel extends ChangeNotifier {
  final value1 = ValueNotifier(0);
  final value2 = ValueNotifier(0);
}

2. 通过 ValueListenableBuilder 监听数据变化刷新

ValueListenableBuilder(
  valueListenable: viewModel.value1,
  builder: (context, value, child) {
    debugPrint("ValueListenableBuilder $value");
    return Text("ValueListenableBuilder $value");
    },
)

列表刷新控制

1. 通过 ListNotifier 创建可观察对象

class ViewModel extends ChangeNotifier {
  final list = ListNotifier<String>([]);
}

2. 通过 ListListenableBuilder 监听数据变化刷新

ListListenableBuilder(
  valueListenable: viewModel.list,
  builder: (context, value, child) {
    debugPrint("ValueListenableBuilder $value");
    return Text("ValueListenableBuilder $value");
    },
)

实现生命周期管理

LifecycleWidget,提供Widget生命周期监听,开放了以下回调接口可进行初始化和解绑操作

  • create,可以监听一个数据变化
  • initState,Widget initState 回调
  • initFrame,Widget 第一帧绘制完成调用
  • deactivate,Widget deactivate 回调
  • dispose,Widget dispose 回调
  • didUpdateWidget,Widget didUpdateWidget 回调
  • didChangeDependencies,Widget didChangeDependencies 回调

ViewModelProvider

创建ViewModel 提供给子Widget使用,开放了以下回调接口可进行初始化和解绑操作

  • initViewModel,ViewModel首次初始化 Widget initState 期间执行
  • bindViewModel,ViewModel 首次绑定 Widget ,方法在Widget build 期间执行
  • disposeViewModel,ViewModel 销毁,Widget dispose 时执行
/// [ViewModelProvider] 创建ViewModel
class ProviderExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ViewModelProvider<ViewModel>(
      create: (_) => ViewModel(),
      initViewModel: (context, viewModel) {
        debugPrint("ProviderBuilderExample initViewModel $viewModel");
      },
      bindViewModel: (context, viewModel) {
        debugPrint("ProviderBuilderExample bindViewModel $viewModel");
      },
      disposeViewModel: (context, viewModel) {
        debugPrint("ProviderBuilderExample disposeViewModel $viewModel");
      },
      builder: (context, viewModel, child) {
        debugPrint("ProviderBuilderExample builder $viewModel");
        return ViewModelWidget(viewModel);
      },
    );
  }
}

另外还可以通过继承ViewModelProviderWidget来创建ViewModel

/// 继承 [ViewModelProviderWidget] 创建ViewModel
class ProviderWidgetExample extends ViewModelProviderWidget<ViewModel> {
  ProviderWidgetExample() : super();

  @override
  ViewModel create(BuildContext context) => ViewModel();

  @override
  void initViewModel(BuildContext context, ViewModel viewModel) {
    debugPrint("ProviderWidgetExample initViewModel $viewModel");
  }

  @override
  void bindViewModel(BuildContext context, ViewModel viewModel) {
    debugPrint("ProviderWidgetExample bindViewModel $viewModel");
  }

  @override
  Widget buildChild(BuildContext context, ViewModel viewModel, Widget child) {
    debugPrint("ProviderWidgetExample build $viewModel");
    return ViewModelWidget(viewModel);
  }
}

ViewModel嵌套处理

ViewModel 嵌套 ViewModel 管理子 ViewModel ,提供了两种方式,一种需要手动调用刷新,另一种通过ValueNotifier包装替换ViewModel不需要手动刷新,同ViewModelProvider一样也有相关的抽象类提供继承支持。

class ParentViewModel extends ChangeNotifier {
  final valueViewModel = ValueNotifier(ChildViewModel());
  var childViewModel = ChildViewModel();

  void valueNotifier() {
    valueViewModel.value = ChildViewModel();
  }

  void notifyListenerChild() {
    childViewModel = ChildViewModel();
    notifyListeners();
  }
}

class ChildViewModel extends ChangeNotifier {
  final value = ValueNotifier(0);

  addValue() {
    value.value++;
  }
}

1 通过 ViewModelProvider 创建父ViewModel

class ChildProviderExapmle extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ViewModelProvider<ParentViewModel>(
      create: (context) => ParentViewModel(),
      builder: (context, viewModel, child) {
        return Scaffold(
          body: Container(
            child: Column(
              children: [
                ValueViewModelProviderExample(),
                ChildViewModelProviderExample(),
                ElevatedButton(
                  onPressed: () => viewModel.valueNotifier(),
                  child: Text("valueNotifier"),
                ),
                ElevatedButton(
                  onPressed: () => viewModel.notifyListenerChild(),
                  child: Text("notifyListenerChild"),
                ),
              ],
            ),
          ),
        );
      },
    );
  }
}

2 创建子ViewModelProvider

2-1 ChildViewModelProvider

需要手动刷新通常用于列表刷新Item区域,在ViewModelProvider已有回调基础上添加了

  • changeViewModel ,在子 ViewModel 被替换后可重新执行绑定流程
/// [ChildViewModelProvider] 获取子 ViewModel 例子
class ChildViewModelProviderExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChildViewModelProvider<ParentViewModel, ChildViewModel>(
      create: (_, parent) => parent.childViewModel,
      changeViewModel: (context, parent, viewModel, oldViewModel) {
        debugPrint(
            "ChildViewModelProvider changeViewModel $viewModel, $oldViewModel");
      },
      builder: (context, parent, viewModel, child) {
        debugPrint("ChildViewModelProvider builder $viewModel");
        return Row(
          children: [
            ValueListenableBuilder(
              valueListenable: viewModel.value,
              builder: (context, value, child) => Text("${viewModel.value}"),
            ),
            ElevatedButton(
              onPressed: () => viewModel.addValue(),
              child: Text("addValue"),
            )
          ],
        );
      },
    );
  }
}

2-2 ValueViewModelProvider

作用和回调与 ChildViewModelProvider一样,接收数据类型为 ValueListenable<ChangeNotifier>

/// [ValueViewModelProvider] 获取子 ViewModel 例子
class ValueViewModelProviderExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ValueViewModelProvider<ParentViewModel, ChildViewModel>(
      create: (_, parent) => parent.valueViewModel,
      changeViewModel: (context, parent, viewModel, oldViewModel) {
        debugPrint(
            "ValueViewModelProvider changeViewModel $viewModel, $oldViewModel");
      },
      builder: (context, parent, viewModel, child) {
        debugPrint("ValueViewModelProvider builder $viewModel");
        return Row(
          children: [
            ValueListenableBuilder(
              valueListenable: viewModel.value,
              builder: (context, value, child) => Text("${viewModel.value}"),
            ),
            ElevatedButton(
              onPressed: () => viewModel.addValue(),
              child: Text("addValue"),
            )
          ],
        );
      },
    );
  }

获取ViewModel

1 扩展函数

通过context.viewModel<ViewModel>() 可以快速取出ViewModelProvider ChildViewModelProviderValueViewModelProvider的ViewModel,这个方法在Widget build期间使用,如果要在initStatus期间使用可以直接使用Provider提供的扩展context.read<ViewModel>()

2 ViewModelBuilder

用于取出ViewModelProvider提供的ViewModel

ValueListenableBuilder

ValueListenableBuilder 只能监听当额数据刷新,同时监听多个数据刷新可采用ValueTuple2WidgetBuilderValueListenableTuple7BuilderValueListenableListBuilder

  /// 外部传入 ViewModel,可采用[ValueListenableBuilder]系列监听数据变化
  Widget _buildValueListenable(ViewModel viewModel) {
    return Column(
      children: [
        /// 监听单个数据变化
        ValueListenableBuilder(
            valueListenable: viewModel.value1,
            builder: (context, value, child) {
              debugPrint("ValueListenableBuilder $value");
              return Text("ValueListenableBuilder $value");
            }),
        /// 监听多个数据变化,继承自
        ValueListenableListBuilder(
          valueListenables: [
            viewModel.value1,
            viewModel.value2,
          ],
          builder: (context, value, child) {
            debugPrint(
                "ValueListenableListBuilder ${value.first}, ${value.last}");
            return Text(
                "ValueListenableListBuilder ${value.first}, ${value.last}");
          },
        ),
        /// 监听多个数据变化,继承自[ValueListenableListBuilder]可指定泛型
        ValueListenableTuple2Builder(
          valueListenables: Tuple2(viewModel.value1, viewModel.value2),
          builder: (context, value, child) {
            debugPrint(
                "ValueListenableTuple2Builder ${value.item1}, ${value.item2}");
            return Text(
                "ValueListenableTuple2Builder ${value.item1}, ${value.item2}");
          },
        ),
      ],
    );
  }

ViewModelValueBuilder

ViewModelBuilder 和 ValueListenableBuilder 组合,用于获取 ViewModel 和管理Widget刷新区域。

提供过个实现 ViewModelValueListBuilderViewModelValueTuple2BuilderViewModelValueTuple7WidgetBuilder可同时监听多个ViewMode参数变化来刷新Widget

  /// 不通过外部传入 ViewModel,可采用[ViewModelValueBuilder]系列获取 ViewModel 并监听数据变化
  Widget _buildViewModelValue() {
    return Column(
      children: [
        ViewModelValueBuilder(
          valueListenable: (ViewModel viewModel) => viewModel.value1,
          builder: (context, viewModel, value, child) {
            debugPrint("ViewModelValueBuilder $value");
            return Text("ViewModelValueBuilder $value");
          },
        ),
        ViewModelValueListBuilder(
          valueListenables: (ViewModel viewModel) => [
            viewModel.value1,
            viewModel.value2,
          ],
          builder: (context, viewModel, value, child) {
            debugPrint(
                "ViewModelValueListBuilder ${value.first}, ${value.last}");
            return Text(
                "ViewModelValueListBuilder ${value.first}, ${value.last}");
          },
        ),
        ViewModelValueTuple2Builder(
          valueListenables: (ViewModel viewModel) =>
              Tuple2(viewModel.value1, viewModel.value2),
          builder: (context, viewModel, value, child) {
            debugPrint(
                "ViewModelValueTuple2Builder ${value.item1}, ${value.item2}");
            return Text(
                "ViewModelValueTuple2Builder ${value.item1}, ${value.item2}");
          },
        ),
      ],
    );
  }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容