{"id":15293108,"url":"https://github.com/xmartlabs/stock","last_synced_at":"2025-07-15T10:09:36.125Z","repository":{"id":58171828,"uuid":"523761568","full_name":"xmartlabs/stock","owner":"xmartlabs","description":"Dart package for Async Data Loading and Caching. Combine local (DB, cache) and network data simply and safely.","archived":false,"fork":false,"pushed_at":"2024-11-06T19:56:35.000Z","size":130,"stargazers_count":81,"open_issues_count":5,"forks_count":8,"subscribers_count":10,"default_branch":"main","last_synced_at":"2025-05-19T17:15:15.561Z","etag":null,"topics":["cache","dart","database","flutter","hacktoberfest","networking"],"latest_commit_sha":null,"homepage":"https://pub.dev/packages/stock","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/xmartlabs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-08-11T14:48:40.000Z","updated_at":"2025-04-03T09:02:51.000Z","dependencies_parsed_at":"2024-05-07T14:45:54.843Z","dependency_job_id":"6f8e6546-cbe9-495f-8735-5af3990d1548","html_url":"https://github.com/xmartlabs/stock","commit_stats":{"total_commits":40,"total_committers":5,"mean_commits":8.0,"dds":0.09999999999999998,"last_synced_commit":"fa3707235b49bf5bf655e65a41347e4fe00e6f46"},"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/xmartlabs/stock","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xmartlabs%2Fstock","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xmartlabs%2Fstock/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xmartlabs%2Fstock/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xmartlabs%2Fstock/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xmartlabs","download_url":"https://codeload.github.com/xmartlabs/stock/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xmartlabs%2Fstock/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265427580,"owners_count":23763351,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cache","dart","database","flutter","hacktoberfest","networking"],"created_at":"2024-09-30T16:39:50.564Z","updated_at":"2025-07-15T10:09:36.086Z","avatar_url":"https://github.com/xmartlabs.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cimg src=\"https://raw.githubusercontent.com/xmartlabs/stock/main/img/banner.png\" width=\"100%\" alt=\"stock\" /\u003e\n\n[![Pub](https://img.shields.io/pub/v/stock.svg?style=flat-square)](https://pub.dartlang.org/packages/stock)\n[![Codecov](https://codecov.io/gh/xmartlabs/stock/branch/main/graph/badge.svg)](https://codecov.io/gh/xmartlabs/stock)\n[![Dart CI](https://github.com/xmartlabs/stock/actions/workflows/dart-ci.yml/badge.svg?branch=main)](https://github.com/xmartlabs/stock/actions/workflows/dart-ci.yml)\n[![style: very good analysis](https://img.shields.io/badge/style-very_good_analysis-B22C89.svg)](https://pub.dev/packages/very_good_analysis)\n\nStock is a dart package for loading data from both remote and local sources.\nIt is inspired by the [Store] Kotlin library.\n\nIts main goal is to prevent excessive calls to the network and disk cache. \nBy utilizing it, you eliminate the possibility of flooding your network with the same request while, at the same time, adding layers of caching.\n\nAlthough 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. \n\n## Features\n\n- Combine local (DB, cache) and network data simply. It provides a data `Stream` where you can listen and work with your data. \n- Know the data `Stream` state. It's useful for displaying errors or loading indicators, for example.\n- If you are not using a local DB, `Stock` provides a memory cache, to share and improve upon your app's experience.\n- Work with your data more safely. If an error is thrown, Stock will catch it and return it into the stream so it can be handled easily.\n\n## Overview\n\nA [`Stock`] is responsible for managing a particular data request.\n\nIt is based on two important classes:\n- [`Fetcher`]: defines how data will be fetched over the network.\n- [`SourceOfTruth`]: defines how local data will be read and written in your local cache. Although `Stock` can be used without it, we recommend its usage.\n\n`Stock` uses generic keys as identifiers for data.\nA key can be any value object that properly implements `toString()`, `equals()` and `hashCode()`.\n\nWhen you create `Stock`, it provides you with a bunch of methods in order to access the data.\nThe most important one is `stream()`, which provides you with a `Stream` of your data, which can be used to update your UI or to do a specific action.\n\n\n## Getting started\n\nTo use this package, add `stock` as a [dependency in your pubspec.yaml file](https://flutter.dev/docs/development/platform-integration/platform-channels).\n\n### 1. Create a Fetcher\n\nThe `Fetcher` is required to fetch new data from the network.\nYou can create it from a `Future` or from a `Stream`.\n\n`FutureFetcher` is usually used alongside a RestApi, whereas `StreamFetcher` is used with a `Stream` source like a Web Socket.\n\n```dart\n  final futureFetcher = Fetcher.ofFuture\u003cString, List\u003cTweet\u003e\u003e(\n    (userId) =\u003e _api.getUserTweets(userId),\n  );\n\n  final streamFetcher = Fetcher.ofStream\u003cString, List\u003cTweet\u003e\u003e(\n    (userId) =\u003e _api.getUserTweetsStream(userId),\n  );\n```\n\n### 2. Create a Source Of Truth\n\nThe `SourceOfTruth` is used to read and write the remote data in a local cache.\n\nGenerally you will implement the `SourceOfTruth` using a local database. However, if you are not using a local database / cache, the library provides the [`CachedSourceOfTruth`], a source of truth which stores the data in memory.   \n\n```dart\n  final sourceOfTruth = SourceOfTruth\u003cString, List\u003cTweet\u003e\u003e(\n    reader: (userId) =\u003e _database.getUserTweets(userId),\n    writer: (userId, tweets) =\u003e _database.writeUserTweets(userId, tweets),\n    delete: (userId) =\u003e _database.deleteUserTweets(userId), // this is optional\n    deleteAll: () =\u003e _database.deleteAllTweets(), // this is optional\n  );\n```\n\nIn this example, `Fetcher` type is exactly the `SourceOfTruth` type. If you need to use multiple types, you can use the [`mapTo` extension](#use-different-types-for-fetcher-and-sourceoftruth).\n\n_Note: to the proper operation, when `write` is invoked with new data, the source of truth has to emit the new value in the `reader`._\n\n### 3. Create the `Stock`\n\n`Stock` lets you combine the different data sources and get the data.\n\n```dart\n final stock = Stock\u003cString, List\u003cTweet\u003e\u003e(\n    fetcher: fetcher,\n    sourceOfTruth: sourceOfTruth,\n  );\n```\n\n### Get a data `Stream` from Stock\n\nYou can generate a data `Stream` using `stream()`.\n\nYou need to invoke it with a specific `key`, and an optional `refresh` value that tells Stock if a refresh is optional or mandatory.\n\nThat returns a data stream of `StockResponse`, which has 3 possible values:\n- `StockResponseLoading` informs that a network request is in progress. It can be useful to display a loading indicator in your UI.\n- `StockResponseData` holds the response data. It has a `value` field which includes an instance of the type returned by `Stock`.\n- `StockResponseError` indicates that an error happened.\nWhen an error happens, `Stock` does not throw an exception, instead, it wraps it in this class.\nIt includes an `error` field that contains the exception thrown by the given `origin`.\n\nEach `StockResponse` includes an `origin` field which specifies where the event is coming from. \n\n```dart\n  stock\n      .stream('user_id', refresh: true)\n      .listen((StockResponse\u003cList\u003cTweet\u003e\u003e stockResponse) {\n    stockResponse.when(\n      onLoading: (origin) =\u003e _displayLoadingIndicator(),\n      onData: (origin, data) =\u003e _displayTweetsInUI(data),\n      onError: (origin, error, stacktrace) =\u003e _displayErrorInUi(error),\n    );\n  });\n```\n\n_Note: `Stock` provides a [large list of extensions][response_extensions] to facilitate and manipulate the responses._\n\n### Get non-stream data from Stock\n\nStock provides a couple of methods to get data without using a data stream.\n\n1. `get` returns cached data -if it is cached- otherwise will return fresh/network data (updating your caches).\n2. `fresh` returns fresh data updating your cache\n3. `clear` purge a particular entry from memory and disk cache\n4. `clearAll` Purge all entries from memory and disk cache\n\n\n```dart\n  // Get fresh data\n  final List\u003cTweet\u003e freshTweets = await stock.fresh(key);\n  \n  // Get the previous cached data\n  final List\u003cTweet\u003e cachedTweets = await stock.get(key);\n\n  // Clear key from stock\n  await stock.clear(key);\n\n  // Clear all keys from stock\n  await stock.clearAll();\n```\n\n\n### Use different types for `Fetcher` and `SourceOfTruth`\n\nSometimes 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.\n\n`StockTypeMapper` is used in the `SourceOfTruth` via the method `mapToUsingMapper`\n\n```dart\nclass TweetMapper implements StockTypeMapper\u003cDbTweet, NetworkTweet\u003e {\n  @override\n  NetworkTweet fromInput(DbTweet value) =\u003e NetworkTweet(value);\n\n  @override\n  DbTweet fromOutput(NetworkTweet value) =\u003e DbTweet(value);\n}\n\nfinal SourceOfTruth\u003cint, DbTweet\u003e sot = _createMapper();\nfinal SourceOfTruth\u003cint, NetworkTweet\u003e newSot = sot.mapToUsingMapper(TweetMapper());\n```\n\nYou can also achieve the same result using the `mapTo` extension.\n\n```dart\n\nfinal SourceOfTruth\u003cint, DbTweet\u003e sot = _createMapper();\nfinal SourceOfTruth\u003cint, NetworkTweet\u003e newSot = mapTo(\n      (networkTweet) =\u003e DbTweet(networkTweet),\n      (dbTweet) =\u003e NetworkTweet(dbTweet),\n);\n```\n\n### Use a non-stream source of truth\n\nSometimes your Source of Truth does not provide you with a real-time data stream.\nFor example, suppose that you are using [shared_preferences] to store your data, or you are just catching your data in memory.\nFor these cases, `Stock` provides you the `CachedSourceOfTruth`, a `SourceOfTruth` that be helpful in these cases.\n\n```dart\nclass SharedPreferencesSourceOfTruth extends CachedSourceOfTruth\u003cString, String\u003e {\n  SharedPreferencesSourceOfTruth();\n\n  @override\n  @protected\n  Stream\u003cT?\u003e reader(String key) async* {\n    final prefs = await SharedPreferences.getInstance();\n    // Read data from an non-stream source\n    final stringValue = prefs.getString(key);\n    setCachedValue(key, stringValue);\n    yield* super.reader(key);\n  }\n\n  @override\n  @protected\n  Future\u003cvoid\u003e write(String key, String? value) async {\n    final prefs = await SharedPreferences.getInstance();\n    await prefs.setString(key, value);\n    await super.write(key, value);\n  }\n}\n```\n\nThis class can be used with `StockTypeMapper` to transform your data into an entity.\n\n```dart\n// The mapper json to transform a string into a User \nfinal StockTypeMapper\u003cString, User\u003e mapper = _createUserMapper();\nfinal SharedPreferencesSourceOfTruth\u003cString, User\u003e = SharedPreferencesSourceOfTruth()\n    .mapToUsingMapper(mapper);\n```\n\n## Additional information\n\nFor 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).\n\nMade with ❤️ by [Xmartlabs](https://xmartlabs.com).\n\n## License\n\n    Copyright (c) 2022 Xmartlabs SRL.\n\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n        http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n\n[Drift]: https://pub.dev/packages/drift\n[Floor]: https://pub.dev/packages/floor\n[Realm]: https://pub.dev/packages/realm\n[response_extensions]: https://pub.dev/documentation/stock/latest/stock/StockResponseExtensions.html\n[Store]: https://github.com/MobileNativeFoundation/Store\n[`CachedSourceOfTruth`]: lib/src/source_of_truth.dart\n[`Fetcher`]: lib/src/fetcher.dart\n[`SourceOfTruth`]: lib/src/source_of_truth.dart\n[`Stock`]: lib/src/stock.dart\n[shared_preferences]: https://pub.dev/packages/shared_preferences\n[Sqflite]: https://pub.dev/packages/sqflite\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxmartlabs%2Fstock","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxmartlabs%2Fstock","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxmartlabs%2Fstock/lists"}