Flutter MVVM架构插件pmvvm的使用

Flutter MVVM架构插件pmvvm的使用

简介

PMVVM 是一个基于 MVVM 模式的 Flutter 状态管理包,它底层使用了 Provider 和 Hooks。与 BloC 相比,PMVVM 不需要太多的样板代码,更加简洁易用。

该包借鉴了一些 Stacked 包的概念,但采用了更简单和干净的方法。

工作原理

PMVVM 的核心组件有三个:

  1. View:代表应用程序的 UI,不包含任何应用逻辑。ViewModel 通过发送通知来更新 View 的 UI。
  2. ViewModel:持有状态和事件,作为 Model 和 View 之间的桥梁。ViewModel 是平台无关的,可以轻松绑定到 Web、移动或桌面视图。
  3. Model:持有应用数据和业务逻辑,通常是一些简单的类。

使用场景

当你的小部件有自己的事件可以直接改变状态时,例如页面、帖子等,可以使用 PMVVM。

使用方法

1. 构建 ViewModel

class MyViewModel extends ViewModel {
  int counter = 0;

  // 可选
  @override
  void init() {
    // 在 MVVM 小部件的 initState 被调用后调用
  }

  // 可选
  @override
  void onBuild() {
    // 当视图的 `build` 方法被调用时的回调
  }

  void increase() {
    counter++;
    notifyListeners();
  }
}

2. 访问 ViewModel 中的 context

class MyViewModel extends ViewModel {
  @override
  void init() {
    var height = MediaQuery.of(context).size.height;
  }
}

3. 声明 MVVM

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

  @override
  Widget build(BuildContext context) {
    return MVVM<MyViewModel>(
      view: () => _MyView(),
      viewModel: MyViewModel(),
    );
  }
}

4. 构建 View

StatelessView

class _MyView extends StatelessView<MyViewModel> {
  const _MyView({Key key}) : super(key: key, reactive: true);

  @override
  Widget render(context, vmodel) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: <Widget>[
        Text(vmodel.counter.toString()),
        SizedBox(height: 24),
        RaisedButton(onPressed: vmodel.increase, child: Text('Increase')),
      ],
    );
  }
}

HookView

class _MyView extends HookView<MyViewModel> {
  const _MyView({Key key}) : super(key: key, reactive: true);

  @override
  Widget render(context, vmodel) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: <Widget>[
        Text(vmodel.counter.toString()),
        SizedBox(height: 24),
        RaisedButton(onPressed: vmodel.increase, child: Text('Increase')),
      ],
    );
  }
}

5. 使用 MVVM.builder

如果视图很简单,可以使用 MVVM.builder

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

  @override
  Widget build(BuildContext context) {
    return MVVM<MyViewModel>.builder(
      viewModel: viewModel,
      viewBuilder: (_, vm) {
        return Text(
          vm.counter.toString(),
          textDirection: TextDirection.ltr,
        );
      },
    );
  }
}

高级用法

不可变性和可观察性

PMVVM 支持不可变性和可观察性,通过 Observable 类可以观察变量的变化。

1. 包装变量

/// 'MyCounter' 是变量的别名,这在日志记录中很有用
final counter = Observable<int>('MyCounter');

// 或者

final counter = Observable.initialized(0, 'MyCounter');
final counter = 0.observable('MyCounter');

2. 应用动作

counter.setValue(counter.value + 1, action: 'INCREASE');

3. 消费 Observable

/// 确保在使用 [value] 获取器之前已经初始化
/// 通过检查 [counter.hasValue] 或使用 [counter.valueOrNull] 或 [counter.valueOrDefault]

counter.value;

counter.stream.listen((value) { });

4. 观察和取消观察

class CounterPageVM extends ViewModel {
  final counter = 0.reactive('MyCounter');

  @override
  void init() {
    observe([counter]);
  }

  void increase() {
    counter.setValue(counter.value + 1, action: 'INCREASE');
  }
}

Observable API

Getter / Method Description
value 返回当前值
valueOrNull 返回当前值或未初始化时为 null
prevValue 返回历史中的前一个值(如果存在)
hasValue 检查是否已初始化
history 返回所有应用于 observable 的操作历史
stream 返回 observable 值的流
valueOrDefault 返回当前值或默认值(未初始化时)
setValue 更改当前值的方法
log 允许更改日志形状的方法
clearHistory 删除所有存储的操作
resetLogCallback 清除自定义日志函数

模式

1. Naive 方法

使用级联符号初始化 ViewModel 的 widget 属性。这种方法的缺点是当 widget 的依赖项(属性)更新时,ViewModel 不会变得响应式。

class MyWidget extends StatelessWidget {
  const MyWidget({Key key, this.varName}) : super(key: key);

  final String varName;

  @override
  Widget build(BuildContext context) {
    return MVVM<MyViewModel>(
      view: () => _MyView(),
      viewModel: MyViewModel()..varName = varName,
    );
  }
}

