Flutter视图模型管理插件view_model_provider的使用

Flutter视图模型管理插件view_model_provider的使用

view_model_provider

A Flutter MVVM Framework.

Getting Started

基于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和生命周期管理的框架。

局部刷新控制

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("ListListenableBuilder $value");
    return Text("ListListenableBuilder $value");
  },
)

实现生命周期管理

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

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

ViewModelProvider

1. 嵌套使用

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

  • initViewModel,ViewModel首次初始化 Widget initState 期间执行
  • bindViewModel,ViewModel 首次绑定 Widget ,方法在Widget build 期间执行
  • disposeViewModel,ViewModel 销毁,Widget dispose 时执行
/// [ViewModelProvider] 创建ViewModel
class ProviderExample extends StatelessWidget {
  [@override](/user/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);
      },
    );
  }
}

2. 通过继承来创建ViewModel

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

  [@override](/user/override)
  ViewModel create(BuildContext context) => ViewModel();

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

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

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

3. 通过Mixin来创建ViewModel

/// 混入 [ViewModelProviderMixin] 创建ViewModel
/// 混入 [ViewModelProviderLifecycle] 监听ViewModel生命周期
/// 混入 [ViewModelProviderBuilder] 支持buildChild
class ProviderMixinExample extends SingleChildStatelessWidget
    with
        ViewModelProviderMixin<ViewModel>,
        ViewModelProviderLifecycle<ViewModel>,
        ViewModelProviderBuilder<ViewModel> {
  ProviderMixinExample() : super();

  [@override](/user/override)
  ViewModel create(BuildContext context) => ViewModel();

  [@override](/user/override)
  void initViewModel(BuildContext context, ViewModel viewModel) {
    debugPrint("ProviderMixinExample initViewModel $viewModel");
  }
  [@override](/user/override)
  void initFrame(BuildContext context, ViewModel viewModel) {
    debugPrint("ProviderMixinExample initFrame $viewModel");
    super.initFrame(context, viewModel);
  }

  [@override](/user/override)
  void bindViewModel(BuildContext context, ViewModel viewModel) {
    debugPrint("ProviderMixinExample bindViewModel $viewModel");
  }

  [@override](/user/override)
  Widget buildChild(BuildContext context, ViewModel viewModel, Widget? child) {
    debugPrint("ProviderMixinExample build $viewModel");
    return ViewModelWidget(viewModel);
  }

  [@override](/user/override)
  Widget buildWithChild(BuildContext context, Widget? child) {
    return buildProvider(context, child);
  }
}

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 ChildProviderExample extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return ViewModelProvider<ParentViewModel>(
      create: (context) => ParentViewModel(),
      builder: (context, viewModel, child) {
        return Scaffold(
          body: Container(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                ValueViewModelProviderExample(),
                ChildViewModelProviderExample(),
                ElevatedButton(
                  onPressed: () => viewModel.valueNotifier(),
                  child: Text("valueNotifier"),
                ),
                ElevatedButton(
                  onPressed: () => viewModel.notifyListenerChild(),
                  child: Text("notifyListenerChild"),
                ),
              ],
            ),
          ),
        );
      },
    );
  }
}

2. 创建子ViewModelProvider

2-1 ValueViewModelProvider

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

/// [ValueViewModelProvider] 获取子 ViewModel 例子
class ValueViewModelProviderExample extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return ValueViewModelProvider<ChildViewModel>(
      /// 使用viewModel扩展函数 只监听ParentViewModel变化
      create: (context) => context.viewModel<ParentViewModel>().valueViewModel,
      initViewModel: (context, viewModel) {
        debugPrint("ValueViewModelProvider initViewModel $viewModel");
      },
      bindViewModel: (context, viewModel) {
        debugPrint("ValueViewModelProvider bindViewModel $viewModel");
      },
      disposeViewModel: (context, viewModel) {
        debugPrint("ValueViewModelProvider disposeViewModel $viewModel");
      },
      changeViewModel: (context, viewModel, oldViewModel) {
        debugPrint(
            "ValueViewModelProvider changeViewModel $viewModel, $oldViewModel");
      },
      builder: (context, viewModel, child) {
        debugPrint("ValueViewModelProvider builder $viewModel");
        return Row(
          children: [
            ElevatedButton(
              onPressed: () => viewModel.addValue(),
              child: Text("addValue"),
            ),
            ValueListenableBuilder(
              valueListenable: viewModel.value,
              builder: (context, value, child) => Text("${viewModel.value}"),
            ),
          ],
        );
      },
    );
  }
}

2-2 ChildViewModelProvider

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

  • changeViewModel ,在子 ViewModel 被替换后可重新执行绑定流程
