<div id="qayss"></div>
  • <xmp id="qayss">
  • <small id="qayss"><small id="qayss"></small></small>
    <div id="qayss"></div>
  • <li id="qayss"></li>
  • <div id="qayss"><div id="qayss"></div></div>
  • <xmp id="qayss"><li id="qayss"></li>
    <div id="qayss"><li id="qayss"></li></div>
  • <small id="qayss"></small>
  • <div id="qayss"></div>
  • <xmp id="qayss"><div id="qayss"></div><div id="qayss"><div id="qayss"></div></div><xmp id="qayss">
    <small id="qayss"><div id="qayss"></div></small><div id="qayss"></div>
  • 如何使用 Riverpod 架構獲取數據并執行數據變更

    來源:codewithandrea.com 更新時間:2023-10-31 17:49

    在我的先前文章中,我介紹了一個由四個層次(數據、領域、應用和展示)構成的Riverpod應用架構: App architecture using data, domain, application, and presentation layers. Arrows show the dependencies between layers. 使用數據、領域、應用和表現層的應用架構。箭頭顯示了各層之間的依賴關系。每個層次都有自己的職責,各層之間的通信方式也有清晰的約定。

    但是,所有這些不同的類如何相互交互,以及我們如何利用它們來構建和發布應用程序中的功能呢?

    這就是Riverpod及其所有有用的提供者派上用場的地方。

    在構建移動應用程序時,我們的工作大致可以歸結為兩件事情:

    • 如何從網絡中獲取數據并在用戶界面中顯示?
    • 如何在響應輸入事件時執行數據變更操作?

    因此,在本文中,我將回答這些問題,并為您提供更清晰的整體架構圖。

    準備好了嗎?讓我們開始吧!

    如何使用Riverpod架構獲取數據

    從概念上來看,當我們的應用程序從網絡中讀取一些數據時,數據會從數據源流向用戶界面: Unidirectional data flow from the data layer to the presentation layer.
    數據層到呈現層的單向數據流。但如果我們只是獲取數據,真的需要創建單獨的服務和控制器嗎?

    當然不需要。

    相反,使用Riverpod獲取數據的最簡單方式是聲明一個FutureProvider(或如果您有實時數據,可以使用StreamProvider),并在我們小部件的build方法中監視它。

    對于這種用例,我們應用程序架構的簡化版本如下: Simplified architecture when fetching data.
    在獲取數據時,采用了簡化的架構。整個過程分為四個步驟:

    1. Widget 觀察一個 FutureProvider,該 FutureProvider 反過來調用一個倉庫方法,以檢索數據。
    2. 倉庫從數據源中獲取數據。
    3. 一旦收到響應,倉庫將解析并從倉庫(以及附加的 FutureProvider,它還緩存數據)返回數據。
    4. Widget 將數據作為 AsyncValue 接收,并將其映射到用戶界面。

    那么,我們如何在代碼中實現這個過程呢?

    數據獲取示例:天氣應用

    作為一個實際示例,讓我們看看如何從 API 中獲取一些天氣數據并在用戶界面中顯示它。

    第一步是創建一個 Weather 模型類:

    
    // domain/weather.dart
    class Weather {
      Weather(this.temp);
      final double temp;
      // just a basic implementation
      factory Weather.fromJson(Map<String, dynamic> json) {
        return Weather(json['temp'] as double);
      }
      // TODO: Implement ==, hashCode, toString()
    }
    

    接下來,我們可以定義一個 WeatherRepository 及其相應的提供者:

    
    // data/weather_repository.dart
    import 'package:http/http.dart' as http;
    import 'package:riverpod_annotation/riverpod_annotation.dart';
    import 'package:riverpod_examples/weather.dart';
    part 'weather_repository.g.dart';
    class WeatherRepository {
      WeatherRepository(this.client);
      // this is the data source (from the http package)
      final http.Client client;
      Future<Weather> fetchWeather(String city) {
        // TODO: use the http client to:
        // 1. fetch the weather data
        // 2. parse the response and return a Weather object
      }
    }
    // this will generate a weatherRepositoryProvider
    @riverpod
    WeatherRepository weatherRepository(WeatherRepositoryRef ref) {
      return WeatherRepository(http.Client());
    }
    // this will generate a fetchWeatherProvider
    @riverpod
    Future<Weather> fetchWeather(FetchWeatherRef ref, String city) {
      return ref.watch(weatherRepositoryProvider).fetchWeather(city);
    }
    

    上面的代碼使用了現代的 @riverpod 語法,來自 Riverpod Generator 包(但這并非強制要求,我們也可以使用常規的 providers)。要了解更多信息,請閱讀:如何使用Flutter Riverpod Generator自動生成提供者。

    最重要的是我們有兩個提供者:

    • weatherRepositoryProvider 以便我們可以訪問存儲庫
    • fetchWeatherProvider 以便我們可以獲取天氣信息(并進行緩存)

    最后,這是UI代碼:

    
    // presentation/weather_ui.dart
    import 'package:flutter/material.dart';
    import 'package:flutter_riverpod/flutter_riverpod.dart';
    import 'package:riverpod_examples/weather_repository.dart';
    class WeatherUI extends ConsumerWidget {
      const WeatherUI({super.key});
      @override
      Widget build(BuildContext context, WidgetRef ref) {
        // note 
        final weatherAsync = ref.watch(fetchWeatherProvider('London'));
        return weatherAsync.when(
          data: (weather) => Text(weather.temp.toString()),
          loading: () => const Center(child: CircularProgressIndicator()),
          error: (e, _) => Text(e.toString()),
        );
      }
    }
    

    注意,由于我們從異步提供程序中獲取數據,我們的小部件將重新構建兩次。第一次使用AsyncLoading的值(當小部件首次掛載時),然后再次使用AsyncData(weather)的值(一旦數據已經獲?。?。

    這是一個非常簡單的數據獲取示例。但即使我們需要解析復雜的JSON或獲取整個結果列表,我們只需要四個要素:

    • 一個小部件類來顯示UI
    • 一個模型類來表示數據
    • 一個用于從網絡獲取數據的存儲庫
    • 一些提供程序來將所有內容粘合在一起

    請注意,在獲取數據時,根本不需要控制器。

    我知道使用Riverpod的人在這一點上經常感到困惑,所以我會再說一遍:

    當您僅僅需要獲取數據(無論多復雜),根本不需要AsyncNotifier(或StateNotifierChangeNotifier)!

    您只需要一個FutureProvider(用于一次性讀?。┗蛞粋€StreamProvider(如果您有實時數據源)。通過使用它們,您可以免費獲得數據緩存!

    要了解有關Riverpod數據緩存功能的更多信息,請閱讀:Riverpod數據緩存和提供程序生命周期:完整指南。

    獲取數據:基本步驟

    總之,當您需要獲取一些數據并在UI中顯示它時,請按照以下步驟操作:

    1. 創建一個模型類,并添加一個fromMap/fromJson工廠構造函數(只存儲/解析需要在UI中顯示的值)。
    2. 創建一個存儲庫并添加一個獲取數據并返回Future(或Stream)的方法。
    3. 創建一個存儲庫提供程序,以及使用剛剛添加的方法的FutureProvider(或StreamProvider)。
    4. 在小部件中,觀察該提供程序并將您的數據映射到UI。

    再次強調,在運行時,它是這樣工作的: Simplified architecture when fetching data.
    在獲取數據時的簡化架構。當然,獲取數據只是故事的一部分(而且相對較簡單)!

    但有時,您還需要以響應輸入事件的方式將一些數據寫回到數據庫或遠程后端。

    "寫回數據"的過程稱為數據變更(data mutation),讓我們來了解一下它是如何工作的。??

    如何使用Riverpod架構執行數據變更

    當發生數據變更時,數據的傳播順序如下:widget → controller → 服務(可選)→ 存儲庫 → 數據源: Update flow during a data mutation
    在進行數據變更時更新流程 談到數據變更時,我們需要解決一些問題:

    • 在響應輸入事件時,如何在后端上寫入(創建/更新/刪除)數據?
    • 如何確保我們的用戶界面在變更之前、期間和之后處于正確的狀態(數據/加載/錯誤)?
    • 在變更完成后,如何將數據(或錯誤)傳播回用戶界面?

    所有這些問題都需要引入控制器類,它們可用于:

    • 從小部件接收輸入數據
    • 調用存儲庫方法并傳遞數據,以便將其寫入后端
    • 更新狀態,以便小部件能夠處理數據/加載/錯誤情況

    下面是一個更詳細的圖表,顯示了在進行數據變更時發生的情況: Overview of the classes needed when performing a data mutation.
    在執行數據變更時所需的類概述。步驟按以下順序進行:

    1. 發生輸入事件(例如用戶提交表單)
    2. 輸入數據傳遞給處理它的控制器
    3. 控制器將狀態設置為“加載”,以便小部件可以更新UI
    4. 控制器調用異步方法,將數據傳遞給倉庫
    5. 倉庫將數據轉換為DTO并將其(異步)寫入數據源
    6. 收到響應,或拋出異常
    7. 成功或錯誤狀態傳播回控制器,控制器可以處理它
    8. 控制器將狀態設置為“成功”或“錯誤”,小部件更新UI

    請注意,按照上述步驟,我們處理了兩個方面的問題:

    • 執行數據變更(通過在后端創建/更新/刪除數據)
    • 更新UI(通過將狀態設置為加載/成功/錯誤)

    在上述所有類中,控制器扮演著非常重要的角色。??

    使用控制器類處理變更

    控制器可以實現為AsyncNotifier的子類。我已經在我的關于演示層的文章中詳細介紹了控制器,所以在這里不會提供完整的示例。

    但主要思想是數據變更是異步操作,可能成功也可能失敗,最好將變更邏輯保持在小部件之外。

    例如,這是我們如何實現一個用于更新產品的控制器的示例:

    
    @riverpod
    class EditProductController extends _$EditProductController {
      @override
      FutureOr<void> build() {
        // perform some initialization if needed
        // then return the initial value
      }
      Future<void> updateProduct({
        required Product product, // the previous product
        required String title, // the new title
        required String description, // the new description
      }) async {
        final productsRepository = ref.read(productsRepositoryProvider);
        final updatedProduct = product.copyWith(
          title: title,
          description: description,
        );
        state = const AsyncLoading();
        // perform the mutation and update the state
        state = await AsyncValue.guard(
          () => productsRepository.updateProduct(updatedProduct),
        );
      }
      Future<void> deleteProduct(Product product) async {
        // similar to the method above, but use the repository
        // to delete the product instead
      }
    }
    

    以下是我們如何從小部件中調用 updateProduct 方法:

    
    onPressed: () => ref
        .read(editProductControllerProvider.notifier)
        .updateProduct(
          product: product,
          title: _titleController.text,
          description: _descriptionController.text,
        ),
    

    如我們所見,控制器有助于將用戶界面(UI)代碼與數據更新邏輯分離,從而使我們的代碼更易閱讀、可測試和可維護。

    通常情況下,控制器負責以下工作:

    然而,請注意,控制器不執行實際的數據變更(存儲庫通過與數據源通信來執行變更,數據源是事實來源)。

    因此,如果你發現自己在控制器中存儲某些應用程序狀態(如主題設置或用戶身份驗證狀態),那么你的方法是錯誤的。

    相反,應記住事實來源在數據層中,而控制器應僅在小部件和存儲庫之間進行中介。 The data source is the source of truth 讓我們回顧一下我們所學到的內容。??

    數據變更:必要步驟

    如果您需要在您的應用程序中實現數據變更,可以按照以下步驟進行操作:

    1. 如果您之前還沒有這樣做,需要在您的模型中添加序列化邏輯(toMap/toJson)。
    2. 添加一個執行所需變更操作(創建/更新/刪除)的倉庫方法。
    3. 創建一個作為AsyncNotifier子類的控制器。將build方法保持為空,然后添加一個調用第2步中的倉庫方法并更新狀態的方法。
    4. 在小部件回調中,使用ref.read來訪問控制器并調用第3步中的方法。

    作為額外步驟,您可以在小部件的build方法中觀察控制器的狀態,并在變更進行中禁用UI。要了解更多關于這一步的信息,請閱讀:如何使用StateNotifier和AsyncValue處理加載和錯誤狀態。

    上述步驟涵蓋了執行數據變更的操作。

    然而,還有一個關鍵問題沒有解決。??

    變更完成后如何在UI中顯示更新后的數據?

    答案取決于數據源是否支持實時更新。

    處理實時監聽和一次性讀取將是另一篇文章的主題。但現在,我會這樣說:

    在可能的情況下,盡量使用支持實時更新的后端,因為這樣可以在數據變更時自動重新構建UI。??

    為了考慮這一點,這是一個更新后的圖示,每當發生變更時,UI也會自動更新: Realtime updates happen automatically when a data mutation takes place
    實時更新會在數據發生變化時自動發生,實現這一功能的最簡單方法如下:

    • 添加一個返回Stream的存儲庫方法,該方法會在數據發生變化時發出新值。
    • 創建相應的StreamProvider。
    • 在您的小部件中監視StreamProvider,以便在數據更改時重新構建用戶界面。

    另外,這意味著一個小部件可以同時監視StreamProvider(獲取數據)和AsyncNotifierProvider(在進行數據變更時從控制器獲取狀態更新),實際上,我在自己的應用中經常這樣做。

    說了這么多,現在是總結的時候了。??

    結論

    在構建移動應用程序時,我們需要關注兩個重要問題:獲取數據和執行數據變更。

    如果使用Riverpod,遵循以下簡單規則將使我們的生活更輕松:

    • 在獲取數據時,使用FutureProviderStreamProvider。
    • 在進行數據變更時,使用AsyncNotifier。

    由于Riverpod不是非常清晰的,我們可以遵循一種引用架構,其中每個層級都有自己的責任。

    通過遵循這種架構,獲取數據變得非常簡單: Simplified architecture when fetching data.
    在獲取數據時采用簡化的架構。而數據的更改需要進行一些額外的工作,但也可以以可重復的方式進行實施(特別是如果我們支持實時更新)。 Realtime updates happen automatically when a data mutation takes place
    實時更新會在數據發生變化時自動發生。上面的圖表和我分享的步驟應該足夠讓您入門。

    但隨著深入研究,可能會出現一些額外的問題:

    • 當您的項目擁有許多復雜功能時,如何組織項目結構?
    • 如果需要將來自不同數據源的數據組合在一起怎么辦?
    • 如果數據源拋出異常,是倉庫(repository)還是控制器(controller)負責捕獲異常?
    • 我們應該使用什么用戶界面(UI)來表示加載和錯誤狀態?
    • 如果用戶提交一些數據然后在變異完成之前離開頁面會發生什么?

    其中一些實現細節可以一勞永逸地為整個項目做出決策,而其他決策可以根據具體情況進行。

    曰本丰满熟妇XXXX性,一女多男在疯狂伦交,多人乱P杂交公车调教,成人AV在线一区二区三区
    <div id="qayss"></div>
  • <xmp id="qayss">
  • <small id="qayss"><small id="qayss"></small></small>
    <div id="qayss"></div>
  • <li id="qayss"></li>
  • <div id="qayss"><div id="qayss"></div></div>
  • <xmp id="qayss"><li id="qayss"></li>
    <div id="qayss"><li id="qayss"></li></div>
  • <small id="qayss"></small>
  • <div id="qayss"></div>
  • <xmp id="qayss"><div id="qayss"></div><div id="qayss"><div id="qayss"></div></div><xmp id="qayss">
    <small id="qayss"><div id="qayss"></div></small><div id="qayss"></div>