层级间通信
如何实现依赖注入以在 MVVM 层级之间进行通信。
在为架构的每个组件定义明确职责的同时,考虑组件之间如何通信也同样重要。这既指规定通信的规则,也指组件通信的技术实现。应用程序的架构应回答以下问题:
- 哪些组件被允许与哪些其他组件(包括同类型的组件)进行通信?
- 这些组件向彼此公开了哪些输出?
- 给定层级是如何与另一层级“连接(wired up)”的?
以该图表作为指南,交互规则如下:
| 组件 | 交互规则 |
|---|---|
| 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 应用程序组件树的顶层。
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 方法注入到存储库中而被公开,如前述代码片段所示。随后,存储库被公开以便根据需要注入到视图模型中。
在组件树更低的位置,对应于整个屏幕的视图模型是在 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 应用的代码演练。本页面仅介绍了与架构相关的代码,并未涵盖全部内容。大多数工具代码、组件代码和 UI 样式都被忽略了。请浏览 Compass 应用代码库,以获取遵循这些原则构建的健壮 Flutter 应用程序的完整示例。
反馈
#随着网站这一部分的不断完善,我们欢迎您提供反馈!