2. Clean 方法

类似于 ReactJS,创建一个属性类,将所有 widget 属性保存在其中。

my_widget.props.dart

class MyWidgetProps {
  MyWidgetProps({required this.name});

  final String name;
}

my_widget.vm.dart

class MyWidgetVM extends ViewModel {
  late MyWidgetProps props;

  @override
  void init() {
    props = context.fetch<MyWidgetProps>();
  }
}

my_widget.view.dart

class MyWidget extends StatelessWidget {
  MyWidget({
    Key? key,
    required String name,
  })  : props = MyWidgetProps(name: name),
        super(key: key);

  final MyWidgetProps props;

  Widget build(context) {
    return Provider.value(
      value: props,
      child: MVVM<MyWidgetVM>(
        view: () => _MyWidgetView(),
        viewModel: MyWidgetVM(),
      ),
    );
  }
}

更多关于 PMVVM

  • 可以使用 context.fetch<T>(listen: true/false),等同于 Provider.of<T>(context)
  • 可以使用 PMVVMConfig 控制 enableLoggingtrackObservablesHistory
  • 如果不想让视图响应 ViewModel 的状态通知,可以在构造 StatelessViewHookView 时将 reactive 设置为 false
  • ViewModel 生命周期方法(所有方法都是可选的)
void init() {}
void onDependenciesChange() {}
void onBuild() {}
void onMount() {}
void onUnmount() {}
void onResume() {}
void onPause() {}
void onInactive() {}
void onDetach() {}

FAQ

  • 能否在生产环境中使用?
    • 当然可以!它是稳定的,准备好投入使用。
  • Stacked 和 PMVVM 有什么区别?
Stacked PMVVM
不能在 ViewModel 中访问 BuildContext 可以通过 Provider.of<T>(context)context.watch<T>()context.read<T>()context.select<T, R>(R cb(T value)) 访问 BuildContext
需要实现 Initialisable 接口来调用 initialise init 事件默认被调用,只需覆盖即可(可选)
ViewModel 中没有 build 方法 onBuild 方法默认在每次视图重建时被调用,可以覆盖以实现自己的逻辑(可选)
过度包装 provider,如 FutureViewModelStreamViewModel 不过度包装 provider 包,可以使用 StreamProvider/FutureProviderHooks,提供更大的灵活性

总结来说,PMVVM 更简单、更干净,没有过度包装,且 Idioms 更加清晰。

依赖

  • provider
  • flutter_hooks
  • tint

示例代码

import 'package:flutter/material.dart';
import 'package:example/pages/counter/counter_page.view.dart';

/// import 'package:example/pages/counter_with_observable/counter_page.view.dart';

void main() {
  runApp(MaterialApp(home: CounterPage()));
}

希望这些信息对你有所帮助!如果你有任何问题或需要进一步的帮助,请随时提问。


更多关于Flutter MVVM架构插件pmvvm的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter MVVM架构插件pmvvm的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,我可以为你提供一个关于如何在Flutter项目中使用pmvvm插件来实现MVVM架构的示例代码。pmvvm是一个帮助在Flutter中实现MVVM架构模式的插件。下面是一个基本的示例,展示如何使用pmvvm来组织你的Flutter应用。

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

dependencies:
  flutter:
    sdk: flutter
  pmvvm: ^最新版本号 # 请替换为当前最新版本号

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

接下来,我们创建一个简单的Flutter应用,使用pmvvm来实现MVVM架构。这个示例将包含一个简单的计数器应用。

1. 创建ViewModel

首先,我们创建一个CounterViewModel来处理业务逻辑。

import 'package:pmvvm/pmvvm.dart';

class CounterViewModel extends BaseViewModel {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners(); // 通知视图更新
  }
}

2. 创建View (UI)

接下来,我们创建一个简单的Flutter界面来显示和更新计数器。

import 'package:flutter/material.dart';
import 'package:pmvvm/pmvvm.dart';
import 'counter_view_model.dart'; // 导入我们之前创建的ViewModel

class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ViewModelBuilder<CounterViewModel>.reactive(
      viewModelBuilder: () => CounterViewModel(),
      builder: (context, model, child) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Flutter MVVM with pmvvm'),
          ),
          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. 运行应用

最后,在你的main.dart文件中,使用CounterPage作为根页面来运行应用。

import 'package:flutter/material.dart';
import 'counter_page.dart'; // 导入我们之前创建的View

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter MVVM Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: CounterPage(),
    );
  }
}

总结

以上代码展示了如何使用pmvvm插件在Flutter中实现一个简单的MVVM架构应用。通过分离视图(UI)和视图模型(业务逻辑),我们可以更容易地管理和维护代码,特别是在大型项目中。ViewModelBuilder组件负责将视图模型绑定到视图,并在视图模型数据变化时自动更新视图。

回到顶部