股票

Pub Codecov Dart CI style: very good analysis

Stock 是一个 Dart 包,用于从远程和本地源加载数据。它的灵感来自 Store Kotlin 库。

它的主要目标是防止过多的网络和磁盘缓存调用。通过使用它,您可以消除网络中因相同请求而泛滥的可能性,同时又添加了缓存层。

虽然您可以在没有本地源的情况下使用它,但最大的好处在于将 Stock 与本地数据库结合使用,例如 FloorDriftSqfliteRealm 等。

功能

  • 简单地合并本地(数据库、缓存)和网络数据。它提供了一个数据 Stream,您可以在其中收听和处理您的数据。
  • 了解数据 Stream 的状态。例如,这对于显示错误或加载指示器非常有用。
  • 如果您不使用本地数据库,Stock 提供内存缓存,以共享和改进您应用程序的体验。
  • 更安全地处理您的数据。如果发生错误,Stock 会捕获它并将其返回到流中,以便轻松处理。

概述

一个 Stock 负责管理特定的数据请求。

它基于两个重要类

  • Fetcher:定义如何通过网络获取数据。
  • SourceOfTruth:定义如何在您的本地缓存中读取和写入本地数据。尽管 Stock 可以不用它,但推荐使用。

Stock 使用泛型键作为数据的标识符。键可以是任何正确实现 toString()equals()hashCode() 的值对象。

创建 Stock 时,它会为您提供大量方法来访问数据。最重要的方法是 stream(),它为您提供数据的 Stream,可用于更新您的 UI 或执行特定操作。

入门

要使用此包,请将 stock 添加为 pubspec.yaml 文件中的依赖项

1. 创建一个 Fetcher

Fetcher 用于从网络获取新数据。您可以从 FutureStream 创建它。

FutureFetcher 通常与 RestApi 一起使用,而 StreamFetcher 则与 Stream 源(如 Web Socket)一起使用。

  final futureFetcher = Fetcher.ofFuture<String, List<Tweet>>(
    (userId) => _api.getUserTweets(userId),
  );

  final streamFetcher = Fetcher.ofStream<String, List<Tweet>>(
    (userId) => _api.getUserTweetsStream(userId),
  );

2. 创建一个 Source Of Truth

Source Of Truth 用于将远程数据读写到本地缓存中。

通常,您将使用本地数据库实现 SourceOfTruth。但是,如果您不使用本地数据库/缓存,该库提供了 CachedSourceOfTruth,一个在内存中存储数据的 truth source。

  final sourceOfTruth = SourceOfTruth<String, List<Tweet>>(
    reader: (userId) => _database.getUserTweets(userId),
    writer: (userId, tweets) => _database.writeUserTweets(userId, tweets),
  );

在此示例中,Fetcher 类型与 SourceOfTruth 类型完全相同。如果您需要使用多种类型,可以使用 mapTo 扩展

注意:为确保正常运行,当使用新数据调用 write 时,truth source 必须在 reader 中发出新值。

3. 创建 Stock

Stock 允许您组合不同的数据源并获取数据。

 final stock = Stock<String, List<Tweet>>(
    fetcher: fetcher,
    sourceOfTruth: sourceOfTruth,
  );

从 Stock 获取数据 Stream

您可以使用 stream() 生成数据 Stream

您需要用特定的 key 和可选的 refresh 值来调用它,其中 refresh 值告诉 Stock 刷新是可选的还是强制的。

这将返回一个 StockResponse 的数据流,它有 3 种可能的值

  • StockResponseLoading 通知网络请求正在进行中。它可用于在 UI 中显示加载指示器。
  • StockResponseData 包含响应数据。它有一个 value 字段,其中包含 Stock 返回的类型的实例。
  • StockResponseError 表示发生了错误。当发生错误时,Stock 不会抛出异常,而是将其包装在这个类中。它包含一个 error 字段,其中包含给定 origin 抛出的异常。

每个 StockResponse 都包含一个 origin 字段,指定事件的来源。

  stock
      .stream('key', refresh: true)
      .listen((StockResponse<List<Tweet>> stockResponse) {
    if (stockResponse is StockResponseLoading) {
      _displayLoadingIndicator();
    } else if (stockResponse is StockResponseData) {
      _displayTweetsInUI((stockResponse is StockResponseData).data);
    } else {
      _displayErrorInUi((stockResponse as StockResponseError).error);
    }
  });

从 Stock 获取非流式数据

Stock 提供了一些方法来在不使用数据流的情况下获取数据。

  1. get 返回缓存的数据 - 如果已缓存 - 否则将返回新鲜/网络数据(更新您的缓存)。
  2. fresh 返回新鲜数据并更新您的缓存

  // Get fresh data
  final List<Tweet> freshTweets = await stock.fresh(key);
  
  // Get the previous cached data
  final List<Tweet> cachedTweets = await stock.get(key);

FetcherSourceOfTruth 使用不同的类型

有时您需要为网络和数据库使用不同的实体。为此,Stock 提供了 StockTypeMapper,一个将一个实体转换为另一个实体的类。

StockTypeMapper 通过 mapToUsingMapper 方法在 SourceOfTruth 中使用

class TweetMapper implements StockTypeMapper<DbTweet, NetworkTweet> {
  @override
  NetworkTweet fromInput(DbTweet value) => NetworkTweet(value);

  @override
  DbTweet fromOutput(NetworkTweet value) => DbTweet(value);
}

final SourceOfTruth<int, DbTweet> sot = _createMapper();
final SourceOfTruth<int, NetworkTweet> newSot = sot.mapToUsingMapper(TweetMapper());

您也可以使用 mapTo 扩展来实现相同的结果。

final SourceOfTruth<int, DbTweet> sot = _createMapper();
final SourceOfTruth<int, NetworkTweet> newSot = mapTo(
      (networkTweet) => DbTweet(networkTweet),
      (dbTweet) => NetworkTweet(dbTweet),
);

使用非流式 truth source

有时您的 truth source 不提供实时数据流。例如,假设您正在使用 shared_preferences 来存储您的数据,或者您只是在内存中缓存数据。对于这些情况,Stock 提供了 CachedSourceOfThruth,一个 SourceOfThruth,在这种情况下会很有帮助。

class SharedPreferencesSourceOfTruth extends CachedSourceOfTruth<String, String> {
  SharedPreferencesSourceOfTruth();

  @override
  @protected
  Stream<T?> reader(String key) async* {
    final prefs = await SharedPreferences.getInstance();
    // Read data from an non-stream source
    final stringValue = prefs.getString(key);
    setCachedValue(key, stringValue);
    yield* super.reader(key);
  }

  @override
  @protected
  Future<void> write(String key, String? value) async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString(key, value);
    await super.write(key, value);
  }
}

此类可与 StockTypeMapper 一起使用,将您的数据转换为实体。

// The mapper json to transform a string into a User 
final StockTypeMapper<String, User> mapper = _createUserMapper();
final SharedPreferencesSourceOfTruth<String, User> = SharedPreferencesSourceOfTruth()
    .mapToUsingMapper(mapper);

附加信息

对于 bug,请使用 GitHub Issues。对于问题、想法和讨论,请使用 GitHub Discussions

Xmartlabs 使用 ❤️ 制作。

许可证

Copyright (c) 2022 Xmartlabs SRL.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://apache.ac.cn/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

GitHub

查看 Github