分享我在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架构可能有些阉割,但也能够做到利用测试工具去做测试的情境了。

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

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

原始码在此

相关推荐
hstar952710 小时前
三十四、面向对象底层逻辑-SpringMVC九大组件之FlashMapManager接口设计哲学
java·spring·设计模式·架构
恋猫de小郭13 小时前
Flutter 多版本管理工具 Puro ,它和 FVM 有什么区别?
android·前端·flutter
颜颜颜yan_13 小时前
【HarmonyOS5】DevEco Studio 使用指南:代码阅读与编辑功能详解
架构·harmonyos
W说编程15 小时前
Linux与量子计算:面向未来的架构演进
linux·服务器·性能优化·架构·系统架构·量子计算
林鹿16 小时前
Dart: 串联多个数据流
后端·架构·dart
异常君16 小时前
Java 应用中构建 Elasticsearch 多层次缓存:提升查询效率的实战方案
java·elasticsearch·架构
wu~97016 小时前
计算机网络自定向下:第二章复习
服务器·网络·架构
快起来别睡了17 小时前
代理模式:送花风波
前端·javascript·架构
前端付豪17 小时前
汇丰银行技术架构揭秘:全球交易稳定背后的“微服务+容灾+零信任安全体系”
前端·后端·架构
brzhang18 小时前
Flutter 调用原生代码,看这篇就够了:从零教你搭起通信的桥
前端·后端·架构