/rx_shared_preferences

🌀 Shared preferences with RxDart Stream observation ⚡️ Reactive shared preferences for Flutter 🌸Reactive stream wrapper around SharedPreferences 🍄 Lightweight and easy-to-use 🌱 A reactive key-value store for Flutter projects. Like shared_preferences, but with Streams 📕 Rx Shared Preferences for Flutter 🌿 rx_shared_preferences 🌰 rx_shared_preference 🍷 Reactive SharedPreferences for Flutter 🌰 A stream based wrapper over shared_preferences, allowing reactive key-value storage.

Primary LanguageDartMIT LicenseMIT

rx_shared_preferences alt text

Codacy Badge Pub Pub codecov Build example Flutter CI License: MIT Style Hits

All Contributors

  • Shared preference with rxdart Stream observation.
  • Reactive shared preferences for Flutter.
  • Reactive stream wrapper around SharedPreferences.
  • This package provides reactive shared preferences interaction with very little code. It is designed specifically to be used with Flutter and Dart.

Buy me a coffee

Liked some of my work? Buy me a coffee (or more likely a beer)

Buy Me A Coffee

Note

Since version 1.3.4, this package is an extension of rx_storage package.

More details about the returned Stream

  • It's a single-subscription Stream (i.e. it can only be listened once).

  • Stream will emit the value (nullable) or a TypeError as its first event when it is listened to.

  • It will automatically emit the new value when the value associated with key was changed successfully (it will also emit null when value associated with key was removed or set to null).

  • When value read from Storage has a type other than expected type:

    • If value is null, the Stream will emit null (this occurred because null can be cast to any nullable type).
    • Otherwise, the Stream will emit a TypeError.
  • Can emit two consecutive data events that are equal. You should use Rx operator like distinct (more commonly known as distinctUntilChanged in other Rx implementations) to create a Stream where data events are skipped if they are equal to the previous data event.

Key changed   |----------K1---K2------K1----K1-----K2---------> time
              |                                                
Value stream  |-----@----@------------@-----@-----------------> time
              |    ^                                      
              |    |
              |  Listen(key=K1)
              |
              |  @: nullable value or TypeError

Getting Started

In your flutter project, add the dependency to your pubspec.yaml

dependencies:
  [...]
  rx_shared_preferences: <latest_version>

Usage

1. Import and instantiate

  • Import rx_shared_preferences.
import 'package:rx_shared_preferences/rx_shared_preferences.dart';
  • Wrap your SharedPreferences in a RxSharedPreferences.
// via constructor.
final rxPrefs = RxSharedPreferences(await SharedPreferences.getInstance()); // use await
final rxPrefs = RxSharedPreferences(SharedPreferences.getInstance());       // await is optional
final rxPrefs = RxSharedPreferences.getInstance();                          // default singleton instance

// via extension.
final rxPrefs = (await SharedPreferences.getInstance()).rx;                 // await is required

NOTE: When using RxSharedPreferences.getInstance() and extension (await SharedPreferences.getInstance()).rx, to config the logger, you can use RxSharedPreferencesConfigs.logger setter.

2. Add a logger (optional)

You can pass a logger to the optional parameter of RxSharedPreferences constructor. The logger will log messages about operations (such as read, write, ...) and stream events. This package provides two RxSharedPreferencesLoggers:

  • RxSharedPreferencesDefaultLogger.
  • RxSharedPreferencesEmptyLogger.
final rxPrefs = RxSharedPreferences(
  SharedPreferences.getInstance(),
  kReleaseMode ? null : RxSharedPreferencesDefaultLogger(),
  // disable logging when running in release mode.
);

NOTE: To disable logging when running in release mode, you can pass null or const RxSharedPreferencesEmptyLogger() to RxSharedPreferences constructor, or use RxSharedPreferencesConfigs.logger setter.

NOTE: To prevent printing ↓ Disposed successfully → DisposeBag#....

import 'package:disposebag/disposebag.dart' show DisposeBagConfigs;
void main() {
  DisposeBagConfigs.logger = null;
}

3. Select stream and observe

  • Then, just listen Streams, transform Streams through operators such as map, flatMap, etc...
  • If you need to listen to the Stream many times, you can use broadcast operators such as share, shareValue, publish, publishValue, etc...
// Listen
rxPrefs.getStringListStream('KEY_LIST').listen(print);                  // [*]

