Flutter MVVM架构实现插件mvvm_plus的使用

发布于 1周前 作者 songsunli 来自 Flutter

Flutter MVVM架构实现插件mvvm_plus的使用

简介

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

mvvm plus logo

YouTube 视频介绍

Tiny API

MVVM+ 扩展了现有的Flutter类并引入了以下方法:

  • Model 继承自 ChangeNotifier 并添加了 getlistenTo 方法。
  • ViewWidget 继承自 StatefulWidget/State 并添加了 getlistenTo 方法。
  • ViewModel 继承自 Model 并添加了 buildView 方法。
  • PropertyValueNotifier 的别名,因此没有额外的方法。

Model-View-View Model (MVVM)

在MVVM中,UI被组织成一个称为View的对象。与View关联的业务逻辑被组织成一个称为ViewModel的对象。跨越两个或多个ViewModel的业务逻辑被组织成一个或多个Model。

mvvm flow

创建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 类有 initStatedispose 方法,用于初始化和清理工作:

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

支持 FutureStream,当它们解析时,会通知监听器:

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

1 回复

更多关于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:定义CounterView Widget。
  • main.dart:应用入口,使用Provider连接ViewModel和View。

运行应用

确保所有文件都已正确创建和配置后,运行flutter run即可在模拟器或设备上查看效果。

这个示例展示了如何使用mvvm_plus插件在Flutter中实现MVVM架构。当然,根据实际需求,你可能需要扩展ViewModel的功能,并在View中进行相应的调整。

回到顶部