/// [ChildViewModelProvider] 获取子 ViewModel 例子
class ChildViewModelProviderExample extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return ChildViewModelProvider<ChildViewModel>(
      /// 使用watch扩展函数 监听ParentViewModel的notifyListeners
      create: (context) => context.watch<ParentViewModel>().childViewModel,
      initViewModel: (context, viewModel) {
        debugPrint("ChildViewModelProvider initViewModel $viewModel");
      },
      bindViewModel: (context, viewModel) {
        debugPrint("ChildViewModelProvider bindViewModel $viewModel");
      },
      disposeViewModel: (context, viewModel) {
        debugPrint("ChildViewModelProvider disposeViewModel $viewModel");
      },
      changeViewModel: (context, viewModel, oldViewModel) {
        debugPrint(
            "ChildViewModelProvider changeViewModel $viewModel, $oldViewModel");
      },
      builder: (context, viewModel, child) {
        debugPrint("ChildViewModelProvider builder $viewModel");
        return Column(
          children: [
            Row(
              children: [
                ElevatedButton(
                  onPressed: () => viewModel.addValue(),
                  child: Text("addValue"),
                ),
                ValueListenableBuilder(
                  valueListenable: viewModel.value,
                  builder: (context, value, child) =>
                      Text("${viewModel.value}"),
                ),
              ],
            ),
          ],
        );
      },
    );
  }
}

获取ViewModel

1. 扩展函数

通过 context.viewModel<ViewModel>() 可以快速取出 ViewModelProvider ChildViewModelProviderValueViewModelProvider 的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}");
        },
      ),
    ],
  );
}

完整示例Demo

import 'package:flutter/material.dart';

import 'child_provider_example.dart';
import 'list_example.dart';
import 'provider_example.dart';

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

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ViewModelExample(),
    );
  }
}

class ViewModelExample extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return SafeArea(
      top: true,
      child: Scaffold(
        body: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            ElevatedButton(
              onPressed: () {
                push(context, ProviderExample());
              },
              child: Text("BaseExample"),
            ),
            ElevatedButton(
              onPressed: () {
                push(context, ProviderWidgetExample());
              },
              child: Text("BaseExtendsExample"),
            ),
            ElevatedButton(
              onPressed: () {
                push(context, ProviderMixinExample());
              },
              child: Text("BaseMixinExample"),
            ),
            ElevatedButton(
              onPressed: () {
                push(context, ProviderLifecycleMixinExample());
              },
              child: Text("BaseLifecycleMixinExample"),
            ),
            ElevatedButton(
              onPressed: () {
                push(context, ChildProviderExample());
              },
              child: Text("ChildExample"),
            ),
            ElevatedButton(
              onPressed: () {
                push(context, ListProviderExapmle());
              },
              child: Text("ListExample"),
            ),
          ],
        ),
      ),
    );
  }

  push(BuildContext context, Widget widget) {
    Navigator.of(context).push(MaterialPageRoute(builder: (context) {
      return SafeArea(top: true, child: widget);
    }));
  }
}

更多关于Flutter视图模型管理插件view_model_provider的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter视图模型管理插件view_model_provider的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何在Flutter中使用view_model_provider插件来管理视图模型的示例代码。view_model_provider插件允许你以声明式的方式在Flutter应用中管理和共享视图模型(ViewModel),类似于在Android中使用的ViewModel概念。

首先,确保你的pubspec.yaml文件中已经添加了view_model_provider依赖:

dependencies:
  flutter:
    sdk: flutter
  view_model_provider: ^x.y.z  # 请替换为最新版本号

然后运行flutter pub get来安装依赖。

1. 创建一个ViewModel

首先,创建一个简单的ViewModel类。ViewModel通常用于持有和管理UI相关的数据逻辑。

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

class CounterViewModel extends ViewModel {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();  // 通知监听器数据已更改
  }
}

2. 创建一个Flutter Widget并使用ViewModelProvider

接下来,创建一个Flutter Widget,并使用ViewModelProvider来提供和管理CounterViewModel

import 'package:flutter/material.dart';
import 'package:view_model_provider/view_model_provider.dart';
import 'counter_view_model.dart';  // 假设你的ViewModel文件名为counter_view_model.dart

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: ViewModelProvider<CounterViewModel>(
        createViewModel: () => CounterViewModel(),
        builder: (context, model, child) {
          return Scaffold(
            appBar: AppBar(
              title: Text('Flutter Demo Home Page'),
            ),
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Text(
                    'You have pushed the button this many times:',
                  ),
                  Text(
                    '${model.count}',
                    style: Theme.of(context).textTheme.headline4,
                  ),
                ],
              ),
            ),
            floatingActionButton: FloatingActionButton(
              onPressed: model.increment,
              tooltip: 'Increment',
              child: Icon(Icons.add),
            ),
          );
        },
      ),
    );
  }
}

3. 运行应用

现在,你可以运行你的Flutter应用。你应该能看到一个带有按钮和计数器的简单页面。每次点击按钮时,计数器都会增加,并且UI会更新以反映最新的计数。

解释

  • CounterViewModel: 这是一个简单的ViewModel类,它持有一个整数计数器,并提供一个increment方法来增加计数。当计数改变时,它通过调用notifyListeners()来通知所有监听器。
  • ViewModelProvider: 这是一个Widget,它负责创建并提供ViewModel实例。它接受一个createViewModel函数来创建ViewModel,以及一个builder函数来构建子Widget树。builder函数接收ViewModel作为参数,这样你就可以在UI中使用它。
  • MyApp: 这是应用的主Widget,它使用ViewModelProvider来提供CounterViewModel,并构建一个简单的UI,包括一个显示计数的Text和一个增加计数的FloatingActionButton。

通过这种方式,你可以使用view_model_provider插件在Flutter应用中管理和共享ViewModel,使你的代码更加清晰和模块化。

回到顶部