// Broadcast stream
rxPrefs.getStringListStream('KEY_LIST').share();
rxPrefs.getStringListStream('KEY_LIST').shareValue();
rxPrefs.getStringListStream('KEY_LIST').asBroadcastStream();

// Transform stream
rxPrefs.getIntStream('KEY_INT')
  .map((i) => /* Do something cool */)
  .where((i) => /* Filtering */)
  ...

// must **use same rxPrefs** instance when set value and select stream
await rxPrefs.setStringList('KEY_LIST', ['Cool']);                      // [*] will print ['Cool']
  • In the previous example, we re-used the RxSharedPreferences object rxPrefs for all operations. All operations must go through this object in order to correctly notify subscribers. Basically, you must use the same RxSharedPreferences instance when set value and select stream.

  • In a Flutter app, you can:

    • Create a global RxSharedPreferences instance.

    • Use the default singleton instance via RxSharedPreferences.getInstance().

    • Use InheritedWidget/Provider to provide a RxSharedPreferences instance (create it in main function) for all widgets (recommended). See example/main.

// An example for wrong usage.

rxPrefs1.getStringListStream('KEY_LIST').listen(print); // [*]

rxPrefs2.setStringList('KEY_LIST', ['Cool']);           // [*] will not print anything,
                                                        // because rxPrefs1 and rxPrefs2 are different instances.

4. Stream APIs and RxStorage APIs

  • All Streams APIs (via extension methods).
  Stream<Object?>              getObjectStream(String key, [Decoder<Object?>? decoder]);
  Stream<bool?>                getBoolStream(String key);
  Stream<double?>              getDoubleStream(String key);
  Stream<int?>                 getIntStream(String key);
  Stream<String?>              getStringStream(String key);
  Stream<List<String>?>        getStringListStream(String key);
  Stream<Set<String>>          getKeysStream();

  Future<void>                 updateBool(String key, Transformer<bool?> transformer);
  Future<void>                 updateDouble(String key, Transformer<double?> transformer);
  Future<void>                 updateInt(String key, Transformer<int?> transformer);
  Future<void>                 updateString(String key, Transformer<String?> transformer);
  Future<void>                 updateStringList(String key, Transformer<List<String>?> transformer);
  • All methods from RxStorage (because RxSharedPreferences implements RxStorage).
  Future<void>                 update<T extends Object>(String key, Decoder<T?> decoder, Transformer<T?> transformer, Encoder<T?> encoder);
  Stream<T?>                   observe<T extends Object>(String key, Decoder<T?> decoder);
  Stream<Map<String, Object?>> observeAll();
  Future<void>                 dispose();

5. Get and set methods likes SharedPreferences

  • RxSharedPreferences is like to SharedPreferences, it provides read, write functions (via extension methods).
  Future<Object?>              getObject(String key, [Decoder<Object?>? decoder]);
  Future<bool?>                getBool(String key);
  Future<double?>              getDouble(String key);
  Future<int?>                 getInt(String key);
  Future<Set<String>>          getKeys();
  Future<String?>              getString(String key);
  Future<List<String>?>        getStringList(String key);

  Future<Map<String, Object?>> reload();
  Future<void>                 setBool(String key, bool? value);
  Future<void>                 setDouble(String key, double? value);
  Future<void>                 setInt(String key, int? value);
  Future<void>                 setString(String key, String? value);
  Future<void>                 setStringList(String key, List<String>? value);
  • All methods from Storage (because RxSharedPreferences implements Storage).
  Future<bool>                 containsKey(String key);
  Future<T?>                   read<T extends Object>(String key, Decoder<T?> decoder);
  Future<Map<String, Object?>> readAll();
  Future<void>                 clear();
  Future<void>                 remove(String key);
  Future<void>                 write<T extends Object>(String key, T? value, Encoder<T?> encoder);

6. Dispose

You can dispose the RxSharedPreferences when it is no longer needed. Just call rxPrefs.dispose(). Usually, you call this method on dispose method of a Flutter State.

Example demo

Simple authentication app with BLoC rxdart pattern Build ListView from Stream using RxSharedPreferences Change theme and locale (language) runtime

Features and bugs

Please file feature requests and bugs at the issue tracker.

License

MIT License

Copyright (c) 2019-2024 Petrus Nguyễn Thái Học

Contributors ✨

Thanks goes to these wonderful people (emoji key):


Petrus Nguyễn Thái Học

💻 📖 🚧

This project follows the all-contributors specification. Contributions of any kind welcome!