前言
各位好,最近气候炎热,还请多多补充水分
会想要试着做做看框架,原因有以下几个:
- 由于是一人团队,随着代码越来越多的情况下,每隔一段时间回去看就会需要花一些时间才能看懂
- 临时要加功能,会导致原本的代码越来越杂乱
因此便开始寻找是否有比较有效的框架,最终我选择了MVVM,并且加上provider,来让我在区隔代码上更游刃有余。
由于自认本人能力不太足,因此这则文章单纯分享我自己想的MVVM架构,在更大型的专案下可能会有不足
那就来看看我的代码,看看他是怎么实现的吧!一样在最后会附上我的原始码。
代码解释
main.dart
flutter
void main() async {
WidgetsFlutterBinding.ensureInitialized();
InitializeViewModel initializeViewModel = InitializeViewModel();
await initializeViewModel.init();
runApp(ChangeNotifierProvider(
create: (_) => initializeViewModel,
child: const MaterialApp(
home: HomePage(),
)));
}
首先我会在main.dart的部分,先把通用的viewModel先注入,有一些比较常用到的package function都会放在里面。
InitializeViewModel.dart
flutter
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:package_info_plus/package_info_plus.dart';
class InitializeViewModel extends ChangeNotifier {
String? version = "loading";
final storage = const FlutterSecureStorage();
init() async {
version = await initPackageInfo();
notifyListeners();
}
/// 读取版本号
Future<String> initPackageInfo() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
return packageInfo.version;
}
/// 取得key的值
Future<String?> getStorageValue(String key) async {
return await storage.read(key: key);
}
/// 设置给定 [key] 的存储值为指定的 [value]。
///
/// 该方法使用 [storage] 对象将 [value] 写入存储。
/// 它返回一个 [Future],在值成功写入后完成。
Future<void> setStorageValue(
{required String key, required String value}) async {
await storage.write(key: key, value: value);
return;
}
/// --------------------读取目前语系--------------------
Locale getLocale() {
switch (PlatformDispatcher.instance.locale.countryCode.toString()) {
case "CN":
return const Locale('zh', 'CN');
case "US":
return const Locale('en', 'US');
case "ID":
return const Locale('id');
default:
return const Locale('zh', 'TW');
}
}
}
例如我使用的flutter_secure_storage 或是package_info_plus都会在这先初始化。
读取、储存使用者资料、变更语系的function都会放在里面
在其他地方想要读取储存在手机里的资料时,就可以直接调用initializeViewModel的function,这样做的用意是为了减少重复建立新的FlutterSecureStorage以及重复的代码。
主页则是使用flutter中的范例代码:点击增加数字。
home.dart
先来看看建立变数的部分
flutter
InitializeViewModel initializeViewModel = InitializeViewModel();
HomeViewModel homeViewModel = HomeViewModel();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
initializeViewModel = context.read<InitializeViewModel>();
});
}
我会在widgets都渲染以后再取得provider中的资料,使其可以正确的取得例如version之类的变数。
flutter
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => homeViewModel,
child: Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text("首页"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Consumer(builder: (context, HomeViewModel viewModel, child) {
return Text(
'${viewModel.counter}',
);
})
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: homeViewModel.incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
));
}
在这段代码中,我只修改了
- 在scaffold前面用ChangeNotifierProvider包起来
- 将counter用Consumer包起来
- 将按钮的onPressed更改成HomeViewModel里的function
其实就是正常的provider使用方法😂
HomeViewModel
flutter
class HomeViewModel extends ChangeNotifier {
Home home = Home(counter: 0);
num get counter => home.counter;
void incrementCounter() {
home.counter++;
notifyListeners();
}
}
而HomeViewModel则是把原本放在statefulWidget里的function全数移动到这里,这样可以实现逻辑与view分开的情况,并且也可以使用测试工具单独测试每一个function。
Model
flutter
class Home {
num counter;
Home({this.counter = 0});
}
由于是演示,因此model里的变数只有一个counter
资料夹结构如下:
结语
对我来说这种MVVM已经可以适应我的大部分工作环境,在每次客户突然乱加需求又乱取消时,也能够很轻松地做更动,并不会遇到移除某个部分却造成整个代码无法运行的状况。
虽然这种MVVM架构可能有些阉割,但也能够做到利用测试工具去做测试的情境了。
本人文笔不太好,若有更好的排版建议也请告诉我,我会改进的。
也谢谢各位看到这里,给你一个爱心🫰。