{"id":15489076,"url":"https://github.com/roughike/streaming_shared_preferences","last_synced_at":"2025-04-06T12:09:31.571Z","repository":{"id":34801974,"uuid":"182518333","full_name":"roughike/streaming_shared_preferences","owner":"roughike","description":"A reactive key-value store for Flutter projects. Like shared_preferences, but with Streams.","archived":false,"fork":false,"pushed_at":"2022-03-02T10:29:43.000Z","size":267,"stargazers_count":247,"open_issues_count":7,"forks_count":26,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-03-30T11:07:04.074Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/roughike.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":"roughike","patreon":"iirokrankka"}},"created_at":"2019-04-21T10:14:25.000Z","updated_at":"2025-03-12T12:03:41.000Z","dependencies_parsed_at":"2022-07-08T10:32:58.668Z","dependency_job_id":null,"html_url":"https://github.com/roughike/streaming_shared_preferences","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roughike%2Fstreaming_shared_preferences","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roughike%2Fstreaming_shared_preferences/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roughike%2Fstreaming_shared_preferences/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/roughike%2Fstreaming_shared_preferences/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/roughike","download_url":"https://codeload.github.com/roughike/streaming_shared_preferences/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247478323,"owners_count":20945266,"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":[],"created_at":"2024-10-02T07:03:26.665Z","updated_at":"2025-04-06T12:09:31.554Z","avatar_url":"https://github.com/roughike.png","language":"Dart","readme":"# streaming_shared_preferences\n\n[![pub package](https://img.shields.io/pub/v/streaming_shared_preferences.svg)](https://pub.dartlang.org/packages/streaming_shared_preferences)\n [![Build Status](https://travis-ci.org/roughike/streaming_shared_preferences.svg?branch=master)](https://travis-ci.org/roughike/streaming_shared_preferences) \n [![Coverage Status](https://coveralls.io/repos/github/roughike/streaming_shared_preferences/badge.svg?branch=master)](https://coveralls.io/github/roughike/streaming_shared_preferences?branch=master)\n\nA reactive key-value store for Flutter projects.\n\n**streaming_shared_preferences** adds reactive functionality on top of [shared_preferences](https://pub.dartlang.org/packages/shared_preferences). It does everything that regular `SharedPreferences` does, but it also allows _listening to changes in values_. This makes it super easy to keep your widgets in sync with persisted values.\n\n## Getting started\n\nFirst, add streaming_shared_preferences into your pubspec.yaml.\n\nIf you're already using `shared_preferences`, **you should replace the dependency** with `streaming_shared_preferences`.\n\n```yaml\ndependencies:\n  streaming_shared_preferences: ^2.0.0\n```\n\nTo get a hold of `StreamingSharedPreferences`, _await_ on `instance`:\n\n```dart\nimport 'package:streaming_shared_preferences/streaming_shared_preferences.dart';\n\n...\nWidgetsFlutterBinding.ensureInitialized();\nfinal preferences = await StreamingSharedPreferences.instance;\n```\n\n**Caveat**: The change detection works only in Dart side.\nThis means that if you want to react to changes in values, you should always use `StreamingSharedPreferences` (**not** `SharedPreferences`) to store your values.\n\n## Your first streaming preference\n\nHere's the _simplest possible plain Dart example_ on how you would print a value to console every time a `counter` integer changes:\n\n```dart\n// Get a reference to the counter value and provide a default value \n// of 0 in case it is null.\nPreference\u003cint\u003e counter = preferences.getInt('counter', defaultValue: 0);\n\n// \"counter\" is a Preference\u003cint\u003e - it can do anything a Stream\u003cint\u003e can.\n// We're just going to listen to it and print the value to console.\ncounter.listen((value) {\n  print(value);\n});\n\n// Somewhere else in your code, update the value.\ncounter.setValue(1);\n\n// This is exactly same as above, but the above is more convenient.\npreferences.setInt('counter', 2);\n```\n\nThe public API follows the same convention as regular `SharedPreferences`, but every getter returns a `Preference` object which is a special type of `Stream`.\n\nAssuming that there's no previously stored value (=it's null), the above example will print `0`, `1` and `2` to the console.\n\n### Getting a value synchronously\n\nNo problem! Just call `getValue()` on whatever the `preferences.getInt(..)` (or `getString()`, `getBool()`, etc.) returns you.\n\n## Connecting values to Flutter widgets\n\nAlthought it works perfectly fine with a `StreamBuilder`, the recommended way is to use the `PreferenceBuilder` widget.\n\nIf you have only one value you need to store in your app, it might make sense to listen to it inline:\n\n```dart\nclass MyCounterWidget extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    /// PreferenceBuilder is like StreamBuilder, but with less boilerplate.\n    ///\n    /// We don't have to provide `initialData` because it can be fetched synchronously\n    /// from the provided Preference. There's also no initial flicker when transitioning\n    /// between initialData and the stream.\n    ///\n    /// If you want, you could use a StreamBuilder too.\n    return PreferenceBuilder\u003cint\u003e(\n      preference: preferences.getInt('counter', defaultValue: 0),\n      builder: (BuildContext context, int counter) {\n        return Text('Button pressed $counter times!');\n      }\n    );\n  }\n}\n```\n\n### Use a wrapper class when having multiple preferences\n\nIf you have multiple preferences, the recommended approach is to create a class that holds all your `Preference` objects in a single place:\n\n```dart\n/// A class that holds [Preference] objects for the common values that you want\n/// to store in your app. This is *not* necessarily needed, but it makes your\n/// code more neat and fool-proof.\nclass MyAppSettings {\n  MyAppSettings(StreamingSharedPreferences preferences)\n      : counter = preferences.getInt('counter', defaultValue: 0),\n        nickname = preferences.getString('nickname', defaultValue: '');\n\n  final Preference\u003cint\u003e counter;\n  final Preference\u003cString\u003e nickname;\n}\n```\n\nIn our app entry point, you'll obtain an instance to `StreamingSharedPreferences` once and pass that to our settings class.\nNow we can pass `MyAppSettings` down to the widgets that use it:\n\n```dart\nFuture\u003cvoid\u003e main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  /// Obtain instance to streaming shared preferences, create MyAppSettings, and\n  /// once that's done, run the app.\n  final preferences = await StreamingSharedPreferences.instance;\n  final settings = MyAppSettings(preferences);\n  \n  runApp(MyApp(settings));\n}\n```\n\nThis makes the calling code become quite neat:\n\n```dart\nclass MyCounterWidget extends StatelessWidget {\n  MyCounterWidget(this.settings);\n  final MyAppSettings settings;\n\n  @override\n  Widget build(BuildContext context) {\n    return Column(\n      children: [\n        PreferenceBuilder\u003cString\u003e(\n          preference: settings.nickname,\n          builder: (context, nickname) =\u003e Text('Hey $nickname!'),\n        ),\n        PreferenceBuilder\u003cint\u003e(\n          preference: settings.counter,\n          builder: (context, counter) =\u003e Text('You have pushed the button $counter times!'),\n        ),\n        FloatingActionButton(\n          onPressed: () {\n            /// To obtain the current value synchronously, we can call \".getValue()\".\n            final currentCounter = settings.counter.getValue();\n\n            /// To update the value, we can call \".setValue()\" - no need to provide a key!\n            /// Alternatively, we could just call \"preferences.setInt('counter', currentCounter + 1)\".\n            settings.counter.setValue(currentCounter + 1);\n          },\n          child: Icon(Icons.add),\n        ),\n      ],\n    );\n  }\n}\n```\n\nYou can see a full working example of this [in the example project](https://github.com/roughike/streaming_shared_preferences/blob/master/example/lib/main.dart).\n\nWhen your widget hierarchy becomes deep enough, you would want to pass `MyAppSettings` around with an [InheritedWidget](https://docs.flutter.io/flutter/widgets/InheritedWidget-class.html) or [provider](https://github.com/rrousselGit/provider) instead.\n\n### \"But what about muh abstraction!\"\n\nIf you're all about the clean architecture and don't want to pollute your domain layer with `Preference` objects from a third-party library by some random internet stranger, all the power to you.\n\nHere's one way to make Uncle Bob proud.\n\n```dart\n/// The contract for persistent values in your app that can be shared\n/// to your pure business logic classes\nabstract class SettingsContract {\n  Stream\u003cint\u003e streamCounterValues();\n  void setCounter(int value);\n}\n\n/// ... somewhere else in your codebase\nclass MyBusinessLogic {\n  MyBusinessLogic(SettingsContract settings) {\n    // Do something with \"streamCounterValues()\" and \"setCounter()\" along with\n    // whatever your business use case needs\n  }\n}\n```\n\nNo `StreamingSharedPreferences` specifics went in there.\n\nIf for some reason you want to switch into some other library (or get rid of this library altogether), you can do so without modifying your business logic.\n\nHere's how the implementation based on `StreamingSharedPreferences` would look like:\n\n```dart\n/// One implementation of SettingsContract backed by StreamingSharedPreferences\nclass MyAppSettings implements SettingsContract {\n  MyAppSettings(StreamingSharedPreferences preferences)\n      : counter = preferences.getInt('counter', defaultValue: 0);\n\n  final Preference\u003cint\u003e _counter;\n\n  @override\n  Stream\u003cint\u003e streamCounterValues() =\u003e _counter;\n\n  @override\n  void setCounter(int value) =\u003e _counter.setValue(value);\n}\n```\n\n## Storing custom types with JsonAdapter\n\nThe entire library is built to support storing custom data types easily with a `PreferenceAdapter`.\nIn fact, every built-in type has its own `PreferenceAdapter` - so _every type is actually a custom value_.\n\nFor most cases, there's a convenience adapter that handles common pitfalls when storing and retrieving custom values called `JsonAdapter`.\nIt helps you to store your values in JSON and it also saves you from duplicating `if (value == null) return null` for your custom adapters.\n\nFor example, if we have a class called `SampleObject`:\n\n```dart\nclass SampleObject {\n  SampleObject(this.isAwesome);\n  final bool isAwesome;\n\n  SampleObject.fromJson(Map\u003cString, dynamic\u003e json) :\n    isAwesome = json['isAwesome'];\n\n  Map\u003cString, dynamic\u003e toJson() =\u003e { 'isAwesome': isAwesome };\n}\n```\n\nAs seen from the above example, SampleObject implements both `fromJson` and `toJson`.\n\nWhen the `toJson` method is present, JsonAdapter will call `toJson` automatically. \nFor reviving, you need to provide a deserializer that calls `fromJson` manually:\n\n```dart\nfinal sampleObject = preferences.getCustomValue\u003cSampleObject\u003e(\n  'my-key',\n  defaultValue: SampleObject.empty(),\n  adapter: JsonAdapter(\n    deserializer: (value) =\u003e SampleObject.fromJson(value),\n  ),\n);\n```\n\nDepending on your use case, you need to provide a non-null `SampleObject.empty()` that represents a sane default for your custom type when the value is not loaded just yet.\n\n### Using JsonAdapter with built_value\n\nYou can do custom serialization logic before JSON encoding the object by providing a serializer. \nSimilarly, you can use deserializer to map the decoded JSON map into any object you want.\n\nFor example, if the previous `SampleObject` didn't have `toJson` and `fromJson` methods, but was a built_value model instead:\n\n```dart\nfinal sampleObject = preferences.getCustomValue\u003cSampleObject\u003e(\n  'my-key',\n  defaultValue: SampleObject.empty(),\n  adapter: JsonAdapter(\n    serializer: (value) =\u003e serializers.serialize(value),\n    deserializer: (value) =\u003e serializers.deserialize(value),\n  ),\n);\n```\n\nThe `serializers` here is your global serializer that comes from `built_value`.\n","funding_links":["https://github.com/sponsors/roughike","https://patreon.com/iirokrankka"],"categories":["组件","Storage [🔝](#readme)","Components"],"sub_categories":["存储","Storage"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Froughike%2Fstreaming_shared_preferences","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Froughike%2Fstreaming_shared_preferences","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Froughike%2Fstreaming_shared_preferences/lists"}