分享我在flutter中使用的MVVM框架

前言


各位好,最近气候炎热,还请多多补充水分

会想要试着做做看框架,原因有以下几个:

  • 由于是一人团队,随着代码越来越多的情况下,每隔一段时间回去看就会需要花一些时间才能看懂
  • 临时要加功能,会导致原本的代码越来越杂乱

因此便开始寻找是否有比较有效的框架,最终我选择了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架构可能有些阉割,但也能够做到利用测试工具去做测试的情境了。

本人文笔不太好,若有更好的排版建议也请告诉我,我会改进的。

也谢谢各位看到这里,给你一个爱心🫰。

原始码在此

相关推荐
58沈剑7 小时前
80后聊架构:架构设计中两个重要指标,延时与吞吐量(Latency vs Throughput) | 架构师之路...
架构
君蓦8 小时前
Flutter 本地存储与数据库的使用和优化
flutter
想进大厂的小王10 小时前
项目架构介绍以及Spring cloud、redis、mq 等组件的基本认识
redis·分布式·后端·spring cloud·微服务·架构
阿伟*rui11 小时前
认识微服务,微服务的拆分,服务治理(nacos注册中心,远程调用)
微服务·架构·firefox
ZHOU西口11 小时前
微服务实战系列之玩转Docker(十八)
分布式·docker·云原生·架构·数据安全·etcd·rbac
deephub14 小时前
Tokenformer:基于参数标记化的高效可扩展Transformer架构
人工智能·python·深度学习·架构·transformer
架构师那点事儿15 小时前
golang 用unsafe 无所畏惧,但使用不得到会panic
架构·go·掘金技术征文
W Y17 小时前
【架构-37】Spark和Flink
架构·flink·spark
problc17 小时前
Flutter中文字体设置指南:打造个性化的应用体验
android·javascript·flutter