Flutter MVVM架构实现插件mvvm_plus的使用
Flutter MVVM架构实现插件mvvm_plus的使用
简介
mvvm_plus 是一个轻量级的Flutter MVVM模式实现,它通过全局注册表(类似于GetIt)或继承小部件(类似于Provider)来共享状态。MVVM+ 扩展了现有的Flutter类,并引入了三个方法:get、listenTo 和 buildView。

YouTube 视频介绍
Tiny API
MVVM+ 扩展了现有的Flutter类并引入了以下方法:
- Model 继承自 
ChangeNotifier并添加了get和listenTo方法。 - ViewWidget 继承自 
StatefulWidget/State并添加了get和listenTo方法。 - ViewModel 继承自 
Model并添加了buildView方法。 - Property 是 
ValueNotifier的别名,因此没有额外的方法。 
Model-View-View Model (MVVM)
在MVVM中,UI被组织成一个称为View的对象。与View关联的业务逻辑被组织成一个称为ViewModel的对象。跨越两个或多个ViewModel的业务逻辑被组织成一个或多个Model。

创建View和ViewModel
创建ViewModel
class MyWidgetViewModel extends ViewModel {
  String someText;
}
创建View
class MyWidget extends ViewWidget<MyWidgetViewModel> {
  MyWidget({super.key}) : super(builder: () => MyWidgetViewModel());
  @override
  Widget build(BuildContext context) {
    return Text(viewModel.someText);
  }
}
Rebuilding a ViewWidget
你可以显式调用 buildView 或者使用 addListener(buildView) 来绑定ViewModel到ViewWidget:
late final counter = ValueNotifier<int>(0)..addListener(buildView);
// 或者使用 createProperty 方便地添加监听器
late final counter = createProperty<int>(0);
初始化和销毁
ViewModel 类有 initState 和 dispose 方法,用于初始化和清理工作:
class MyWidgetViewModel extends ViewModel {
  late final streamCounter = createStreamProperty<int>(Stream.value(0));
  @override
  void dispose() {
    streamCounter.dispose();
    super.dispose();
  }
}
注册ViewModel实例
要使ViewModel全局可用,可以使用 location: Location.registry:
class MyOtherWidget extends ViewWidget<MyOtherWidgetViewModel> {
  MyOtherWidget(super.key) : super(
    location: Location.registry,
    builder: () => MyOtherWidgetViewModel());
}
获取注册的ViewModel实例:
final otherViewModel = get<MyOtherWidgetViewModel>();
将ViewModel添加到Widget树
使用 location: Location.tree 将ViewModel添加到Widget树:
class MyOtherWidget extends ViewWidget<MyOtherWidgetViewModel> {
  MyOtherWidget(super.key) : super(
    location: Location.tree,
    builder: () => MyOtherWidgetViewModel());
}
从上下文中获取ViewModel:
final otherViewModel = get<MyOtherWidgetViewModel>(context: context);
Models
Model类是ViewModel的超类,具有类似的功能。MVVM+ 使用 bilocator 包来管理Model:
Bilocator<MyModel>(
  builder: () => MyModel(),
  child: MyWidget(),
);
监听其他Widget的ViewModel
使用 listenTo 方法监听其他Widget的ViewModel:
final someText = listenTo<MyOtherWidgetViewModel>().someText;
FutureProperty 和 StreamProperty
支持 Future 和 Stream,当它们解析时,会通知监听器:
late final futureCounter = createFutureProperty<int>(setNumberSlowly(count));
late final streamCounter = createStreamProperty<int>(Stream.value(0));
示例代码
以下是完整的示例代码,展示了如何使用 mvvm_plus 实现计数器应用:
import 'dart:async';
import 'package:bilocator/bilocator.dart';
import 'package:flutter/material.dart';
import 'package:mvvm_plus/mvvm_plus.dart';
void main() => runApp(myApp());
Widget myApp() =>
    const MaterialApp(debugShowCheckedModeBanner: false, home: Home());
