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

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

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

原始码在此

相关推荐
Xの哲學3 小时前
Linux NAPI 架构详解
linux·网络·算法·架构·边缘计算
朱嘉鼎4 小时前
CPU的发展历程、架构与指令
架构
IT技术分享社区4 小时前
架构入门系列:如何选择适合项目的架构模式
架构
报错小能手7 小时前
项目——基于C/S架构的预约系统平台 (1)
开发语言·c++·笔记·学习·架构
想学全栈的菜鸟阿董7 小时前
LangGraph智能体架构核心概念
网络·架构
消失的旧时光-19438 小时前
iFlutter --> Flutter 开发者 的 IntelliJ IDEA / Android Studio 插件
flutter·android studio·intellij-idea
北京青翼科技10 小时前
【PCIE716-159】基于PCIe总线架构的双通道射频收发处理平台(100%国产)
架构
雨夜之寂10 小时前
第一章-第二节-Cursor IDE与MCP集成.md
java·后端·架构
马拉萨的春天12 小时前
block的样式有哪些?如果copy的话分别会有啥样式
flutter·性能优化·ios的block
Rattenking13 小时前
【CSS】---- 图形函数详解
笔记·学习·flutter