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
:定义CounterView
Widget。main.dart
:应用入口,使用Provider
连接ViewModel和View。
运行应用
确保所有文件都已正确创建和配置后,运行flutter run
即可在模拟器或设备上查看效果。
这个示例展示了如何使用mvvm_plus
插件在Flutter中实现MVVM架构。当然,根据实际需求,你可能需要扩展ViewModel的功能,并在View中进行相应的调整。