/// Counter model that extends [Model]. (Observers listen to class.)
class CounterModel extends Model {
  int count = 0;
  void incrementCount() {
    count++;
    notifyListeners();
  }
}
/// Counter model that does not extend [Model]. (Observers listen to member values.)
class Counter {
  late final count = Property<int>(0);
}
class Home extends StatelessWidget {
  const Home({super.key});
  @override
  Widget build(BuildContext context) {
    return Bilocators(
        key: const ValueKey('Bilocators'),
        delegates: [
          BilocatorDelegate<Counter>(builder: () => Counter()),
          BilocatorDelegate<CounterModel>(builder: () => CounterModel()),
        ],
        child: Bilocator(
            location: Location.tree,
            builder: () => CounterModel(),
            child: Bilocator(
              location: Location.tree,
              builder: () => Counter(),
              child: const _GridOfCounters(),
            )));
  }
}
class _GridOfCounters extends StatelessWidget {
  const _GridOfCounters();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Center(
            child:
                Column(mainAxisAlignment: MainAxisAlignment.center, children: [
      const Spacer(),
      Row(
        children: [
          const Expanded(child: StatefulAndStateWidget()),
          Expanded(child: MyViewWidget()),
        ],
      ),
      const Spacer(),
      Row(
        children: [
          Expanded(child: PropertyWidget()),
          Expanded(child: GetListenToWidget()),
        ],
      ),
      const Spacer(),
      Row(
        children: [
          Expanded(child: ModelWidget()),
          Expanded(child: MixinWidget()),
        ],
      ),
      const Spacer(),
      Row(
        children: [
          Expanded(child: ContextOfWidget()),
          Expanded(child: GetListenToContextWidget()),
        ],
      ),
      const Spacer(),
      Row(
        children: [
          Expanded(child: FutureWidget()),
          Expanded(child: StreamWidget()),
        ],
      ),
      const Spacer(),
    ])));
  }
}
class StatefulAndStateWidget extends StatefulWidget {
  const StatefulAndStateWidget({super.key});
  @override
  State<StatefulAndStateWidget> createState() => _StatefulAndStateWidgetState();
}
class _StatefulAndStateWidgetState extends State<StatefulAndStateWidget> {
  int count = 0;
  incrementCount() {
    setState(() {
      count++;
    });
  }
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const Text('StatefulWidget/State'),
        Text('$count'),
        const SizedBox(height: 10),
        Fab(onPressed: incrementCount),
      ],
    );
  }
}
class MyViewWidget extends ViewWidget<ViewWidgetViewModel> {
  MyViewWidget({super.key}) : super(builder: () => ViewWidgetViewModel());
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const Text('ViewWidget/ViewModel'),
        Text('${viewModel.count}'),
        const SizedBox(height: 10),
        Fab(onPressed: viewModel.incrementCount),
      ],
    );
  }
}
class ViewWidgetViewModel extends ViewModel {
  int count = 0;
  incrementCount() {
    count++;
    buildView();
  }
}
class PropertyWidget extends ViewWidget<PropertyWidgetViewModel> {
  PropertyWidget({super.key}) : super(builder: () => PropertyWidgetViewModel());
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const Text('Property'),
        Text('${viewModel.count.value}'),
        const SizedBox(height: 10),
        Fab(onPressed: () => viewModel.count.value++),
      ],
    );
  }
}
class PropertyWidgetViewModel extends ViewModel {
  late final count = createProperty<int>(0);
}
class GetListenToWidget extends StatelessViewWidget {
  GetListenToWidget({super.key});
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const Text('get/listenTo Property'),
        Text('${listenTo(notifier: get<Counter>().count).value}'),
        const SizedBox(height: 10),
        Fab(onPressed: () => get<Counter>().count.value++),
      ],
    );
  }
}
class ModelWidget extends StatelessViewWidget {
  ModelWidget({super.key});
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const Text('get/listenTo Model'),
        Text('${listenTo<CounterModel>().count}'),
        const SizedBox(height: 10),
        Fab(onPressed: get<CounterModel>().incrementCount),
      ],
    );
  }
}
class MixinWidget extends ViewWidget<MixinWidgetViewModel> {
  MixinWidget({super.key}) : super(builder: () => MixinWidgetViewModel());
  @override
  MixinWidgetState createState() => MixinWidgetState();
  late final color = getState<MixinWidgetState>().color;
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const Text('Mixin'),
        Text('${viewModel.count.value}',
            style: TextStyle(color: getState<MixinWidgetState>().color)),
        const SizedBox(height: 10),
        Fab(onPressed: () => viewModel.count.value++),
      ],
    );
  }
}
class MixinWidgetViewModel extends ViewModel {
  late final count = createProperty<int>(0);
}
mixin ColorMixin {
  final color = Colors.red;
}
class MixinWidgetState extends ViewState<MixinWidgetViewModel> with ColorMixin {}
class ContextOfWidget extends StatelessViewWidget {
  ContextOfWidget({super.key});
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const Text('Context.of'),
        Text('${context.of<CounterModel>().count}'),
        const SizedBox(height: 10),
        Fab(onPressed: () => context.of<CounterModel>().incrementCount()),
      ],
    );
  }
}
class GetListenToContextWidget extends StatelessViewWidget {
  GetListenToContextWidget({super.key});
  @override
  Widget build(BuildContext context) {
    final countProperty = get<Counter>(context: context).count;
    return Column(
      children: [
        const Text('get/listenTo(context)'),
        Text('${listenTo(notifier: countProperty).value}'),
        const SizedBox(height: 10),
        Fab(onPressed: () => get<Counter>(context: context).count.value++),
      ],
    );
  }
}
Future<int> setNumberSlowly(int number) async =>
    Future.delayed(const Duration(milliseconds: 350), () => number);
