層與層之間的通訊
如何實現依賴注入以在 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 應用程式的完整示例。
反饋
#由於本網站的這一部分正在不斷發展,我們 歡迎您的反饋!