xmartlabs / stock

Dart package for Async Data Loading and Caching. Combine local (DB, cache) and network data simply and safely.
https://pub.dev/packages/stock
Apache License 2.0
74 stars 6 forks source link

Why stock output type depends on Fetcher type not SourceOfTruth one ?. #29

Closed Abdktefane closed 1 year ago

Abdktefane commented 1 year ago

I need to use different entities for Network (Fetcher) and DB (SourceOfTruth), when I'm using StockTypeMapper it will convert the SourceOfTruth entities to Fetcher which is not follow the concept of SourceOfTruth. As Stock documentations says at source_of_truth.dart file:

/// In other words, values coming from the [Fetcher] will always be sent to the
/// [SourceOfTruth] and will be read back via [reader] to then be returned to
/// the collector.

For example if I need to observe list of tweets in my UI :

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

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

    final SourceOfTruth<void, List<NetworkTweet>> mappedSourceOfTruth = sourceOfTruth.mapTo(
      (List<DbTweet> dbTweets) => dbTweets.map((it) => it.asNetwork).toList(),
      (List<NetworkTweet> networkTweets) => networkTweets.map((it) => it.asDb).toList(),
    );

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

   stock
      .stream('user_id', refresh: true)
      .listen((StockResponse<List<NetworkTweet>> stockResponse) { // we must get List<DbTweet> instead to achieve SourceOfTruth principles.
          /// ......
      }

The stream response must be same as DB model not Network model because it's my source of truth. I think we can add StockTypeMapper to Fetcher class rather than SourceOfTruth one, can I achieve this behavior in another way ?

mirland commented 1 year ago

Hi! Thanks for asking!

Stock tries to be as simple as possible, so it's defined only by two types, the Key and the entity type T:

abstract class Stock<Key, T> {
  factory Stock({
    required Fetcher<Key, T> fetcher,
    required SourceOfTruth<Key, T>? sourceOfTruth,
  }) =>
      StockImpl<Key, T>(fetcher: fetcher, sourceOfTruth: sourceOfTruth);

On the other hand, as you mentioned sometimes you need to use multiple types, for example, DBEntity and NetworkEntity. In this case, what you can do is transform one of them into the other.

You have two ways of doing that:

final fetcher = Fetcher.ofFuture<String, List<NetworkTweet>>(
      (userId) => _api.getUserTweets(userId).then((networkResponse) => mapper(networkResponse)), // TODO: implement mapper
    );

Maybe we can change the documentation to clarify that. Again, the idea is to use only one type, it doesn't matter what type are you using. There's another complex case. How can I use 3 different types? for that I recommend you to check #28

Abdktefane commented 1 year ago

Thanks for your quick response, I'll create mapper over the Fetcher.