青鸾繁华录手游
326.05MB · 2025-09-15
认真对待每时、每刻每一件事,把握当下、立即去做。
移动应用开发领域的技术演进正持续推动着跨平台解决方案的创新。在 Android 与 iOS 等多平台并存的现状下,传统原生开发面临代码复用率低和开发效率瓶颈等核心挑战。Flutter 作为 Google 推出的现代化 UI 工具包,通过自绘引擎和响应式框架实现了真正的跨平台一致性,其"一次编写,处处运行"的理念已在全球范围内得到验证——根据往年 Dart 开发者调研,采用 Flutter 的企业项目平均缩短了40%左右的开发周期。本文基于 MVVM+Repository 架构模式,系统阐述 Flutter 在工程化实践中的解决方案。
这次公司在新项目技术再次选型的前景下,让我对 Flutter 做一次技术构架分享。为了把 Flutter 说清楚,如何去做架构企业级项目,项目架构中应该包含哪些技术点,我做了下面结构性的技术总结,前面部分我会针对技术、工具链生态做一个系统解析,最后一部分详细根据业务点来阐述 MVVM+Repository 架构。
特别地,本文方案融合了笔者在2022年主导公司的企业级移动应用重构经验(Native + KMM + React 架构),其中对状态管理、模块化解耦等关键问题的解决路径,均在本架构中得到延续与升级。通过完整的代码示例与架构图解进行解析。
当然,在互相学习过程中欢迎指出其中的不足和改进意见,后续有时间会对基础架构一些延续的东西我也会陆续补充进来。我们先看看基础项目结构的定义,有个大概了解再往下看。
# 项目目录结构定义pubassistant/├── android/ # Android 平台代码├── ios/ # iOS 平台代码├── assets/ # 静态资源│ ├── images/ # 图片资源│ ├── fonts/ # 字体文件│ └── json/ # 本地JSON文件├── lib/ # Flutter 源代码│ ├── generated/ # 资源管理生成器│ │ └── assets.dart # assets│ ├── src/│ │ ├── core/ # 核心层│ │ │ ├── constants/ # 常量│ │ │ │ ├── app_constants.dart # 应用常量│ │ │ │ ├── app_strings.dart # 字符串常量│ │ │ │ ├── app_layouts.dart # 布局尺寸常量│ │ │ │ └── app_colors.dart # 颜色常量│ │ │ ├── di/ # 依赖注入配置核心文件│ │ │ │ └── injector.dart # GetIt│ │ │ ├── routes/ # 路由配置│ │ │ │ ├── app_pages.dart # 页面路由表│ │ │ │ └── app_router.dart # 路由生成器│ │ │ ├── theme/ # 主题配置│ │ │ │ ├── app_theme.dart # 主题配置│ │ │ │ └── text_styles.dart # 文本样式规范│ │ │ ├── network/ # 网络层封装│ │ │ │ ├── dio_client.dart # Dio 实例配置│ │ │ │ ├── exceptions/ # 自定义异常类│ │ │ │ └── interceptors/ # 拦截器(日志、Token刷新) │ │ │ ├── database/ # 数据库层封装│ │ │ └── utils/ # 工具类│ │ │ └── storage_util.dart # 存储工具│ │ ├── features/ # 业务功能模块划分层│ │ │ ├── data/ # 数据层:聚焦数据获取与存储逻辑│ │ │ │ ├── models/ # 数据模型│ │ │ │ ├── repositories/ # 数据仓库│ │ │ │ └── services/ # 数据服务(API接口)│ │ │ ├── domain/ # 业务层:处理业务规则与逻辑流转,如数据验证、流程编排、领域模型转换│ │ │ │ ├── entities/ # 业务实体│ │ │ │ ├── repositories/ # 抽象仓库接口│ │ │ │ └── use_cases/ # 业务逻辑用例│ │ │ └── presentation/ # 表现层│ │ │ ├── pages/ # UI 页面│ │ │ ├── widgets/ # 模块内复用组件│ │ │ ├── view_models/ # 视图模型│ │ │ ├── router/ # 模块独立路由│ │ │ └── state/ # 状态管理│ │ └── config/ # 环境配置│ │ └── app_config.dart│ └── main.dart # 应用入口├── test/ # 测试目录├── scripts/ # 构建/部署脚本├── environments/ # 环境配置文件│ ├── dev.env│ ├── staging.env│ └── prod.env└── pubspec.yaml # 依赖管理
这里一般配置一个开发环境和一个生产环境就行了,目前我们公司涉及到大量客户演示,这里增加一个演示环境,总的来说按需配置。
├── environments/ # 环境配置文件│ ├── dev.env│ ├── staging.env│ └── prod.env
dev.env 配置详情示例:
API_BASE_URL=https://api.**dev.examp*le.comENV_NAME=DevelopmentENABLE_DEBUG_LOGS=true
dependencies: flutter_dotenv: ^5.2.1
配置文件路径:lib/src/config/env_loader.dart
// 创建配置加载器class EnvLoader { static Future<void> load() async { const env = String.fromEnvironment("ENV", defaultValue: 'dev'); await dotenv.load(fileName: 'environments/$env.env'); } static String get apiBaseUrl => dotenv.get('API_BASE_URL'); static String get envName => dotenv.get('ENV_NAME'); static bool get enableDebugLogs => dotenv.get('ENABLE_DEBUG_LOGS') == 'true';}
void main() async { // 初始化环境配置 await EnvLoader.load(); runApp(const MyApp());}
# 1. 命令启动开发环境flutter run --dart-define=ENV=dev # 2. 配置IDE运行参数# 在IDE的 "Run"->"Edit Configurations" 中: - 找到 Flutter 运行配置 - 在"Additional arguments"添加:--dart-define=ENV=dev
Android APK:
# 生产环境flutter build apk --dart-define=ENV=prod# 演示环境flutter build apk --dart-define=ENV=staging
iOS IPA:
命令行打包:
# 生产环境flutter build ipa --dart-define=ENV=prod --release# 演示环境flutter build ipa --dart-define=ENV=staging --release
Xcode 配置:
打开 ios.Runner.xcworkspace,选择 Target Build Settings,添加 DART_DEFINES 环境变量 DART_DEFINES=ENV=prod
。
Text(EnvLoader.envName)
├── assets/ # 静态资源│ ├── images/ # 图片资源│ ├── fonts/ # 字体文件│ └── json/ # 本地JSON文件
flutter: assets: - assets/images/ - assets/json/ fonts: - family: Rbt fonts: - asset: assets/fonts/Rbt-Framework.ttf
这里是自定义工具实现示例,其实我们可以直接使用通过资源代码生成工具实现自动生成的 generated/assets.dart
工具类实现文件。该机制本质上是通过元编程手段,将文件系统的资源组织结构转化为类型安全的编程接口,属于 Flutter 现代化开发工具链的典型实践,后面会具体介绍。
// lib/src/core/constants/assets_constants.dartclass AppAssets { static const String framework = 'assets/images/framework/home_head_image.jpg';}// 使用示例Image.asset(AppAssets.framework)
全局应用:
MaterialApp( theme: ThemeData( fontFamily: 'Rbt', // 使用声明的字体家族名 ),);
局部应用:
Text( '自定义字体', style: TextStyle( fontFamily: 'Rbt', fontWeight: FontWeight.bold, // 匹配配置的字重 ),);
推荐使用 json_serializable、json_annotation、build_runner 库,进行一个通用的封装,这部分会在后续框架项目中进行开源,欢迎 star。
在 Flutter 项目中,generated/assets.dart
是一个自动生成的文件,主要用于资源管理的代码化和开发效率优化。以下是其核心作用与生成逻辑:
1)资源路径的静态化访问
将 assets
目录下的资源(如图片、字体)转换为 Dart 常量,避免手动输入路径字符串,减少拼写错误。
// 示例:通过生成的常量访问图片Image.asset(Assets.images.logo); // 替代 Image.asset('assets/images/logo.png')
2)类型安全与智能提示
资源名称通过代码生成器映射为强类型属性,IDE 可提供自动补全,提升开发体验。
3)多分辨率资源适配
自动处理不同分辨率的资源文件(如 [email protected]
),生成统一的访问接口。
1)依赖插件
通常由 flutter_gen
或 flutter_generate_assets
等插件实现,这些插件基于 Dart 的 build_runner
工具链。
2)配置文件驱动
在 pubspec.yaml
中声明资源后,插件会监听文件变化并自动生成代码:
flutter: assets: - assets/images/
3)编译时生成
执行 flutter pub run build_runner build
命令触发生成,结果保存在 lib/generated/
目录下。
特性 | 手动管理 | 自动生成 (generated/assets.dart ) |
---|---|---|
路径准确性 | 易出错 | 100% 准确 |
重构友好性 | 需全局搜索替换 | 自动同步修改 |
多语言支持 | 需额外工具 | 可整合国际化资源 |
1)与国际化结合
通过注解生成多语言资源的访问代码,例如 Assets.translations.homeTitle
。
2)自定义资源类型
扩展支持 JSON、音频等非图片资源,生成对应的解析方法。
常用常量配置集合结构参考如下,当然我们在开发过程中应该根据具体实际情况进行增加和修改。
core/ # 核心层│ │ │ ├── constants/ # 常量│ │ │ │ ├── app_constants.dart # 应用常量│ │ │ │ ├── app_strings.dart # 字符串常量│ │ │ │ ├── app_layouts.dart # 布局尺寸常量│ │ │ │ └── app_colors.dart # 颜色常量
class AppConstants { // 应用基础信息 static const String appName = 'pubassistant'; static const String appVersion = '1.0.0.0'; static const int appBuildNumber = 1000;}
Theme 主题系统的核心文件,用于集中管理应用的视觉样式和文本风格。
功能:定义应用的整体视觉风格,包括颜色、组件样式、亮度模式等,通过 ThemeData
类实现统一管理。典型内容:
import 'package:flutter/material.dart';import 'text_styles.dart'; // 关联文本样式class AppTheme { // 明亮主题 static ThemeData lightTheme = ThemeData( colorScheme: ColorScheme.light( primary: Colors.blueAccent, secondary: Colors.green, ), appBarTheme: AppBarTheme( backgroundColor: Colors.blueAccent, titleTextStyle: TextStyles.headlineMedium, ), buttonTheme: ButtonThemeData( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), ), textTheme: TextTheme( displayLarge: TextStyles.displayLarge, // 引用文本样式 bodyMedium: TextStyles.bodyMedium, ), ); // 黑暗主题 static ThemeData darkTheme = ThemeData.dark().copyWith( colorScheme: ColorScheme.dark( primary: Colors.indigo, secondary: Colors.tealAccent, ), );}
关键点:
ColorScheme
定义主色、辅色等配色方案;appBarTheme
、buttonTheme
等定制组件样式;text_styles.dart
中的文本样式保持一致性;功能:集中管理所有文本样式(如标题、正文、按钮文字等),避免散落在各处重复定义。典型内容:
class TextStyles { // 标题样式 static const TextStyle displayLarge = TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: Colors.black87, ); // 正文字体 static const TextStyle bodyMedium = TextStyle( fontSize: 16, height: 1.5, color: Color(0xFF424242), ); // 按钮文字 static const TextStyle buttonLabel = TextStyle( fontSize: 14, fontWeight: FontWeight.w600, letterSpacing: 0.5 );}
关键点:
const
定义静态样式提升性能;pubspec.yaml
配置)。在 main.dart
中应用主题:
MaterialApp( theme: AppTheme.lightTheme, // 使用预定义主题 darkTheme: AppTheme.darkTheme, home: MyApp(),);
在组件中调用文本样式:
Text('Hello', style: TextStyles.displayLarge);
colors.dart
),这一点就是常量配置集中提到的;copyWith
方法局部覆盖主题;dio 是一个强大的 HTTP 网络请求库,支持全局配置、Restful API、FormData、拦截器、 请求取消、Cookie 管理、文件上传/下载、超时、自定义适配器、转换器等。
项目里通过封装设计 http_exception、http_interceptor、http_options、http_request 类,适应于大型项目的开发应用。
推荐 shared_preferences 方案,项目里进行了一层应用封装。
数据库封装采用了分层架构设计,主要由三个部分组成:基础提供者类(DbBaseProvider)、数据库助手类(DbHelper)和具体业务提供者(UserDbProvider)。
结构清晰:分层明确,职责分离;
复用性强:基础功能封装在父类,子类只需关注业务表结构;
性能优化:
扩展性好:新增表只需继承 DbBaseProvider;
线程安全:所有操作都是异步的;
注意事项:
最佳实践建议:
总结:封装遵循了基本的软件设计原则,提供了清晰的扩展接口。主要改进空间在于错误处理、类型安全和版本管理方面。通过引入模型层和 ORM 框架可以进一步提升代码质量和开发效率。
InheritedWidget 提供了在 Widget 树中从上往下共享数据的能力;
全局事件总线(Event Bus)实现跨页面、跨组件的通信,进行数据传递与交互。具体的实现封装结合项目;
ChangeNotifier(provider) + ValueNotifier;
BLoC(推荐 bloc + flutter_bloc + Cubit);
在 Flutter 项目中,go_router 和 auto_route 都是优秀的第三方路由库,但它们的定位和特性有所不同。以下是两者的对比分析及选型建议:
go_router:
auto_route:
选择 go_router 的场景:
选择 auto_route 的场景:
对于大型项目,可结合两者优势:
建议根据团队技术栈和项目需求(如是否跨平台、是否需要强类型支持)做出选择。
以下是 Flutter MVVM + Repository 架构的业务示例解析。
├── features/ # 业务功能模块划分层│ ├── data/ # 数据层:聚焦数据获取与存储逻辑│ │ ├── models/ # 数据模型│ │ ├── repositories/ # 数据仓库│ │ └── services/ # 数据服务(API接口)│ ├── domain/ # 业务层:处理业务规则与逻辑流转,如数据验证、流程编排、领域模型转换│ │ ├── entities/ # 业务实体│ │ ├── repositories/ # 抽象仓库接口│ │ └── use_cases/ # 业务逻辑用例│ └── presentation/ # 表现层│ ├── pages/ # UI 页面│ ├── widgets/ # 模块内复用组件│ ├── view_models/ # 视图模型│ ├── router/ # 模块独立路由└── └── state/ # 状态管理
Model 层:
data/models
:数据模型(DTO)domain/entities
:业务实体data/services
:数据源实现(SQLite/API)ViewModel 层:调用 UseCase、处理业务逻辑、管理 UI 状态。
presentation/viewmodels
View 层:纯 UI 展示、通过 Consumer 监听 ViewModel。
presentation/pages
Repository 层:
domain/repositories
:抽象接口。data/repositories
:具体实现。在 Flutter 功能优先结构中融入 ViewModel 层时,核心区别如下:
在现有结构中,presentation/state/
目录即 ViewModel 层的天然位置,用于管理 UI 状态和业务逻辑协调。ViewModel 在 MVVM 架构中主要承担以下角色:
状态管理:负责管理应用的状态,包括 UI 状态(如加载中、错误)和业务数据状态(如用户信息)。
业务逻辑处理:封装业务逻辑,包括数据获取、转换和处理。
数据层交互:通过 UseCase 或 Repository 与数据层交互,获取或存储数据。
典型实现方式:
class UserViewModel with ChangeNotifier { final GetUserByIdUseCase _getUserByIdUseCase; UserEntity? _userEntity; bool _isLoading = false; String? _error; UserEntity? get user => _userEntity; bool get isLoading => _isLoading; String? get error => _error; UserViewModel(this._getUserByIdUseCase); Future<void> fetchUser(String userId) async { _isLoading = true; notifyListeners(); try { _userEntity = await _getUserByIdUseCase.execute(userId); _error = null; } catch(e) { _error = e.toString(); } finally { _isLoading = false; notifyListeners(); } }}
此处 presentation/state/
存放 ViewModel,通过 use_cases
调用领域逻辑。
职责分离,解决UI与业务逻辑耦合问题。
pages/
, widgets/
)state/
)repositories/
, services/
)可测试性提升,ViewModel 独立于 Widget 树,可直接进行单元测试。
test('UserViewModel should emit loading state', () { final vm = UserViewModel(mockUseCase); vm.fetchUser('123'); expect(vm.state, ViewState.isLoading);});
状态生命周期管理,自动处理页面销毁时的资源释放,避免内存泄漏。
跨组件状态共享,通过 Provider/Riverpod 实现多个 Widget 访问同一状态源。
逻辑臃肿,业务代码侵入 Widget,导致万行 StatefulWidget
地狱。
// 反例:业务逻辑混入UI层class LoginPage extends StatefulWidget { Future<void> _login() async { // API调用+状态管理+导航跳转 }}
测试困难,需启动完整 Widget 树测试基础逻辑。
状态分散,相同业务状态可能被重复实现于不同Widge。
层级交互规范,遵循单向依赖:外层→内层
View[Widget] -->|监听| ViewModelViewModel -->|调用| UseCaseUseCase -->|依赖抽象| RepositoryRepository -->|组合| DataSource
状态管理选型
ChangeNotifier
+ Provider
Riverpod
/Bloc
+ Freezed
模块化扩展,保持各功能模块内聚性
假设我们需要通过 API 获取用户数据,并进行业务逻辑处理(如数据验证、模型转换)。
目的:聚焦数据获取与存储逻辑,实现具体的数据获取逻辑(如网络请求、数据库操作)。
// data/models/user_model.dart// 数据模型:对应 API 返回的 JSON 结构(含序列化注解)@JsonSerializable()class UserModel { @JsonKey(name: 'user_id') final String id; final String username; final int age; UserModel({required this.id, required this.username, required this.age}); factory UserModel.fromJson(Map<String, dynamic> json) => _$UserModelFromJson(json);}
// data/services/user_api_service.dart// 数据服务:与 API 交互(具体实现)class UserApiService { final Dio dio; UserApiService(this.dio); Future<UserModel> fetchUser(String userId) async { final response = await dio.get('/users/$userId'); return UserModel.fromJson(response.data); }}
// data/repositories/user_repository_impl.dart// 仓库实现:将数据转换为业务实体(实现 domain 层的抽象接口)class UserRepositoryImpl implements UserRepository { final UserApiService apiService; UserRepositoryImpl(this.apiService); @override Future<UserEntity> getUserById(String userId) async { final userModel = await apiService.fetchUser(userId); return UserEntity( id: userModel.id, name: userModel.username, // 字段名转换(API username → 业务 name) age: userModel.age, ); }}
目的:处理业务规则与核心逻辑流转、抽象接口,如数据验证、流程编排、领域模型转换(与具体技术无关)。
// domain/entities/user_entity.dart// 业务实体:纯 Dart 对象,仅包含业务核心属性(无 JSON 注解)class UserEntity { final String id; final String name; final int age; UserEntity({required this.id, required this.name, required this.age}); // 业务逻辑方法(如年龄验证) bool isAdult() => age >= 18;}
// domain/repositories/user_repository.dart// 仓库抽象接口:定义业务需要的数据操作方法(不依赖具体实现)abstract class UserRepository { Future<UserEntity> getUserById(String userId);}
// domain/use_cases/user_id_usecase.dart// 业务用例:编排数据获取和业务逻辑(如验证)class GetUserByIdUseCase { final UserRepository repository; // 依赖抽象接口 GetUserByIdUseCase(this.repository); Future<UserEntity> execute(String userId) async { final user = await repository.getUserById(userId); if (!user.isAdult()) { throw Exception('User must be an adult'); // 业务规则验证 } return user; }}
final getIt = GetIt.instance;void setupDependencies() { setupApiDependencies(); setupRepositoryDependencies(); setupCaseDependencies(); setupViewModelDependencies();}void setupApiDependencies() { // 数据层 getIt.registerSingleton<UserApiService>(UserApiService(Dio()));}void setupRepositoryDependencies() { // 仓库层 getIt.registerSingleton<UserRepository>( UserRepositoryImpl(getIt<UserApiService>()) );}void setupCaseDependencies() { // 业务用例层 getIt.registerSingleton<GetUserByIdUseCase>( GetUserByIdUseCase(getIt<UserRepository>()) );}void setupViewModelDependencies() { // ViewModel(工厂模式,每次新建实例) getIt.registerFactory<UserViewModel>( () => UserViewModel(getIt<GetUserByIdUseCase>()) );}
状态管理采用 ChangeNotifier,统一处理成功/失败。
class UserViewModel with ChangeNotifier { final GetUserByIdUseCase _getUserByIdUseCase; UserEntity? _userEntity; bool _isLoading = false; String? _error; // 状态暴露给视图层 UserEntity? get user => _userEntity; bool get isLoading => _isLoading; String? get error => _error; UserViewModel(this._getUserByIdUseCase); Future<void> fetchUser(String userId) async { _isLoading = true; notifyListeners(); try { _userEntity = await _getUserByIdUseCase.execute(userId); _error = null; } catch(e) { _error = e.toString(); } finally { _isLoading = false; notifyListeners(); } }}
class HomePage extends StatefulWidget { const HomePage({super.key}); @override State<HomePage> createState() => _HomePageState();}class _HomePageState extends State<HomePage> { @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { final viewModel = Provider.of<UserViewModel>(context, listen: false); viewModel.fetchUser("1234"); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Home Page')), body: Consumer<UserViewModel>( builder: (context, viewModel, child) { if (viewModel.isLoading) { return const Center(child: CircularProgressIndicator()); } if (viewModel.error != null) { return Center(child: Text('Error: ${viewModel.error}')); } return ElevatedButton( onPressed: () => context.push('/detail', extra: {'id': '${viewModel.user?.id}'}), child: Text('Go to the Details page With id: ${viewModel.user?.name}') ); }, ) ); }}
⚠️ 在具体页面局部注册业务逻辑类(如 LoginViewModel
)。
class LoginPage extends StatelessWidget { @override Widget build(BuildContext context) { return ChangeNotifierProvider( create: (_) => LoginViewModel( loginUseCase: sl<LoginUseCase>(), ), child: _LoginView(), ); }}
⚠️ 我们应该只在 main.dart
全局注册基础服务(如 NetworkService
)。
void main() async { // 初始化环境配置 await EnvLoader.load(); setupDependencies(); runApp( MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => getIt<UserViewModel>()), ], child: const MyApp(), ) );}
presentation 层 → domain/use_cases → domain/repositories(接口) ↑data/services(API/Database) ← data/repositories(实现)
层面 | domain/ 业务层 | data/ 数据层 | 是否冗余? |
---|---|---|---|
模型 |
UserEntity (业务属性+逻辑方法) |
UserModel (纯数据映射) |
否,面向不同场景 |
仓库 | 接口(UserRepository ) |
实现(UserRepositoryImpl ) |
否,抽象与实现分离 |
关注点 | 业务规则(如年龄验证) | 技术细节(如 JSON 解析、网络请求) | 明确分工 |
? 架构效能对比
维度 | 无 Repository | 有 Repository |
---|---|---|
数据源切换 | 需修改 ViewModel | 仅调整 Repository 实现 |
测试成本 | 需启动完整网络环境 | Mock 单一接口即可 |
错误处理 | 分散在各 ViewModel | 集中处理 |
代码复用 | 相似逻辑需重复实现 | 跨模块共享数据策略 |
不重复设计:业务层定义 “做什么”(抽象接口、业务规则),数据层实现 “怎么做”(具体技术细节)。
优势:业务层可独立测试(无需依赖网络/数据库);数据源切换灵活(如从 API 切换为本地缓存只需修改 data/
层);符合依赖倒置原则(高层模块不依赖低层细节)。
当应用涉及多数据源协同(如实时API+本地缓存)时,Repository 的价值尤为突出。
Repository 是 MVVM 架构中数据层的统一管理者,通过抽象数据访问细节、标准化数据格式和集中化策略处理,显著提升代码的可维护性、扩展性和测试便利性。其设计本质符合“高内聚低耦合”的架构原则,是复杂 Flutter 项目推荐的实践模式。
在 Flutter 的 MVVM + Repository 架构中,Repository 层扮演着核心协调(数据中驱)角色,本质上是数据层的统一抽象网关。其核心价值体现在以下方面。
1)隔离数据源细节:Repository 作为数据访问层,将网络 API、本地数据库(如SQLite)、缓存(如 Hive)等数据源的具体实现与业务逻辑解耦。ViewModel 仅通过 Repository 提供的统一接口获取数据,无需关心数据来自 REST 请求还是本地存储。
abstract class UserRepository { Future<User> fetchUser(); // 统一接口}
2)数据转换与标准化:将原始数据(如 JSON)转换为领域模型(Domain Model),确保 ViewModel 接收的是可直接使用的业务实体(Entity),而非原始 API 响应。
User _mapToEntity(UserDto dto) { return User(id: dto.id, name: dto.username);}
3)多数据源协调器:智能组合远程与本地数据源,实现如「缓存优先」策略:
Future<User> fetchUser() async { if (localDataSource.hasData) { return localDataSource.getUser(); } else { final remoteUser = await api.getUser(); await localDataSource.cache(remoteUser); return remoteUser; }}
为何 MVVM 需要 Repository?
1)降低耦合性,打破 ViewModel 数据耦合:通过 Repository 模式,数据源变更(如切换 API 提供商)只需修改 Repository 内部实现,无需改动 ViewModel 或 UI 层代码。不加 Repository 时,ViewModel 直接对接 API 导致:
// 反例:ViewModel 直接调用APIclass ProfileVM { final ApiService _api; // 直接依赖具体实现 Future<void> loadData() => _api.getProfile();}
2)统一错误处理机制:Repository 可集中处理数据层异常(如网络超时/解析错误),避免 ViewModel 重复实现错误处理。
3)增强可测试性,测试效率倍增:ViewModel 测试只需 Mock Repository 接口,无需构建真实网络环境。可轻松替换为 Mock 实现,方便单元测试时模拟网络请求或数据库操作:
test('VM测试', () { when(mockRepo.getUser()).thenReturn(mockUser); expect(viewModel.user, mockUser);});
4)集中管理数据策略:在 Repository 内部实现缓存逻辑(如“先本地后网络”)、数据合并或错误重试等复杂策略,简化 ViewModel 的职责。
典型数据流:ViewModel 调用 Repository 方法 → Repository 从数据源获取数据 → 返回标准化模型 → ViewModel 更新状态并触发 UI 渲染。代码示例:
class UserViewModel { final UserRepository repository; Future<void> loadUser() async { final user = await repository.fetchUser(); // 通过Repository获取数据 // 更新状态... }}
错误处理桥梁:Repository 统一捕获数据源异常(如网络超时),转换为业务层可理解的错误类型,避免 ViewModel 直接处理底层异常。
UserRepository
):业务层仅依赖抽象,不关心具体实现(如 UserRepositoryImpl
)。UserRepositoryImpl
):数据层通过实现接口提供具体功能,但业务层无需直接引用它。// 抽象接口(业务层)abstract class UserRepository { ... }// 实现类(数据层)class UserRepositoryImpl implements UserRepository { ... }
在应用启动时,通过依赖注入框架(如 get_it
、injectable
)注册绑定关系:
// 示例:使用 get_it 注册依赖final getIt = GetIt.instance;void setupDependencies() { // 注册接口与实现的绑定关系 getIt.registerSingleton<UserRepository>(UserRepositoryImpl(apiService)); // 注册 UseCase,自动注入 UserRepositoryImpl 实例 getIt.registerFactory(() => GetUserByIdUseCase(getIt<UserRepository>()));}
当 GetUserByIdUseCase
被实例化时:
UserRepository
类型参数;UserRepositoryImpl
的实例。UserRepositoryImpl
实例是在程序运行时由 DI 容器动态解析的,而非编码时硬依赖。GetUserByIdUseCase
)仅依赖接口,更换数据源(如从 API 改为本地数据库)只需修改实现类,无需改动业务代码。void main() { // 初始化 DI 容器 setupDependencies(); // 获取 UseCase 实例(自动注入 UserRepositoryImpl) final useCase = getIt<GetUserByIdUseCase>(); useCase.execute("123"); // 实际调用 UserRepositoryImpl 的方法}
如何做到:通过 DI 容器在运行时将接口与实现绑定,业务层通过构造函数声明依赖接口,容器自动注入具体实现。
核心价值:实现层间解耦,提升代码可测试性和可维护性。
如需进一步了解具体 DI 框架的使用,可参考 get_it
或 injectable
的官方文档。
在 Flutter 中使用 MVVM + repository 架构时,确实不需要将所有 ViewModel 都在 main 函数中通过 MultiProvider 集中注册。以下是几种优化方案,可以避免 main 函数臃肿并实现按需注册,前面提到了一些实用规则,在具体页面局部注册业务逻辑类(如 LoginViewModel
),应该只在 main.dart
全局注册基础服务(如 NetworkService
)。
结合 GetIt 的工厂模式注册和 Provider 的按需使用,可以避免在 main 中预注册所有 ViewModel:
// injection_container.dartvoid setupViewModelDependencies() { getIt.registerFactory<UserViewModel>( () => UserViewModel(getIt<GetUserByIdUseCase>()) ); // 其他ViewModel同理}// 页面中使用时动态获取final viewModel = Provider.of<UserViewModel>( context, listen: false, create: (_) => getIt<UserViewModel>(), // 从GetIt工厂创建);
通过 ProxyProvider
或 ChangeNotifierProxyProvider
实现 ViewModel 的延迟初始化:
// main.dart中仅注册基础服务void main() { runApp( MultiProvider( providers: [ Provider(create: (_) => Dio()), Provider(create: (_) => UserApiService(getIt<Dio>())), ], child: MyApp(), ), );}// 页面中按需组合 ViewModelProvider( create: (context) => UserViewModel( GetUserByIdUseCase( UserRepositoryImpl( context.read<UserApiService>() ) ) ), child: Consumer<UserViewModel>(...),)
使用 onGenerateRoute
在路由跳转时动态注册:
MaterialApp( onGenerateRoute: (settings) { return MaterialPageRoute( builder: (context) { return Provider( create: (_) => getIt<UserViewModel>(), // 或直接构造 child: const HomePage(), ); }, ); },)
方案 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
GetIt+Provider | 解耦注册与使用,支持全局单例 | 需维护GetIt容器 | 中大型项目 |
懒加载ProxyProvider | 依赖关系清晰 | 嵌套可能较深 | 依赖链复杂的场景 |
路由级注册 | 精确控制生命周期 | 需手动管理路由 | 页面独立性强的应用 |
最佳实践建议:
核心服务(如API Client、数据库)仍在 main 中注册。
页面级 ViewModel 通过 GetIt 工厂或路由动态创建。
使用 context.read()
替代 Provider.of
减少不必要的 rebuild。
通过以上方式,可以保持 main 函数简洁,同时享受 MVVM 架构的清晰分层优势。
物不尽美,事无万全。我很清楚,上面提到的很多细节方面存在一些不足,但作为一篇可参考技术文档,还是直接借鉴和 star 的。我在后面项目开发过程中,会对架构(文章和架构代码)进一步在实践中做不断的优化,代码链接后续再放出来, Thanks 观看。
苹果 A19、A19 Pro 芯片对比出炉:L2、SLC 缓存有差异,iPhone Air 阉割 EU 单元
28.99 万元起,领克 900 上市 4 个月累计交付量突破 3 万台