diff --git a/README.md b/README.md index f72f0ab..1c275e1 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ It is inspired by the [Store] Kotlin library. Its main goal is to prevent excessive calls to the network and disk cache. By utilizing it, you eliminate the possibility of flooding your network with the same request while, at the same time, adding layers of caching. -Although you can use it without a local source, the greatest benefit comes from combining Stock with a local database such as [Floor], [Drift], [sqflite], [Realm], etc. +Although you can use it without a local source, the greatest benefit comes from combining Stock with a local database such as [Floor], [Drift], [Sqflite], [Realm], etc. ## Features @@ -73,7 +73,7 @@ _Note: to the proper operation, when `write` is invoked with new data, the sourc `Stock` lets you combine the different data sources and get the data. ```dart - final store = Store>( + final stock = Stock>( fetcher: fetcher, sourceOfTruth: sourceOfTruth, ); @@ -85,25 +85,25 @@ You can generate a data `Stream` using `stream()`. You need to invoke it with a specific `key`, and an optional `refresh` value that tells Stock if a refresh is optional or mandatory. -That returns a data stream of `StoreResponse`, which has 3 possible values: -- `StoreResponseLoading` informs that a network request is in progress. It can be useful to display a loading indicator in your UI. -- `StoreResponseData` holds the response data. It has a `value` field which includes an instance of the type returned by `Stock`. -- `StoreResponseError` indicates that an error happened. +That returns a data stream of `StockResponse`, which has 3 possible values: +- `StockResponseLoading` informs that a network request is in progress. It can be useful to display a loading indicator in your UI. +- `StockResponseData` holds the response data. It has a `value` field which includes an instance of the type returned by `Stock`. +- `StockResponseError` indicates that an error happened. When an error happens, `Stock` does not throw an exception, instead, it wraps it in this class. It includes an `error` field that contains the exception thrown by the given `origin`. -Each `StoreResponse` includes an `origin` field which specifies where the event is coming from. +Each `StockResponse` includes an `origin` field which specifies where the event is coming from. ```dart - store + stock .stream('key', refresh: true) - .listen((StoreResponse> storeResponse) { - if (storeResponse is StoreResponseLoading) { + .listen((StockResponse> stockResponse) { + if (stockResponse is StockResponseLoading) { _displayLoadingIndicator(); - } else if (storeResponse is StoreResponseData) { - _displayTweetsInUI((storeResponse is StoreResponseData).data); + } else if (stockResponse is StockResponseData) { + _displayTweetsInUI((stockResponse is StockResponseData).data); } else { - _displayErrorInUi((storeResponse as StoreResponseError).error); + _displayErrorInUi((stockResponse as StockResponseError).error); } }); ``` @@ -117,21 +117,21 @@ Stock provides a couple of methods to get data without using a data stream. ```dart // Get fresh data - final List freshTweets = await store.fresh(key); + final List freshTweets = await stock.fresh(key); // Get the previous cached data - final List cachedTweets = await store.get(key); + final List cachedTweets = await stock.get(key); ``` ### Use different types for `Fetcher` and `SourceOfTruth` -Sometimes you need to use different entities for Network and DB. For that case `Stock` provides the `StoreTypeMapper`, a class that transforms one entity into the other. +Sometimes you need to use different entities for Network and DB. For that case `Stock` provides the `StockTypeMapper`, a class that transforms one entity into the other. -`StoreTypeMapper` is used in the `SourceOfTruth` via the method `mapToUsingMapper` +`StockTypeMapper` is used in the `SourceOfTruth` via the method `mapToUsingMapper` ```dart -class TweetMapper implements StoreTypeMapper { +class TweetMapper implements StockTypeMapper { @override NetworkTweet fromInput(DbTweet value) => NetworkTweet(value); @@ -197,7 +197,7 @@ final SharedPreferencesSourceOfTruth = SharedPreferencesSourceOfTr For bugs please use [GitHub Issues](https://github.com/xmartlabs/stock/issues). For questions, ideas, and discussions use [GitHub Discussions](https://github.com/xmartlabs/stock/discussions). -Made with ❤️ by [Xmartlabs](http://xmartlabs.com). +Made with ❤️ by [Xmartlabs](https://xmartlabs.com). ## License @@ -215,13 +215,13 @@ Made with ❤️ by [Xmartlabs](http://xmartlabs.com). See the License for the specific language governing permissions and limitations under the License. -[Store]: https://github.com/MobileNativeFoundation/Store -[Floor]: https://pub.dev/packages/floor [Drift]: https://pub.dev/packages/drift -[sqflite]: https://pub.dev/packages/sqflite +[Floor]: https://pub.dev/packages/floor [Realm]: https://pub.dev/packages/realm -[`Stock`]: lib/store.dart -[`Fetcher`]: lib/fetcher.dart -[`SourceOfTruth`]: lib/source_of_truth.dart -[`CachedSourceOfTruth`]: lib/source_of_truth.dart +[Store]: https://github.com/MobileNativeFoundation/Store +[`CachedSourceOfTruth`]: lib/src/source_of_truth.dart +[`Fetcher`]: lib/src/fetcher.dart +[`SourceOfTruth`]: lib/src/source_of_truth.dart +[`Stock`]: lib/src/stock.dart [shared_preferences]: https://pub.dev/packages/shared_preferences +[Sqflite]: https://pub.dev/packages/sqflite diff --git a/example/main.dart b/example/main.dart index df5fff4..023207c 100644 --- a/example/main.dart +++ b/example/main.dart @@ -1,8 +1,4 @@ -import 'package:stock/fetcher.dart'; -import 'package:stock/source_of_truth.dart'; -import 'package:stock/src/extensions/store_response_extensions.dart'; -import 'package:stock/store.dart'; -import 'package:stock/store_response.dart'; +import 'package:stock/stock.dart'; late TwitterApi _api; late TweetsLocalDatabase _database; @@ -19,32 +15,32 @@ void main() async { writer: (userId, tweets) => _database.writeUserTweets(userId, tweets), ); - // Create Store - final store = Store>( + // Create Stock + final stock = Stock>( fetcher: fetcher, sourceOfTruth: sourceOfTruth, ); // Create a stream to listen tweet changes of the user `xmartlabs` - store + stock .stream('xmartlabs', refresh: true) - .listen((StoreResponse> storeResponse) { - if (storeResponse is StoreResponseLoading) { + .listen((StockResponse> stockResponse) { + if (stockResponse is StockResponseLoading) { _displayLoadingIndicator(); - } else if (storeResponse is StoreResponseData) { - _displayTweetsInUI(storeResponse.data); + } else if (stockResponse is StockResponseData) { + _displayTweetsInUI(stockResponse.requireData()); } else { - _displayErrorInUi((storeResponse as StoreResponseError).error); + _displayErrorInUi((stockResponse as StockResponseError).error); } }); // Get Xmartlabs tweets from the network and save them in the DB. - final List freshTweets = await store.fresh('xmartlabs'); + final List freshTweets = await stock.fresh('xmartlabs'); // Get Xmartlabs tweets // It will try to use Local Database Tweets. // However, if the DB tweets are empty, it will fetch new tweets from the fetcher, and save them in the DB. - final List cachedTweets = await store.get('xmartlabs'); + final List cachedTweets = await stock.get('xmartlabs'); } void _displayTweetsInUI(List? tweets) {} diff --git a/lib/fetcher.dart b/lib/fetcher.dart deleted file mode 100644 index cbaa82e..0000000 --- a/lib/fetcher.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:stock/src/factory_fetcher.dart'; -import 'package:stock/store.dart'; -import 'package:stock/store_response.dart'; - -/// Fetcher is used by [Store] to fetch network records of the type [Output] -/// for a given key of the type [Key]. The return type is [Stream] to -/// allow for multiple results per request. -/// -/// Note: Store does not catch exceptions thrown by a [Fetcher]. -/// Use [StoreResponseError] to communicate expected errors. -/// -/// See [ofFuture] for easily translating from a regular `Future` function. -/// See [ofStream], for easily translating to [StoreResponse] (and -/// automatically transforming exceptions into [StoreResponseError]. -abstract class Fetcher { - Fetcher._(); - - /// "Creates" a [Fetcher] from a [futureFactory] and translates the results into a [StoreResponse]. - /// - /// Emitted values will be wrapped in [StoreResponseData]. If an exception disrupts the stream then - /// it will be wrapped in [StoreResponseError] - /// - /// Use when creating a [Store] that fetches objects in a single response per request - /// network protocol (e.g Http). - static Fetcher ofFuture( - Future Function(Key key) futureFactory, - ) => - FutureFetcher(futureFactory); - - /// "Creates" a [Fetcher] from a [streamFactory] and translates the results into a [StoreResponse]. - /// - /// Emitted values will be wrapped in [StoreResponseData]. If an exception disrupts the flow then - /// it will be wrapped in [StoreResponseError]. - /// - /// Use when creating a [Store] that fetches objects in a multiple responses per request - /// network protocol (e.g Web Sockets). - static Fetcher ofStream( - Stream Function(Key key) streamFactory, - ) => - StreamFetcher(streamFactory); -} diff --git a/lib/src/key_value.dart b/lib/src/common/key_value.dart similarity index 100% rename from lib/src/key_value.dart rename to lib/src/common/key_value.dart diff --git a/lib/errors.dart b/lib/src/errors.dart similarity index 100% rename from lib/errors.dart rename to lib/src/errors.dart diff --git a/lib/src/extensions/future_stream_extensions.dart b/lib/src/extensions/future_stream_extensions.dart deleted file mode 100644 index db1d80c..0000000 --- a/lib/src/extensions/future_stream_extensions.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:rxdart/rxdart.dart'; -import 'package:stock/store_response.dart'; - -extension StreamExtensions on Stream { - Stream> mapToResponse(ResponseOrigin origin) => - map((data) => StoreResponse.data(origin, data)) - .onErrorReturnWith((error, stacktrace) { - return StoreResponse.error(origin, error, stacktrace); - }); -} - -extension FutureExtensions on Future { - Future> mapToResponse(ResponseOrigin origin) async { - try { - return StoreResponse.data(origin, await this); - } catch (error, stacktrace) { - return StoreResponse.error(origin, error, stacktrace); - } - } -} diff --git a/lib/src/extensions/future_stream_internal_extensions.dart b/lib/src/extensions/future_stream_internal_extensions.dart new file mode 100644 index 0000000..e6989e9 --- /dev/null +++ b/lib/src/extensions/future_stream_internal_extensions.dart @@ -0,0 +1,20 @@ +import 'package:rxdart/rxdart.dart'; +import 'package:stock/src/stock_response.dart'; + +extension StreamExtensions on Stream { + Stream> mapToResponse(ResponseOrigin origin) => + map((data) => StockResponse.data(origin, data)) + .onErrorReturnWith((error, stacktrace) { + return StockResponse.error(origin, error, stacktrace); + }); +} + +extension FutureExtensions on Future { + Future> mapToResponse(ResponseOrigin origin) async { + try { + return StockResponse.data(origin, await this); + } catch (error, stacktrace) { + return StockResponse.error(origin, error, stacktrace); + } + } +} diff --git a/lib/src/extensions/stock_response_internal_extensions.dart b/lib/src/extensions/stock_response_internal_extensions.dart new file mode 100644 index 0000000..c9e0ada --- /dev/null +++ b/lib/src/extensions/stock_response_internal_extensions.dart @@ -0,0 +1,29 @@ +import 'package:stock/src/errors.dart'; +import 'package:stock/src/stock_response.dart'; +import 'package:stock/src/stock_response_extensions.dart'; + +extension StockResponseExtensions on StockResponse { + StockResponse swapType() { + if (this is StockResponseData) { + return StockResponse.data(origin, requireData() as R); + } else if (isError) { + var errorResponse = this as StockResponseError; + return StockResponse.error( + origin, + errorResponse.error, + errorResponse.stackTrace, + ); + } else if (isLoading) { + return StockResponse.loading(origin); + } else { + throw StockError('Unknown type'); + } + } +} + +extension StockResponseStreamExtensions on Stream> { + Stream> whereDataNotNull() => where( + (event) => + event is StockResponseData ? event.requireData() != null : true, + ).map((event) => event.swapType()); +} diff --git a/lib/src/extensions/store_response_extensions.dart b/lib/src/extensions/store_response_extensions.dart deleted file mode 100644 index 9bed2c1..0000000 --- a/lib/src/extensions/store_response_extensions.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:stock/errors.dart'; -import 'package:stock/store_response.dart'; - -extension StoreResponseExtensions on StoreResponse { - StoreResponse swapType() { - if (this is StoreResponseData) { - return StoreResponse.data(origin, requireData() as R); - } else if (isError) { - var errorResponse = this as StoreResponseError; - return StoreResponse.error( - origin, - errorResponse.error, - errorResponse.stackTrace, - ); - } else if (isLoading) { - return StoreResponse.loading(origin); - } else { - throw StockError('Unknown type'); - } - } - - T requireData() { - if (this is StoreResponseData) { - return (this as StoreResponseData).value; - } else if (isError) { - throw (this as StoreResponseError).error; - } else if (isLoading) { - throw StockError('There is no data in loading'); - } else { - throw StockError('Unknown type'); - } - } - - T? get data => this is StoreResponseData - ? (this as StoreResponseData).value - : null; - - bool get isLoading => this is StoreResponseLoading; - - bool get isError => this is StoreResponseError; -} - -extension StoreResponseStreamExtensions on Stream> { - Stream> whereDataNotNull() => where( - (event) => - event is StoreResponseData ? event.requireData() != null : true, - ).map((event) => event.swapType()); -} diff --git a/lib/src/fetcher.dart b/lib/src/fetcher.dart new file mode 100644 index 0000000..200500e --- /dev/null +++ b/lib/src/fetcher.dart @@ -0,0 +1,41 @@ +import 'package:stock/src/implementations/factory_fetcher.dart'; +import 'package:stock/src/stock.dart'; +import 'package:stock/src/stock_response.dart'; + +/// Fetcher is used by [Stock] to fetch network records of the type [T] +/// for a given key of the type [Key]. The return type is [Stream] to +/// allow for multiple results per request. +/// +/// Note: [Stock] does not catch exceptions thrown by a [Fetcher]. +/// Use [StockResponseError] to communicate expected errors. +/// +/// See [ofFuture] for easily translating from a regular `Future` function. +/// See [ofStream], for easily translating to [StockResponse] (and +/// automatically transforming exceptions into [StockResponseError]. +abstract class Fetcher { + Fetcher._(); + + /// "Creates" a [Fetcher] from a [futureFactory] and translates the results into a [StockResponse]. + /// + /// Emitted values will be wrapped in [StockResponseData]. If an exception disrupts the stream then + /// it will be wrapped in [StockResponseError] + /// + /// Use when creating a [Stock] that fetches objects in a single response per request + /// network protocol (e.g Http). + static Fetcher ofFuture( + Future Function(Key key) futureFactory, + ) => + FutureFetcher(futureFactory); + + /// "Creates" a [Fetcher] from a [streamFactory] and translates the results into a [StockResponse]. + /// + /// Emitted values will be wrapped in [StockResponseData]. If an exception disrupts the flow then + /// it will be wrapped in [StockResponseError]. + /// + /// Use when creating a [Stock] that fetches objects in a multiple responses per request + /// network protocol (e.g Web Sockets). + static Fetcher ofStream( + Stream Function(Key key) streamFactory, + ) => + StreamFetcher(streamFactory); +} diff --git a/lib/src/factory_fetcher.dart b/lib/src/implementations/factory_fetcher.dart similarity index 91% rename from lib/src/factory_fetcher.dart rename to lib/src/implementations/factory_fetcher.dart index d45003d..6ec6cc4 100644 --- a/lib/src/factory_fetcher.dart +++ b/lib/src/implementations/factory_fetcher.dart @@ -1,4 +1,4 @@ -import 'package:stock/fetcher.dart'; +import 'package:stock/src/fetcher.dart'; class FactoryFetcher implements Fetcher { Stream Function(Key key) factory; diff --git a/lib/src/source_of_truth_impl.dart b/lib/src/implementations/source_of_truth_impl.dart similarity index 93% rename from lib/src/source_of_truth_impl.dart rename to lib/src/implementations/source_of_truth_impl.dart index 5379b41..ed7a3be 100644 --- a/lib/src/source_of_truth_impl.dart +++ b/lib/src/implementations/source_of_truth_impl.dart @@ -1,4 +1,4 @@ -import 'package:stock/source_of_truth.dart'; +import 'package:stock/src/source_of_truth.dart'; class SourceOfTruthImpl implements SourceOfTruth { final Stream Function(Key key) _reader; diff --git a/lib/src/store_impl.dart b/lib/src/implementations/stock_impl.dart similarity index 69% rename from lib/src/store_impl.dart rename to lib/src/implementations/stock_impl.dart index 1bb5972..4f72e6d 100644 --- a/lib/src/store_impl.dart +++ b/lib/src/implementations/stock_impl.dart @@ -2,24 +2,25 @@ import 'dart:async'; import 'package:mutex/mutex.dart'; import 'package:rxdart/rxdart.dart'; -import 'package:stock/fetcher.dart'; -import 'package:stock/source_of_truth.dart'; -import 'package:stock/src/extensions/future_stream_extensions.dart'; -import 'package:stock/src/extensions/store_response_extensions.dart'; -import 'package:stock/src/factory_fetcher.dart'; -import 'package:stock/src/source_of_truth_impl.dart'; -import 'package:stock/store.dart'; -import 'package:stock/store_request.dart'; -import 'package:stock/store_response.dart'; - -class StoreImpl implements Store { +import 'package:stock/src/fetcher.dart'; +import 'package:stock/src/source_of_truth.dart'; +import 'package:stock/src/extensions/future_stream_internal_extensions.dart'; +import 'package:stock/src/extensions/stock_response_internal_extensions.dart'; +import 'package:stock/src/implementations/factory_fetcher.dart'; +import 'package:stock/src/implementations/source_of_truth_impl.dart'; +import 'package:stock/src/stock.dart'; +import 'package:stock/src/stock_request.dart'; +import 'package:stock/src/stock_response.dart'; +import 'package:stock/src/stock_response_extensions.dart'; + +class StockImpl implements Stock { final FactoryFetcher _fetcher; final SourceOfTruth? _sourceOfTruth; final Map _writingMap = {}; final _writingLock = Mutex(); - StoreImpl({ + StockImpl({ required Fetcher fetcher, required SourceOfTruth? sourceOfTruth, }) : _fetcher = fetcher as FactoryFetcher, @@ -28,38 +29,38 @@ class StoreImpl implements Store { @override Future fresh(Key key) => _generateCombinedNetworkAndSourceOfTruthStream( - StoreRequest(key: key, refresh: true), + StockRequest(key: key, refresh: true), WriteWrappedSourceOfTruth(_sourceOfTruth), ) - .where((event) => event is! StoreResponseLoading) + .where((event) => event is! StockResponseLoading) .where((event) => event.origin == ResponseOrigin.fetcher) .first .then((value) => value.requireData()); @override Future get(Key key) => stream(key, refresh: false) - .where((event) => event is! StoreResponseLoading) + .where((event) => event is! StockResponseLoading) .first .then((value) => value.requireData()); @override - Stream> stream(Key key, {refresh = true}) => - streamFromRequest(StoreRequest( + Stream> stream(Key key, {refresh = true}) => + streamFromRequest(StockRequest( key: key, refresh: refresh, )); - Stream> streamFromRequest(StoreRequest request) => + Stream> streamFromRequest(StockRequest request) => _generateCombinedNetworkAndSourceOfTruthStream( request, _sourceOfTruth == null ? CachedSourceOfTruth() : _sourceOfTruth!, ); - Stream> _generateCombinedNetworkAndSourceOfTruthStream( - StoreRequest request, + Stream> _generateCombinedNetworkAndSourceOfTruthStream( + StockRequest request, SourceOfTruth sourceOfTruth, ) async* { - final StreamController> controller = + final StreamController> controller = StreamController.broadcast(); final syncLock = Mutex(); await syncLock.acquire(); @@ -85,10 +86,10 @@ class StoreImpl implements Store { } StreamSubscription _generateNetworkStream({ - required StoreRequest request, + required StockRequest request, required SourceOfTruth? sourceOfTruth, required Mutex emitMutex, - required StreamController> dataStreamController, + required StreamController> dataStreamController, }) => Stream.fromFuture(_shouldStartNetworkStream( request, @@ -100,13 +101,13 @@ class StoreImpl implements Store { request, )) .listen((response) => emitMutex.protect(() async { - if (response is StoreResponseData) { + if (response is StockResponseData) { await _writingLock .protect(() async => _incrementWritingMap(request, 1)); var writerResult = await sourceOfTruth ?.write(request.key, response.value) .mapToResponse(ResponseOrigin.fetcher); - if (writerResult is StoreResponseError) { + if (writerResult is StockResponseError) { dataStreamController.add(writerResult.swapType()); await _writingLock .protect(() async => _incrementWritingMap(request, -1)); @@ -116,28 +117,28 @@ class StoreImpl implements Store { } })); - int _incrementWritingMap(StoreRequest request, int increment) => + int _incrementWritingMap(StockRequest request, int increment) => _writingMap[request.key] = (_writingMap[request.key] ?? 0) + increment; - Stream> _startNetworkFlow( + Stream> _startNetworkFlow( bool shouldFetchNewValue, - StreamController> dataStreamController, - StoreRequest request, + StreamController> dataStreamController, + StockRequest request, ) { if (shouldFetchNewValue) { dataStreamController - .add(StoreResponseLoading(ResponseOrigin.fetcher)); + .add(StockResponseLoading(ResponseOrigin.fetcher)); return _fetcher .factory(request.key) .mapToResponse(ResponseOrigin.fetcher); } else { - return Rx.never>(); + return Rx.never>(); } } Future _shouldStartNetworkStream( - StoreRequest request, - StreamController> dataStreamController, + StockRequest request, + StreamController> dataStreamController, ) async { if (request.refresh) { return true; @@ -146,21 +147,21 @@ class StoreImpl implements Store { .where((event) => event.origin == ResponseOrigin.sourceOfTruth) .where((event) => !event.isLoading) .first - .then((value) => value.data == null); + .then((value) => value.getDataOrNull() == null); } StreamSubscription _generateSourceOfTruthStreamSubscription({ - required StoreRequest request, + required StockRequest request, required SourceOfTruth sourceOfTruth, required Mutex dbLock, - required StreamController> dataStreamController, + required StreamController> dataStreamController, }) { var initialSyncDone = false; final sourceOfTruthSubscription = sourceOfTruth .reader(request.key) .mapToResponse(ResponseOrigin.sourceOfTruth) .listen((response) async { - if (response is StoreResponseData) { + if (response is StockResponseData) { final fetcherData = await _writingLock.protect(() async { final writingKeyData = (_writingMap[request.key] ?? -1) > 0; if (writingKeyData) { @@ -168,7 +169,7 @@ class StoreImpl implements Store { } return writingKeyData; }); - dataStreamController.add(StoreResponseData( + dataStreamController.add(StockResponseData( fetcherData ? ResponseOrigin.fetcher : response.origin, response.value, )); diff --git a/lib/source_of_truth.dart b/lib/src/source_of_truth.dart similarity index 77% rename from lib/source_of_truth.dart rename to lib/src/source_of_truth.dart index b1e4e9f..c56a8f9 100644 --- a/lib/source_of_truth.dart +++ b/lib/src/source_of_truth.dart @@ -1,15 +1,15 @@ import 'dart:async'; import 'package:flutter/widgets.dart'; -import 'package:stock/fetcher.dart'; -import 'package:stock/src/key_value.dart'; -import 'package:stock/src/source_of_truth_impl.dart'; -import 'package:stock/store.dart'; -import 'package:stock/store_extensions.dart'; +import 'package:stock/src/fetcher.dart'; +import 'package:stock/src/common/key_value.dart'; +import 'package:stock/src/implementations/source_of_truth_impl.dart'; +import 'package:stock/src/stock.dart'; +import 'package:stock/src/stock_extensions.dart'; /// -/// [SourceOfTruth], as its name implies, is the persistence API which [Store] uses to serve values to -/// the collectors. If provided, [Store] will only return values received from [SourceOfTruth] back +/// [SourceOfTruth], as its name implies, is the persistence API which [Stock] uses to serve values to +/// the collectors. If provided, [Stock] will only return values received from [SourceOfTruth] back /// to the collectors. /// /// In other words, values coming from the [Fetcher] will always be sent to the [SourceOfTruth] @@ -18,16 +18,16 @@ import 'package:stock/store_extensions.dart'; /// This round-trip ensures the data is consistent across the application in case the [Fetcher] does /// not return all fields or returns a different class type than the app uses. It is particularly /// useful if your application has a local observable database which is directly modified by the app, -/// as Store can observe these changes and update the collectors even before value is synced to the +/// as [Stock] can observe these changes and update the collectors even before value is synced to the /// backend. /// /// [SourceOfTruth] takes care of making any source (no matter if it has flowing reads or not) into /// a common flowing API. /// /// A source of truth is usually backed by local storage. Its purpose is to eliminate the need -/// for waiting on a network update before local modifications are available (via [Store.stream]). +/// for waiting on a network update before local modifications are available (via [Stock.stream]). /// -/// For maximal simplicity, [writer]'s record type ([T]] and [reader]'s record type +/// For maximal simplicity, [write]'s record type ([T]] and [reader]'s record type /// ([T]) are identical. However, sometimes reading one type of objects from network and /// transforming them to another type when placing them in local storage is needed. /// For this case you can use the [mapTo] and [mapToUsingMapper] extensions. @@ -43,15 +43,15 @@ abstract class SourceOfTruth { }) => SourceOfTruthImpl(reader, writer); - /// Used by [Store] to read records from the source of truth for the given [key]. + /// Used by [Stock] to read records from the source of truth for the given [key]. Stream reader(Key key); - /// Used by [Store] to write records **coming in from the fetcher (network)** to the source of + /// Used by [Stock] to write records **coming in from the fetcher (network)** to the source of /// truth. /// - /// **Note:** [Store] currently does not support updating the source of truth with local user + /// **Note:** [Stock] currently does not support updating the source of truth with local user /// updates (i.e writing record of type [T]). However, any changes in the local database - /// will still be visible via [Store.stream] APIs as long as you are using a local storage that + /// will still be visible via [Stock.stream] APIs as long as you are using a local storage that /// supports observability (e.g. Floor, Drift, Realm). Future write(Key key, T? value); } diff --git a/lib/store.dart b/lib/src/stock.dart similarity index 53% rename from lib/store.dart rename to lib/src/stock.dart index 77bf977..dc50164 100644 --- a/lib/store.dart +++ b/lib/src/stock.dart @@ -1,29 +1,29 @@ import 'dart:async'; -import 'package:stock/fetcher.dart'; -import 'package:stock/source_of_truth.dart'; -import 'package:stock/src/store_impl.dart'; -import 'package:stock/store_response.dart'; +import 'package:stock/src/fetcher.dart'; +import 'package:stock/src/source_of_truth.dart'; +import 'package:stock/src/implementations/stock_impl.dart'; +import 'package:stock/src/stock_response.dart'; -/// A [Store] is responsible for managing a particular data request. +/// A [Stock] is responsible for managing a particular data request. /// -/// When you create an implementation of a [Store], you provide it with a [Fetcher], a function that defines how data will be fetched over network. +/// When you create an implementation of a [Stock], you provide it with a [Fetcher], a function that defines how data will be fetched over network. /// /// This [SourceOfTruth] is either a real database or an in memory source of truth. -/// Its purpose is to eliminate the need for waiting on a network update before local modifications are available (via [Store.stream]). +/// Its purpose is to eliminate the need for waiting on a network update before local modifications are available (via [Stock.stream]). /// -abstract class Store { - factory Store({ +abstract class Stock { + factory Stock({ required Fetcher fetcher, required SourceOfTruth? sourceOfTruth, }) => - StoreImpl(fetcher: fetcher, sourceOfTruth: sourceOfTruth); + StockImpl(fetcher: fetcher, sourceOfTruth: sourceOfTruth); /// Returns a [Stream] for the given key. /// [refresh] is used to ensure a fresh value from the [Fetcher], - /// If it's `false`, [Store] will try to return the [SourceOfTruth] data, - /// and if it doesn't exist, [Store] will request fresh data using the [Fetcher]. - Stream> stream(Key key, {refresh = true}); + /// If it's `false`, [Stock] will try to return the [SourceOfTruth] data, + /// and if it doesn't exist, [Stock] will request fresh data using the [Fetcher]. + Stream> stream(Key key, {refresh = true}); /// Helper factory that will return fresh data for [key] while updating your cache Future fresh(Key key); diff --git a/lib/store_extensions.dart b/lib/src/stock_extensions.dart similarity index 90% rename from lib/store_extensions.dart rename to lib/src/stock_extensions.dart index 5a37485..dd9c292 100644 --- a/lib/store_extensions.dart +++ b/lib/src/stock_extensions.dart @@ -1,11 +1,11 @@ -import 'package:stock/type_mapper.dart'; +import 'package:stock/src/type_mapper.dart'; import 'source_of_truth.dart'; extension SourceOfTruthExtensions on SourceOfTruth { /// Transforms a [SourceOfTruth] of [Key], [Input] into a [SourceOfTruth] of [Key], [Output]. SourceOfTruth mapToUsingMapper( - StoreTypeMapper mapper, + StockTypeMapper mapper, ) => mapTo(mapper.fromInput, mapper.fromOutput); diff --git a/lib/src/stock_request.dart b/lib/src/stock_request.dart new file mode 100644 index 0000000..11b4e31 --- /dev/null +++ b/lib/src/stock_request.dart @@ -0,0 +1,21 @@ +import 'package:stock/src/fetcher.dart'; +import 'package:stock/src/source_of_truth.dart'; +import 'package:stock/src/stock.dart'; + +/// Represents a single [Stock] request +/// +/// The [key] is a unique identifier for your data +/// +/// If [refresh] is `true`, [Stock] will always get fresh value from fetcher. +/// If it's `false`, [Stock] will try to return the [SourceOfTruth] data, +/// and if it doesn't exist, [Stock] will request fresh data using the [Fetcher]. +/// +class StockRequest { + Key key; + bool refresh; + + StockRequest({ + required this.key, + required this.refresh, + }); +} diff --git a/lib/store_response.dart b/lib/src/stock_response.dart similarity index 51% rename from lib/store_response.dart rename to lib/src/stock_response.dart index 92bd811..11c90a9 100644 --- a/lib/store_response.dart +++ b/lib/src/stock_response.dart @@ -1,44 +1,45 @@ -import 'package:stock/fetcher.dart'; -import 'package:stock/source_of_truth.dart'; -import 'package:stock/store.dart'; +import 'package:stock/src/fetcher.dart'; +import 'package:stock/src/source_of_truth.dart'; +import 'package:stock/src/stock.dart'; -/// Holder for responses from Store. +/// Holder for responses from Stock. /// -/// Instead of using regular error channels (a.k.a. throwing exceptions), Store uses this holder +/// Instead of using regular error channels (a.k.a. throwing exceptions), Stock uses this holder /// class to represent each response. This allows the [Stream] to keep flowing even if an error happens /// so that if there is an observable single source of truth, the application can keep observing it. -class StoreResponse { +class StockResponse { final ResponseOrigin origin; - const StoreResponse._(this.origin); + const StockResponse._(this.origin); - /// Loading event dispatched by [Store] to signal the [Fetcher] is currently running. - const factory StoreResponse.loading(ResponseOrigin origin) = - StoreResponseLoading; + /// Loading event dispatched by [Stock] to signal the [Fetcher] is currently running. + const factory StockResponse.loading(ResponseOrigin origin) = + StockResponseLoading; - /// Data dispatched by [Store] - const factory StoreResponse.data(ResponseOrigin origin, Output value) = - StoreResponseData; + /// Data dispatched by [Stock] + const factory StockResponse.data(ResponseOrigin origin, Output value) = + StockResponseData; /// Error dispatched by a pipeline - const factory StoreResponse.error( + const factory StockResponse.error( ResponseOrigin origin, Object error, [ StackTrace? stackTrace, - ]) = StoreResponseError; + ]) = StockResponseError; } -class StoreResponseLoading extends StoreResponse { - const StoreResponseLoading(origin) : super._(origin); +/// Loading event dispatched by [Stock] to signal the [Fetcher] is currently running. +class StockResponseLoading extends StockResponse { + const StockResponseLoading(origin) : super._(origin); @override - String toString() => 'StoreResponse<$T>.loading(origin: $origin)'; + String toString() => 'StockResponse<$T>.loading(origin: $origin)'; @override bool operator ==(dynamic other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is StoreResponseLoading && + other is StockResponseLoading && origin == other.origin); } @@ -46,19 +47,20 @@ class StoreResponseLoading extends StoreResponse { int get hashCode => Object.hash(runtimeType, origin.hashCode); } -class StoreResponseData extends StoreResponse { +/// Data dispatched by [Stock] +class StockResponseData extends StockResponse { final T value; - const StoreResponseData(ResponseOrigin origin, this.value) : super._(origin); + const StockResponseData(ResponseOrigin origin, this.value) : super._(origin); @override - String toString() => 'StoreResponse<$T>.data(origin: $origin, value: $value)'; + String toString() => 'StockResponse<$T>.data(origin: $origin, value: $value)'; @override bool operator ==(dynamic other) => identical(this, other) || (other.runtimeType == runtimeType && - other is StoreResponseData && + other is StockResponseData && other.origin == origin && other.value == value); @@ -66,22 +68,23 @@ class StoreResponseData extends StoreResponse { int get hashCode => Object.hash(runtimeType, origin.hashCode, value.hashCode); } -class StoreResponseError extends StoreResponse { +/// Error dispatched by a pipeline +class StockResponseError extends StockResponse { final Object error; final StackTrace? stackTrace; - const StoreResponseError(ResponseOrigin origin, this.error, [this.stackTrace]) + const StockResponseError(ResponseOrigin origin, this.error, [this.stackTrace]) : super._(origin); @override String toString() => - 'StoreResponse<$T>.error(origin: $origin, error: $error, stackTrace: $stackTrace)'; + 'StockResponse<$T>.error(origin: $origin, error: $error, stackTrace: $stackTrace)'; @override bool operator ==(dynamic other) => identical(this, other) || (other.runtimeType == runtimeType && - other is StoreResponseError && + other is StockResponseError && other.origin == origin && other.error == error && other.stackTrace == stackTrace); @@ -95,11 +98,11 @@ class StoreResponseError extends StoreResponse { ); } -/// Represents the origin for a [StoreResponse]. +/// Represents the origin for a [StockResponse]. enum ResponseOrigin { - /// [StoreResponse] is sent from the [SourceOfTruth] + /// [StockResponse] is sent from the [SourceOfTruth] sourceOfTruth, - /// [StoreResponse] is sent from a [Fetcher], + /// [StockResponse] is sent from a [Fetcher], fetcher, } diff --git a/lib/src/stock_response_extensions.dart b/lib/src/stock_response_extensions.dart new file mode 100644 index 0000000..e50b9ab --- /dev/null +++ b/lib/src/stock_response_extensions.dart @@ -0,0 +1,39 @@ +import 'package:stock/src/errors.dart'; +import 'package:stock/src/stock_response.dart'; + +extension StockResponseExtensions on StockResponse { + /// Returns the available data or throws error if there is no data. + T requireData() { + if (this is StockResponseData) { + return (this as StockResponseData).value; + } else if (isError) { + throw (this as StockResponseError).error; + } else if (isLoading) { + throw StockError('There is no data in loading'); + } else { + throw StockError('Unknown type'); + } + } + + /// If there is data available, returns it; otherwise returns null. + T? getDataOrNull() => this is StockResponseData + ? (this as StockResponseData).value + : null; + + /// If this [StockResponse] is of type [StockResponseError], throws the exception + /// Otherwise, does nothing. + void throwIfError() { + if (this is StockResponseError) { + throw (this as StockResponseError).error; + } + } + + /// Returns if the response is a [StockResponseLoading] + bool get isLoading => this is StockResponseLoading; + + /// Returns if the response is a [StockResponseError] + bool get isError => this is StockResponseError; + + /// Returns if the response is a [StockResponseData] + bool get isData => this is StockResponseData; +} diff --git a/lib/type_mapper.dart b/lib/src/type_mapper.dart similarity index 80% rename from lib/type_mapper.dart rename to lib/src/type_mapper.dart index 5a5f1b1..da950af 100644 --- a/lib/type_mapper.dart +++ b/lib/src/type_mapper.dart @@ -1,6 +1,6 @@ /// Converts the [Input] type to the [Output] and vice versa. /// Used to transform DB entity to network entity and vice versa. -abstract class StoreTypeMapper { +abstract class StockTypeMapper { Output fromInput(Input value); Input fromOutput(Output value); diff --git a/lib/stock.dart b/lib/stock.dart new file mode 100644 index 0000000..8e2278a --- /dev/null +++ b/lib/stock.dart @@ -0,0 +1,14 @@ +/// A Dart package for Async Data Loading and Caching, +/// +/// A open source project made with ❤️ by [Xmartlabs](https://xmartlabs.com). +library stock; + +export 'src/errors.dart'; +export 'src/fetcher.dart'; +export 'src/source_of_truth.dart'; +export 'src/stock.dart'; +export 'src/stock_extensions.dart'; +export 'src/stock_request.dart'; +export 'src/stock_response.dart'; +export 'src/stock_response_extensions.dart'; +export 'src/type_mapper.dart'; diff --git a/lib/store_request.dart b/lib/store_request.dart deleted file mode 100644 index 503df18..0000000 --- a/lib/store_request.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:stock/fetcher.dart'; -import 'package:stock/source_of_truth.dart'; -import 'package:stock/store.dart'; - -/// Represents a single store request -/// -/// The [key] is a unique identifier for your data -/// -/// If [refresh] is `true`, [Store] will always get fresh value from fetcher. -/// If it's `false`, [Store] will try to return the [SourceOfTruth] data, -/// and if it doesn't exist, [Store] will request fresh data using the [Fetcher]. -/// -class StoreRequest { - Key key; - bool refresh; - - StoreRequest({ - required this.key, - required this.refresh, - }); -} diff --git a/test/common/response_extensions.dart b/test/common/response_extensions.dart index f8a3bbc..baef1ea 100644 --- a/test/common/response_extensions.dart +++ b/test/common/response_extensions.dart @@ -1,7 +1,7 @@ -import 'package:stock/store_response.dart'; +import 'package:stock/src/stock_response.dart'; -extension ResponseExtensions on StoreResponse { - StoreResponse removeStacktraceIfNeeded() => this is StoreResponseError - ? StoreResponse.error(origin, (this as StoreResponseError).error) +extension ResponseExtensions on StockResponse { + StockResponse removeStacktraceIfNeeded() => this is StockResponseError + ? StockResponse.error(origin, (this as StockResponseError).error) : this; } diff --git a/test/common/source_of_truth/cached_and_mocked_source_of_truth.dart b/test/common/source_of_truth/cached_and_mocked_source_of_truth.dart index a4627e1..3001680 100644 --- a/test/common/source_of_truth/cached_and_mocked_source_of_truth.dart +++ b/test/common/source_of_truth/cached_and_mocked_source_of_truth.dart @@ -1,6 +1,6 @@ import 'package:mockito/mockito.dart'; import 'package:rxdart/rxdart.dart'; -import 'package:stock/source_of_truth.dart'; +import 'package:stock/src/source_of_truth.dart'; import '../../common_mocks.mocks.dart'; import 'cached_source_of_truth_with_default_value.dart'; diff --git a/test/common/source_of_truth/cached_source_of_truth_with_default_value.dart b/test/common/source_of_truth/cached_source_of_truth_with_default_value.dart index 7103ea7..b03336e 100644 --- a/test/common/source_of_truth/cached_source_of_truth_with_default_value.dart +++ b/test/common/source_of_truth/cached_source_of_truth_with_default_value.dart @@ -1,5 +1,5 @@ import 'package:rxdart/rxdart.dart'; -import 'package:stock/source_of_truth.dart'; +import 'package:stock/src/source_of_truth.dart'; class CachedSourceOfTruthWithDefaultValue extends CachedSourceOfTruth { diff --git a/test/common/store_test_extensions.dart b/test/common/stock_test_extensions.dart similarity index 79% rename from test/common/store_test_extensions.dart rename to test/common/stock_test_extensions.dart index 571ca58..acab87b 100644 --- a/test/common/store_test_extensions.dart +++ b/test/common/stock_test_extensions.dart @@ -1,12 +1,12 @@ import 'dart:async'; -import 'package:stock/store.dart'; -import 'package:stock/store_response.dart'; +import 'package:stock/src/stock.dart'; +import 'package:stock/src/stock_response.dart'; import 'response_extensions.dart'; -extension StoreExtensions on Store { - Future>> getFreshResult( +extension StockExtensions on Stock { + Future>> getFreshResult( Key key, { refresh = true, Duration delay = const Duration(milliseconds: 100), @@ -16,7 +16,7 @@ extension StoreExtensions on Store { return await resultListener.stopAndGetResult(); } - Future>> getFreshResultRemovingErrorStackTraces( + Future>> getFreshResultRemovingErrorStackTraces( Key key, { refresh = true, Duration delay = const Duration(milliseconds: 100), diff --git a/test/common_errors_test.dart b/test/common_errors_test.dart index ecdbb62..e2096b6 100644 --- a/test/common_errors_test.dart +++ b/test/common_errors_test.dart @@ -1,30 +1,30 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:stock/fetcher.dart'; -import 'package:stock/store.dart'; -import 'package:stock/store_response.dart'; +import 'package:stock/src/fetcher.dart'; +import 'package:stock/src/stock.dart'; +import 'package:stock/src/stock_response.dart'; import 'common/source_of_truth/source_of_truth_with_error.dart'; -import 'common/store_test_extensions.dart'; +import 'common/stock_test_extensions.dart'; void main() { - group("Store requests with errors", () { + group("Stock requests with errors", () { test('Source of truth with read error and fetcher ok', () async { final sourceOfTruth = SourceOfTruthWithError(-1, throwReadErrorCount: 1); final fetcher = Fetcher.ofFuture((int key) async => 1); - final store = Store( + final stock = Stock( fetcher: fetcher, sourceOfTruth: sourceOfTruth, ); - final resultList = await store.getFreshResultRemovingErrorStackTraces(1); + final resultList = await stock.getFreshResultRemovingErrorStackTraces(1); expect( resultList, equals([ - const StoreResponseLoading(ResponseOrigin.fetcher), - StoreResponseError(ResponseOrigin.sourceOfTruth, + const StockResponseLoading(ResponseOrigin.fetcher), + StockResponseError(ResponseOrigin.sourceOfTruth, SourceOfTruthWithError.readException), - const StoreResponse.data(ResponseOrigin.fetcher, 1), + const StockResponse.data(ResponseOrigin.fetcher, 1), ])); }); @@ -32,18 +32,18 @@ void main() { final sourceOfTruth = SourceOfTruthWithError(-1, throwWriteErrorCount: 1); final fetcher = Fetcher.ofFuture((int key) async => 1); - final store = Store( + final stock = Stock( fetcher: fetcher, sourceOfTruth: sourceOfTruth, ); - final resultList = await store.getFreshResultRemovingErrorStackTraces(1); + final resultList = await stock.getFreshResultRemovingErrorStackTraces(1); expect( resultList, equals([ - const StoreResponseLoading(ResponseOrigin.fetcher), - const StoreResponse.data(ResponseOrigin.sourceOfTruth, -1), - StoreResponseError( + const StockResponseLoading(ResponseOrigin.fetcher), + const StockResponse.data(ResponseOrigin.sourceOfTruth, -1), + StockResponseError( ResponseOrigin.fetcher, SourceOfTruthWithError.writeException), ])); }); @@ -52,18 +52,18 @@ void main() { final sourceOfTruth = SourceOfTruthWithError(-1); final fetcherError = Exception('Fetcher error'); final fetcher = Fetcher.ofFuture((int key) async => throw fetcherError); - final store = Store( + final stock = Stock( fetcher: fetcher, sourceOfTruth: sourceOfTruth, ); - final resultList = await store.getFreshResultRemovingErrorStackTraces(1); + final resultList = await stock.getFreshResultRemovingErrorStackTraces(1); expect( resultList, equals([ - const StoreResponseLoading(ResponseOrigin.fetcher), - const StoreResponse.data(ResponseOrigin.sourceOfTruth, -1), - StoreResponseError(ResponseOrigin.fetcher, fetcherError), + const StockResponseLoading(ResponseOrigin.fetcher), + const StockResponse.data(ResponseOrigin.sourceOfTruth, -1), + StockResponseError(ResponseOrigin.fetcher, fetcherError), ])); }); }); diff --git a/test/common_mocks.dart b/test/common_mocks.dart index 25d77c0..b63c3d1 100644 --- a/test/common_mocks.dart +++ b/test/common_mocks.dart @@ -1,6 +1,6 @@ import 'package:mockito/annotations.dart'; -import 'package:stock/source_of_truth.dart'; -import 'package:stock/src/factory_fetcher.dart'; +import 'package:stock/src/source_of_truth.dart'; +import 'package:stock/src/implementations/factory_fetcher.dart'; @GenerateMocks([ FutureFetcher, diff --git a/test/common_mocks.mocks.dart b/test/common_mocks.mocks.dart index 79f9d17..2bf9f82 100644 --- a/test/common_mocks.mocks.dart +++ b/test/common_mocks.mocks.dart @@ -6,8 +6,8 @@ import 'dart:async' as _i3; import 'package:mockito/mockito.dart' as _i1; -import 'package:stock/source_of_truth.dart' as _i4; -import 'package:stock/src/factory_fetcher.dart' as _i2; +import 'package:stock/src/source_of_truth.dart' as _i4; +import 'package:stock/src/implementations/factory_fetcher.dart' as _i2; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values diff --git a/test/fresh_and_get_test.dart b/test/fresh_and_get_test.dart index 39e7ddc..7bc2e1c 100644 --- a/test/fresh_and_get_test.dart +++ b/test/fresh_and_get_test.dart @@ -1,6 +1,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; -import 'package:stock/store.dart'; +import 'package:stock/src/stock.dart'; import 'common/source_of_truth/cached_and_mocked_source_of_truth.dart'; import 'common_mocks.mocks.dart'; @@ -13,12 +13,12 @@ void main() { final sourceOfTruth = createMockedSourceOfTruthFromMethods( (key) => Stream.value(-1), (key, output) => Future.value()); - final store = Store( + final stock = Stock( fetcher: fetcher, sourceOfTruth: sourceOfTruth, ); - final result = await store.fresh(1); + final result = await stock.fresh(1); expect(result, equals(1)); verifyNever(sourceOfTruth.reader(any)); verify(sourceOfTruth.write(any, any)).called(1); @@ -31,12 +31,12 @@ void main() { when(fetcher.factory).thenReturn((key) => Stream.value(1)); final sourceOfTruth = createMockedSourceOfTruthFromMethods( (key) => Stream.value(-1), (key, output) => Future.value()); - final store = Store( + final stock = Stock( fetcher: fetcher, sourceOfTruth: sourceOfTruth, ); - final result = await store.get(1); + final result = await stock.get(1); expect(result, equals(-1)); verifyNever(fetcher.factory); verify(sourceOfTruth.reader(any)).called(1); @@ -48,12 +48,12 @@ void main() { var fetcher = MockFutureFetcher(); when(fetcher.factory).thenReturn((key) => Stream.value(1)); final sourceOfTruth = createMockedSourceOfTruth(); - final store = Store( + final stock = Stock( fetcher: fetcher, sourceOfTruth: sourceOfTruth, ); - final result = await store.get(1); + final result = await stock.get(1); expect(result, equals(1)); verify(fetcher.factory).called(1); verify(sourceOfTruth.reader(any)).called(1); diff --git a/test/mapper_test.dart b/test/mapper_test.dart index 12c91d3..a08aa7e 100644 --- a/test/mapper_test.dart +++ b/test/mapper_test.dart @@ -1,7 +1,7 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:stock/source_of_truth.dart'; -import 'package:stock/store_extensions.dart'; -import 'package:stock/type_mapper.dart'; +import 'package:stock/src/source_of_truth.dart'; +import 'package:stock/src/stock_extensions.dart'; +import 'package:stock/src/type_mapper.dart'; void main() { group("Mapper test", () { @@ -33,7 +33,7 @@ void main() { }); } -class _IntStringMapper implements StoreTypeMapper { +class _IntStringMapper implements StockTypeMapper { @override String fromInput(int value) => value.toString(); diff --git a/test/multiple_request_test.dart b/test/multiple_request_test.dart index 96ec55a..448cb6e 100644 --- a/test/multiple_request_test.dart +++ b/test/multiple_request_test.dart @@ -1,10 +1,10 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:stock/fetcher.dart'; -import 'package:stock/store.dart'; -import 'package:stock/store_response.dart'; +import 'package:stock/src/fetcher.dart'; +import 'package:stock/src/stock.dart'; +import 'package:stock/src/stock_response.dart'; import 'common/source_of_truth/cached_and_mocked_source_of_truth.dart'; -import 'common/store_test_extensions.dart'; +import 'common/stock_test_extensions.dart'; void main() { group('Multiple requests', () { @@ -12,27 +12,27 @@ void main() { final sourceOfTruth = createMockedSourceOfTruthWithDefaultNegativeIntKey(); final fetcher = Fetcher.ofFuture((int key) async => key); - final store = Store( + final stock = Stock( fetcher: fetcher, sourceOfTruth: sourceOfTruth, ); - var resultList = await store.getFreshResultRemovingErrorStackTraces(1); + var resultList = await stock.getFreshResultRemovingErrorStackTraces(1); expect( resultList, equals([ - const StoreResponseLoading(ResponseOrigin.fetcher), - const StoreResponse.data(ResponseOrigin.sourceOfTruth, -1), - const StoreResponse.data(ResponseOrigin.fetcher, 1), + const StockResponseLoading(ResponseOrigin.fetcher), + const StockResponse.data(ResponseOrigin.sourceOfTruth, -1), + const StockResponse.data(ResponseOrigin.fetcher, 1), ])); - resultList = await store.getFreshResultRemovingErrorStackTraces(2); + resultList = await stock.getFreshResultRemovingErrorStackTraces(2); expect( resultList, equals([ - const StoreResponseLoading(ResponseOrigin.fetcher), - const StoreResponse.data(ResponseOrigin.sourceOfTruth, -2), - const StoreResponse.data(ResponseOrigin.fetcher, 2), + const StockResponseLoading(ResponseOrigin.fetcher), + const StockResponse.data(ResponseOrigin.sourceOfTruth, -2), + const StockResponse.data(ResponseOrigin.fetcher, 2), ])); }); }); diff --git a/test/refresh_test.dart b/test/refresh_test.dart index 2aeb73a..102016b 100644 --- a/test/refresh_test.dart +++ b/test/refresh_test.dart @@ -1,12 +1,12 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; -import 'package:stock/source_of_truth.dart'; -import 'package:stock/store.dart'; -import 'package:stock/store_response.dart'; +import 'package:stock/src/source_of_truth.dart'; +import 'package:stock/src/stock.dart'; +import 'package:stock/src/stock_response.dart'; import 'common/source_of_truth/cached_source_of_truth_with_default_value.dart'; import 'common/source_of_truth/source_of_truth_with_error.dart'; -import 'common/store_test_extensions.dart'; +import 'common/stock_test_extensions.dart'; import 'common_mocks.mocks.dart'; void main() { @@ -16,16 +16,16 @@ void main() { var fetcher = MockFutureFetcher(); when(fetcher.factory).thenReturn((key) => Stream.value(1)); final sourceOfTruth = CachedSourceOfTruthWithDefaultValue(-1); - final store = Store( + final stock = Stock( fetcher: fetcher, sourceOfTruth: sourceOfTruth, ); - final resultList = await store.getFreshResult(1, refresh: false); + final resultList = await stock.getFreshResult(1, refresh: false); expect( resultList, equals([ - const StoreResponse.data(ResponseOrigin.sourceOfTruth, -1), + const StockResponse.data(ResponseOrigin.sourceOfTruth, -1), ])); verifyNever(fetcher.factory); }); @@ -34,18 +34,18 @@ void main() { var fetcher = MockFutureFetcher(); when(fetcher.factory).thenReturn((key) => Stream.value(1)); final sourceOfTruth = CachedSourceOfTruthWithDefaultValue(-1); - final store = Store( + final stock = Stock( fetcher: fetcher, sourceOfTruth: sourceOfTruth, ); - final resultList = await store.getFreshResult(1, refresh: true); + final resultList = await stock.getFreshResult(1, refresh: true); expect( resultList, equals([ - const StoreResponseLoading(ResponseOrigin.fetcher), - const StoreResponse.data(ResponseOrigin.sourceOfTruth, -1), - const StoreResponse.data(ResponseOrigin.fetcher, 1), + const StockResponseLoading(ResponseOrigin.fetcher), + const StockResponse.data(ResponseOrigin.sourceOfTruth, -1), + const StockResponse.data(ResponseOrigin.fetcher, 1), ])); verify(fetcher.factory).called(1); }); @@ -55,17 +55,17 @@ void main() { var fetcher = MockFutureFetcher(); when(fetcher.factory).thenReturn((key) => Stream.value(1)); final sourceOfTruth = CachedSourceOfTruth(null); - final store = Store( + final stock = Stock( fetcher: fetcher, sourceOfTruth: sourceOfTruth, ); - final resultList = await store.getFreshResult(1, refresh: false); + final resultList = await stock.getFreshResult(1, refresh: false); expect( resultList, equals([ - const StoreResponseLoading(ResponseOrigin.fetcher), - const StoreResponse.data(ResponseOrigin.fetcher, 1), + const StockResponseLoading(ResponseOrigin.fetcher), + const StockResponse.data(ResponseOrigin.fetcher, 1), ])); verify(fetcher.factory).called(1); }); @@ -76,20 +76,20 @@ void main() { when(fetcher.factory).thenReturn((key) => Stream.value(1)); final sourceOfTruth = SourceOfTruthWithError(null, throwReadErrorCount: 1); - final store = Store( + final stock = Stock( fetcher: fetcher, sourceOfTruth: sourceOfTruth, ); final resultList = - await store.getFreshResultRemovingErrorStackTraces(1, refresh: false); + await stock.getFreshResultRemovingErrorStackTraces(1, refresh: false); expect( resultList, equals([ - StoreResponseError(ResponseOrigin.sourceOfTruth, + StockResponseError(ResponseOrigin.sourceOfTruth, SourceOfTruthWithError.readException), - const StoreResponseLoading(ResponseOrigin.fetcher), - const StoreResponse.data(ResponseOrigin.fetcher, 1), + const StockResponseLoading(ResponseOrigin.fetcher), + const StockResponse.data(ResponseOrigin.fetcher, 1), ])); verify(fetcher.factory).called(1); }); @@ -100,20 +100,20 @@ void main() { when(fetcher.factory).thenReturn((key) => Stream.value(1)); final sourceOfTruth = SourceOfTruthWithError(null, throwReadErrorCount: 1); - final store = Store( + final stock = Stock( fetcher: fetcher, sourceOfTruth: sourceOfTruth, ); final resultList = - await store.getFreshResultRemovingErrorStackTraces(1, refresh: true); + await stock.getFreshResultRemovingErrorStackTraces(1, refresh: true); expect( resultList, equals([ - const StoreResponseLoading(ResponseOrigin.fetcher), - StoreResponseError(ResponseOrigin.sourceOfTruth, + const StockResponseLoading(ResponseOrigin.fetcher), + StockResponseError(ResponseOrigin.sourceOfTruth, SourceOfTruthWithError.readException), - const StoreResponse.data(ResponseOrigin.fetcher, 1), + const StockResponse.data(ResponseOrigin.fetcher, 1), ])); verify(fetcher.factory).called(1); }); diff --git a/test/source_of_truth_test.dart b/test/source_of_truth_test.dart index 7178524..db70664 100644 --- a/test/source_of_truth_test.dart +++ b/test/source_of_truth_test.dart @@ -1,7 +1,7 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:stock/source_of_truth.dart'; +import 'package:stock/src/source_of_truth.dart'; -import 'common/store_test_extensions.dart'; +import 'common/stock_test_extensions.dart'; void main() { group("Cached SOT", () { diff --git a/test/store_response_extension_test.dart b/test/stock_response_extension_test.dart similarity index 62% rename from test/store_response_extension_test.dart rename to test/stock_response_extension_test.dart index 68f0834..d71882b 100644 --- a/test/store_response_extension_test.dart +++ b/test/stock_response_extension_test.dart @@ -1,20 +1,20 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:stock/errors.dart'; -import 'package:stock/src/extensions/store_response_extensions.dart'; -import 'package:stock/store_response.dart'; +import 'package:stock/src/errors.dart'; +import 'package:stock/src/stock_response.dart'; +import 'package:stock/src/stock_response_extensions.dart'; void main() { group('Require data extensions', () { test('requireData of an error throws the exception', () async { final customEx = Exception("Custom ex"); expect( - StoreResponse.error(ResponseOrigin.fetcher, customEx).requireData, + StockResponse.error(ResponseOrigin.fetcher, customEx).requireData, throwsA((e) => e == customEx), ); }); test('requireData of a loading response throws a exception', () async { expect( - const StoreResponse.loading(ResponseOrigin.fetcher).requireData, + const StockResponse.loading(ResponseOrigin.fetcher).requireData, throwsA( (e) => e is StockError && @@ -24,7 +24,7 @@ void main() { }); test('requireData of a data returns the data', () async { expect( - const StoreResponse.data(ResponseOrigin.fetcher, 1).requireData(), + const StockResponse.data(ResponseOrigin.fetcher, 1).requireData(), equals(1), ); }); @@ -33,19 +33,19 @@ void main() { test('getData of an error returns null', () async { final customEx = Exception("Custom ex"); expect( - StoreResponse.error(ResponseOrigin.fetcher, customEx).data, + StockResponse.error(ResponseOrigin.fetcher, customEx).getDataOrNull(), equals(null), ); }); test('getData of a loading response returns null', () async { expect( - const StoreResponse.loading(ResponseOrigin.fetcher).data, + const StockResponse.loading(ResponseOrigin.fetcher).getDataOrNull(), equals(null), ); }); test('getData of a data response returns the data', () async { expect( - const StoreResponse.data(ResponseOrigin.fetcher, 1).data, + const StockResponse.data(ResponseOrigin.fetcher, 1).getDataOrNull(), equals(1), ); }); @@ -53,29 +53,29 @@ void main() { group('Property extensions', () { test('Loading returns true if loading', () async { expect( - StoreResponse.error(ResponseOrigin.fetcher, Error()).isLoading, + StockResponse.error(ResponseOrigin.fetcher, Error()).isLoading, equals(false), ); expect( - const StoreResponse.data(ResponseOrigin.fetcher, 1).isLoading, + const StockResponse.data(ResponseOrigin.fetcher, 1).isLoading, equals(false), ); expect( - const StoreResponse.loading(ResponseOrigin.fetcher).isLoading, + const StockResponse.loading(ResponseOrigin.fetcher).isLoading, equals(true), ); }); test('Error returns true if the response is an error', () async { expect( - StoreResponse.error(ResponseOrigin.fetcher, Error()).isError, + StockResponse.error(ResponseOrigin.fetcher, Error()).isError, equals(true), ); expect( - const StoreResponse.data(ResponseOrigin.fetcher, 1).isError, + const StockResponse.data(ResponseOrigin.fetcher, 1).isError, equals(false), ); expect( - const StoreResponse.loading(ResponseOrigin.fetcher).isError, + const StockResponse.loading(ResponseOrigin.fetcher).isError, equals(false), ); }); diff --git a/test/store_without_key_test.dart b/test/stock_without_key_test.dart similarity index 57% rename from test/store_without_key_test.dart rename to test/stock_without_key_test.dart index fe54e59..5d5166c 100644 --- a/test/store_without_key_test.dart +++ b/test/stock_without_key_test.dart @@ -1,30 +1,30 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; -import 'package:stock/store.dart'; -import 'package:stock/store_response.dart'; +import 'package:stock/src/stock.dart'; +import 'package:stock/src/stock_response.dart'; import 'common/source_of_truth/cached_source_of_truth_with_default_value.dart'; -import 'common/store_test_extensions.dart'; +import 'common/stock_test_extensions.dart'; import 'common_mocks.mocks.dart'; void main() { - group('Store without specific key', () { + group('Stock without specific key', () { test('Simple request using dynamic', () async { var fetcher = MockFutureFetcher(); when(fetcher.factory).thenReturn((_) => Stream.value(1)); final sourceOfTruth = CachedSourceOfTruthWithDefaultValue(-1); - final store = Store( + final stock = Stock( fetcher: fetcher, sourceOfTruth: sourceOfTruth, ); - final resultList = await store.getFreshResult(null); + final resultList = await stock.getFreshResult(null); expect( resultList, equals([ - const StoreResponseLoading(ResponseOrigin.fetcher), - const StoreResponse.data(ResponseOrigin.sourceOfTruth, -1), - const StoreResponse.data(ResponseOrigin.fetcher, 1), + const StockResponseLoading(ResponseOrigin.fetcher), + const StockResponse.data(ResponseOrigin.sourceOfTruth, -1), + const StockResponse.data(ResponseOrigin.fetcher, 1), ])); }); }); diff --git a/test/store_valid_result_store_test.dart b/test/store_valid_result_store_test.dart index e649986..b1c02d3 100644 --- a/test/store_valid_result_store_test.dart +++ b/test/store_valid_result_store_test.dart @@ -1,50 +1,50 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:stock/fetcher.dart'; -import 'package:stock/store.dart'; -import 'package:stock/store_response.dart'; +import 'package:stock/src/fetcher.dart'; +import 'package:stock/src/stock.dart'; +import 'package:stock/src/stock_response.dart'; import 'common/source_of_truth/cached_source_of_truth_with_default_value.dart'; import 'common/source_of_truth/source_of_truth_with_delay.dart'; -import 'common/store_test_extensions.dart'; +import 'common/stock_test_extensions.dart'; void main() { group("Valid results", () { test('Source of truth and fetcher are called', () async { final sourceOfTruth = CachedSourceOfTruthWithDefaultValue(-1); final fetcher = Fetcher.ofFuture((int key) async => 1); - final store = Store( + final stock = Stock( fetcher: fetcher, sourceOfTruth: sourceOfTruth, ); - final resultList = await store.getFreshResult(1); + final resultList = await stock.getFreshResult(1); expect( resultList, equals([ - const StoreResponseLoading(ResponseOrigin.fetcher), - const StoreResponse.data(ResponseOrigin.sourceOfTruth, -1), - const StoreResponse.data(ResponseOrigin.fetcher, 1), + const StockResponseLoading(ResponseOrigin.fetcher), + const StockResponse.data(ResponseOrigin.sourceOfTruth, -1), + const StockResponse.data(ResponseOrigin.fetcher, 1), ])); }); test('Source of truth data is returned before fetcher data', () async { final sourceOfTruth = DelayedSourceOfTruth(-1); final fetcher = Fetcher.ofFuture((int key) async => 1); - final store = Store( + final stock = Stock( fetcher: fetcher, sourceOfTruth: sourceOfTruth, ); - final resultList = await store.getFreshResult( + final resultList = await stock.getFreshResult( 1, delay: const Duration(milliseconds: 500), ); expect( resultList, equals([ - const StoreResponseLoading(ResponseOrigin.fetcher), - const StoreResponse.data(ResponseOrigin.sourceOfTruth, -1), - const StoreResponse.data(ResponseOrigin.fetcher, 1), + const StockResponseLoading(ResponseOrigin.fetcher), + const StockResponse.data(ResponseOrigin.sourceOfTruth, -1), + const StockResponse.data(ResponseOrigin.fetcher, 1), ])); }); @@ -52,39 +52,39 @@ void main() { final sourceOfTruth = CachedSourceOfTruthWithDefaultValue(-1); final fetcher = Fetcher.ofStream((int key) => Stream.fromIterable([1, 2, 3])); - final store = Store( + final stock = Stock( fetcher: fetcher, sourceOfTruth: sourceOfTruth, ); - final resultList = await store.getFreshResult(1); + final resultList = await stock.getFreshResult(1); expect( resultList, equals([ - const StoreResponseLoading(ResponseOrigin.fetcher), - const StoreResponse.data(ResponseOrigin.sourceOfTruth, -1), - const StoreResponse.data(ResponseOrigin.fetcher, 1), - const StoreResponse.data(ResponseOrigin.fetcher, 2), - const StoreResponse.data(ResponseOrigin.fetcher, 3), + const StockResponseLoading(ResponseOrigin.fetcher), + const StockResponse.data(ResponseOrigin.sourceOfTruth, -1), + const StockResponse.data(ResponseOrigin.fetcher, 1), + const StockResponse.data(ResponseOrigin.fetcher, 2), + const StockResponse.data(ResponseOrigin.fetcher, 3), ])); }); - test('Test a store with only a stream fetcher', () async { + test('Test a stock with only a stream fetcher', () async { final fetcher = Fetcher.ofStream((int key) => Stream.fromIterable([1, 2, 3])); - final store = Store( + final stock = Stock( fetcher: fetcher, sourceOfTruth: null, ); - final resultList = await store.getFreshResult(1); + final resultList = await stock.getFreshResult(1); expect( resultList, equals([ - const StoreResponseLoading(ResponseOrigin.fetcher), - const StoreResponse.data(ResponseOrigin.fetcher, 1), - const StoreResponse.data(ResponseOrigin.fetcher, 2), - const StoreResponse.data(ResponseOrigin.fetcher, 3), + const StockResponseLoading(ResponseOrigin.fetcher), + const StockResponse.data(ResponseOrigin.fetcher, 1), + const StockResponse.data(ResponseOrigin.fetcher, 2), + const StockResponse.data(ResponseOrigin.fetcher, 3), ])); }); });