层间通信
如何通过依赖注入实现 MVVM 各层之间的通信。
除为架构各组件定义清晰职责外,还需考虑组件如何通信—— 既包括约束通信的规则,也包括通信的技术实现。应用架构应回答以下问题:
-
哪些组件可以与哪些其他组件(含同类型组件)通信?
这些组件向彼此暴露什么作为输出?
任意一层如何与另一层「接线」?
以该图为指南,协作规则如下:
| Component | Rules of engagement |
|---|---|
| View |
|
| ViewModel |
|
| Repository |
|
| Service |
|
| 组件 | 协作规则 |
|---|---|
| View |
|
| ViewModel |
|
| Repository |
|
| Service |
|
依赖注入
#
本指南已说明各组件如何通过输入输出通信。层间通信一律通过将组件传入消费方构造函数实现,例如将 Service 传入 Repository。
class MyRepository {
MyRepository({required MyService myService})
: _myService = myService;
late final MyService _myService;
}
然而还缺一环:对象创建。应用中 MyService 实例在何处创建以便传入 MyRepository?答案涉及 依赖注入 模式。
在 Compass 中,依赖注入 通过 package:provider
实现。基于构建 Flutter 应用的经验,Google 团队推荐使用 package:provider 实现依赖注入。
Service 与仓库作为 Provider 对象暴露于 Flutter 应用 widget 树顶层。
runApp(
MultiProvider(
providers: [
Provider(create: (context) => AuthApiClient()),
Provider(create: (context) => ApiClient()),
Provider(create: (context) => SharedPreferencesService()),
ChangeNotifierProvider(
create: (context) => AuthRepositoryRemote(
authApiClient: context.read(),
apiClient: context.read(),
sharedPreferencesService: context.read(),
) as AuthRepository,
),
Provider(create: (context) =>
DestinationRepositoryRemote(
apiClient: context.read(),
) as DestinationRepository,
),
Provider(create: (context) =>
ContinentRepositoryRemote(
apiClient: context.read(),
) as ContinentRepository,
),
// In the Compass app, additional service and repository providers live here.
],
child: const MainApp(),
),
);
Service 仅为了立即通过 provider 的 BuildContext.read 注入仓库而暴露,如上一片段所示。随后暴露仓库以便按需注入 view model。
在 widget 树稍低处,对应全屏的 view model 在 package:go_router
配置中创建,再次用 provider 注入所需仓库。
// This code was modified for demo purposes.
GoRouter router(
AuthRepository authRepository,
) =>
GoRouter(
initialLocation: Routes.home,
debugLogDiagnostics: true,
redirect: _redirect,
refreshListenable: authRepository,
routes: [
GoRoute(
path: Routes.login,
builder: (context, state) {
return LoginScreen(
viewModel: LoginViewModel(
authRepository: context.read(),
),
);
},
),
GoRoute(
path: Routes.home,
builder: (context, state) {
final viewModel = HomeViewModel(
bookingRepository: context.read(),
);
return HomeScreen(viewModel: viewModel);
},
routes: [
// ...
],
),
],
);
在 view model 或仓库内部,注入的组件应为私有。例如 HomeViewModel 如下:
class HomeViewModel extends ChangeNotifier {
HomeViewModel({
required BookingRepository bookingRepository,
required UserRepository userRepository,
}) : _bookingRepository = bookingRepository,
_userRepository = userRepository;
final BookingRepository _bookingRepository;
final UserRepository _userRepository;
// ...
}
私有成员防止能访问 view model 的 view 直接调用仓库方法。
Compass 应用代码 walkthrough 到此结束。本页仅涵盖架构相关代码,并非全貌;大部分工具代码、widget 代码与 UI 样式未涉及。请在 Compass 应用仓库 浏览完整示例。
反馈
#网站本节内容仍在完善中, 欢迎提供反馈!
除非另有说明,本文档之所提及适用于 Flutter 3.44.0 版本。本页面最后更新时间:2026-06-04。查看文档源码 或者 为本页面内容提出建议。