层与层之间的通信
如何实现依赖注入以在 MVVM 层之间进行通信。
除了为架构的每个组件定义明确的职责外,还必须考虑组件如何通信。这既指规定通信的规则,也指组件通信的技术实现。应用程序的架构应该回答以下问题
- 哪些组件允许与哪些其他组件通信(包括相同类型的组件)?
- 这些组件将哪些内容作为输出暴露给彼此?
- 如何将任何给定的层“连接”到另一个层?
以这个图表为指导,参与规则如下
| 组件 | 参与规则 |
|---|---|
| 视图 (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 来实现依赖注入。
服务和仓库作为 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(),
),
);
服务仅被暴露,以便它们可以立即通过 provider 中的 BuildContext.read 方法注入到仓库中,如前面的代码片段所示。然后暴露仓库,以便根据需要将其注入到视图模型中。
在 widget 树的稍低层级,与完整屏幕对应的视图模型在 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: [
// ...
],
),
],
);
在视图模型或仓库内部,注入的组件应该是私有的。例如,HomeViewModel 类如下所示
class HomeViewModel extends ChangeNotifier {
HomeViewModel({
required BookingRepository bookingRepository,
required UserRepository userRepository,
}) : _bookingRepository = bookingRepository,
_userRepository = userRepository;
final BookingRepository _bookingRepository;
final UserRepository _userRepository;
// ...
}
私有方法可以防止视图(它可以访问视图模型)直接调用仓库上的方法。
这完成了 Compass 应用程序的代码演练。此页面仅演练了与架构相关的代码,但并没有讲述完整的故事。大多数实用代码、widget 代码和 UI 样式都被忽略了。浏览 Compass 应用程序仓库 以获取遵循这些原则构建的强大 Flutter 应用程序的完整示例。
反馈
#由于本网站的这一部分正在不断发展,我们 欢迎您的反馈!