class FutureWidget extends ViewWidget<FutureWidgetViewModel> {
  FutureWidget({super.key}) : super(builder: () => FutureWidgetViewModel());
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const Text('Future'),
        Text(viewModel.futureCounter.hasData
            ? '${viewModel.futureCounter.data}'
            : 'Loading...'),
        const SizedBox(height: 10),
        Fab(
            onPressed: () =>
                viewModel.futureCounter.value = setNumberSlowly(++viewModel.count)),
      ],
    );
  }
}
class FutureWidgetViewModel extends ViewModel {
  int count = 0;
  late final futureCounter = createFutureProperty<int>(setNumberSlowly(count));
}
int streamCount = 0;
Stream<int> addFiveSlowly() async* {
  int i = streamCount;
  streamCount += 5;
  for (; i <= streamCount; i++) {
    await Future.delayed(const Duration(milliseconds: 350));
    yield i;
  }
}
class StreamWidget extends ViewWidget<StreamWidgetViewModel> {
  StreamWidget({super.key})
      : super(
          location: Location.registry,
          builder: () => StreamWidgetViewModel(),
        );
  @override
  Widget build(BuildContext context) {
    final streamCounter = get<StreamWidgetViewModel>().streamCounter;
    return Column(
      children: [
        const Text('Stream'),
        Text(streamCounter.hasData ? '${streamCounter.data}' : 'Loading...'),
        const SizedBox(height: 10),
        Fab(onPressed: () => streamCounter.value = addFiveSlowly()),
      ],
    );
  }
}
class StreamWidgetViewModel extends ViewModel {
  late final streamCounter = createStreamProperty<int>(Stream.value(0));
}
class Fab extends StatelessWidget {
  const Fab({super.key, required this.onPressed});
  final VoidCallback onPressed;
  @override
  Widget build(BuildContext context) {
    return TextButton(
      onPressed: onPressed,
      style: TextButton.styleFrom(
          backgroundColor: Colors.blue, shape: const CircleBorder()),
      child: const Icon(Icons.add, color: Colors.white),
    );
  }
}
以上代码展示了如何使用 mvvm_plus 插件构建一个简单的计数器应用,涵盖了从基本的ViewModel和View创建到复杂的属性监听和异步数据处理的各个方面。希望这些内容能帮助你更好地理解和使用 mvvm_plus 插件。
更多关于Flutter MVVM架构实现插件mvvm_plus的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter MVVM架构实现插件mvvm_plus的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是一个关于如何在Flutter项目中使用mvvm_plus插件来实现MVVM架构的示例代码。mvvm_plus是一个用于Flutter的MVVM架构实现插件,它可以帮助开发者更好地组织代码,提高项目的可维护性。
步骤 1: 添加依赖
首先,你需要在pubspec.yaml文件中添加mvvm_plus的依赖:
dependencies:
  flutter:
    sdk: flutter
  mvvm_plus: ^最新版本号  # 请替换为实际的最新版本号
然后运行flutter pub get来安装依赖。
步骤 2: 创建ViewModel
接下来,你需要创建一个ViewModel类。ViewModel通常包含业务逻辑和数据管理。
import 'package:mvvm_plus/mvvm_plus.dart';
import 'package:flutter/material.dart';
class CounterViewModel extends BaseViewModel {
  int _count = 0;
  int get count => _count;
  void increment() {
    _count++;
    notifyListeners();  // 通知视图更新
  }
}
步骤 3: 创建View(Widget)
然后,你需要创建一个Widget来作为视图层。在这个例子中,我们将创建一个简单的计数器应用。
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'counter_view_model.dart';  // 导入你的ViewModel
class CounterView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final CounterViewModel model = Provider.of<CounterViewModel>(context);
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter MVVM with mvvm_plus'),
      ),
      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),
      ),
    );
  }
}
步骤 4: 在应用中使用Provider
为了将ViewModel与View连接起来,你需要使用Provider包。在main.dart文件中进行如下设置:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'counter_view.dart';
import 'counter_view_model.dart';
void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => CounterViewModel()),
      ],
      child: MyApp(),
    ),
  );
}
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter MVVM Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: CounterView(),
    );
  }
}
完整代码结构
pubspec.yaml:添加mvvm_plus依赖。counter_view_model.dart:定义CounterViewModel。counter_view.dart:定义CounterViewWidget。main.dart:应用入口,使用Provider连接ViewModel和View。
运行应用
确保所有文件都已正确创建和配置后,运行flutter run即可在模拟器或设备上查看效果。
这个示例展示了如何使用mvvm_plus插件在Flutter中实现MVVM架构。当然,根据实际需求,你可能需要扩展ViewModel的功能,并在View中进行相应的调整。
        
      
            
            
            
