資料層
應用程式的資料層,在 MVVM 術語中稱為模型,是所有應用程式資料的真相來源。作為真相來源,它是應用程式資料應該被更新的唯一地方。
它負責從各種外部 API 消耗資料,將這些資料暴露給 UI,處理需要更新資料的 UI 事件,並在需要時將更新請求傳送到這些外部 API。

- 倉庫是應用程式資料的真相來源,幷包含與該資料相關聯的邏輯,例如響應新的使用者事件更新資料或輪詢服務獲取資料。倉庫負責在支援離線功能時同步資料,管理重試邏輯以及快取資料。
- 服務是與 API(如 HTTP 伺服器和平臺外掛)互動的無狀態 Dart 類。您的應用程式需要的任何不在應用程式程式碼本身內部建立的資料都應該從服務類中獲取。
定義一個服務
#服務類是所有架構元件中最明確的一個。它是無狀態的,其函式沒有副作用。它的唯一工作是封裝外部 API。通常每個資料來源有一個服務類,例如客戶端 HTTP 伺服器或平臺外掛。

例如,在 Compass 應用中,有一個 APIClient 服務,它處理到面向客戶的伺服器的 CRUD 呼叫。
class ApiClient {
// Some code omitted for demo purposes.
Future<Result<List<ContinentApiModel>>> getContinents() async { /* ... */ }
Future<Result<List<DestinationApiModel>>> getDestinations() async { /* ... */ }
Future<Result<List<ActivityApiModel>>> getActivityByDestination(String ref) async { /* ... */ }
Future<Result<List<BookingApiModel>>> getBookings() async { /* ... */ }
Future<Result<BookingApiModel>> getBooking(int id) async { /* ... */ }
Future<Result<BookingApiModel>> postBooking(BookingApiModel booking) async { /* ... */ }
Future<Result<void>> deleteBooking(int id) async { /* ... */ }
Future<Result<UserApiModel>> getUser() async { /* ... */ }
}服務本身是一個類,其中每個方法封裝不同的 API 端點並暴露非同步響應物件。繼續之前的刪除已儲存預訂的示例,deleteBooking 方法返回一個 Future<Result<void>>。
定義一個倉庫
#倉庫的唯一職責是管理應用程式資料。倉庫是單個應用程式資料型別的真相來源,並且應該是該資料型別被修改的唯一地方。倉庫負責從外部源輪詢新資料,處理重試邏輯,管理快取資料,並將原始資料轉換為領域模型。

您的應用程式中每種不同型別的資料都應該有一個單獨的倉庫。例如,Compass 應用有 UserRepository、BookingRepository、AuthRepository、DestinationRepository 等倉庫。
以下示例是 Compass 應用中的 BookingRepository,展示了倉庫的基本結構。
class BookingRepositoryRemote implements BookingRepository {
BookingRepositoryRemote({
required ApiClient apiClient,
}) : _apiClient = apiClient;
final ApiClient _apiClient;
List<Destination>? _cachedDestinations;
Future<Result<void>> createBooking(Booking booking) async {...}
Future<Result<Booking>> getBooking(int id) async {...}
Future<Result<List<BookingSummary>>> getBookingsList() async {...}
Future<Result<void>> delete(int id) async {...}
}BookingRepository 將 ApiClient 服務作為輸入,它使用該服務來獲取和更新伺服器上的原始資料。重要的是服務是私有成員,這樣 UI 層就不能繞過倉庫直接呼叫服務。
透過 ApiClient 服務,倉庫可以輪詢伺服器上可能發生的對使用者已儲存預訂的更新,併發送 POST 請求刪除已儲存的預訂。
倉庫轉換為應用程式模型的原始資料可以來自多個源和多個服務,因此倉庫和服務之間存在多對多關係。一個服務可以被任意數量的倉庫使用,而一個倉庫可以使用多個服務。

領域模型
#BookingRepository 輸出 Booking 和 BookingSummary 物件,它們是領域模型。所有倉庫都輸出相應的領域模型。這些資料模型與 API 模型不同,因為它們只包含應用程式其餘部分所需的資料。API 模型包含原始資料,這些資料通常需要經過過濾、組合或刪除才能被應用程式的檢視模型使用。倉庫會提煉原始資料並將其作為領域模型輸出。
在示例應用中,領域模型透過 BookingRepository.getBooking 等方法的返回值暴露。getBooking 方法負責從 ApiClient 服務獲取原始資料,並將其轉換為 Booking 物件。它透過組合來自多個服務端點的資料來實現這一點。
// This method was edited for brevity.
Future<Result<Booking>> getBooking(int id) async {
try {
// Get the booking by ID from server.
final resultBooking = await _apiClient.getBooking(id);
if (resultBooking is Error<BookingApiModel>) {
return Result.error(resultBooking.error);
}
final booking = resultBooking.asOk.value;
final destination = _apiClient.getDestination(booking.destinationRef);
final activities = _apiClient.getActivitiesForBooking(
booking.activitiesRef);
return Result.ok(
Booking(
startDate: booking.startDate,
endDate: booking.endDate,
destination: destination,
activity: activities,
),
);
} on Exception catch (e) {
return Result.error(e);
}
}完成事件迴圈
#在本頁中,您已經看到使用者如何刪除已儲存的預訂,從一個事件開始——使用者在一個 Dismissible 小部件上滑動。檢視模型透過將實際的資料變異委託給 BookingRepository 來處理該事件。以下程式碼片段顯示了 BookingRepository.deleteBooking 方法。
Future<Result<void>> delete(int id) async {
try {
return _apiClient.deleteBooking(id);
} on Exception catch (e) {
return Result.error(e);
}
}倉庫使用 _apiClient.deleteBooking 方法向 API 客戶端傳送 POST 請求,並返回一個 Result。HomeViewModel 消耗 Result 及其包含的資料,然後最終呼叫 notifyListeners,完成迴圈。
反饋
#此網站的部分內容正在不斷完善中,我們歡迎您的反饋!