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

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

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

原始码在此

相关推荐
Python私教4 小时前
macOS 中搭建 Flutter 开发环境
flutter·macos
爱上语文5 小时前
Springboot三层架构
java·开发语言·spring boot·spring·架构
ღ᭄ꦿ࿐Never say never꧂7 小时前
微服务架构中的负载均衡与服务注册中心(Nacos)
java·spring boot·后端·spring cloud·微服务·架构·负载均衡
CaritoB7 小时前
中台架构下的数据仓库与非结构化数据整合
数据仓库·架构
明似水8 小时前
掌握 Flutter 中的 `Overlay` 和 `OverlayEntry`:弹窗管理的艺术
javascript·flutter
canonical_entropy18 小时前
金蝶云苍穹的Extension与Nop平台的Delta的区别
后端·低代码·架构
Flutter社区19 小时前
使用 Flutter 3.19 更高效地开发
flutter·dart
沛沛老爹19 小时前
服务监控插件全览:提升微服务可观测性的利器
微服务·云原生·架构·datadog·influx·graphite
huaqianzkh20 小时前
了解华为云容器引擎(Cloud Container Engine)
云原生·架构·华为云
Kika写代码21 小时前
【基于轻量型架构的WEB开发】【章节作业】
前端·oracle·架构