Orientação gerada a partir de informações públicas. Apenas para referência.
Um pacote Flutter que fornece uma interface baseada em fluxo para o shared_preferences, permitindo atualizações reativas para armazenamento de chave-valor. Ele envolve o plugin padrão shared_preferences, oferecendo escuta em tempo real das alterações nos dados armazenados por meio de streams. Isso permite que os desenvolvedores criem interfaces responsivas que se atualizam automaticamente quando as preferências mudam, melhorando a gestão de estado em aplicativos que dependem de armazenamento local persistente. Ideal para aplicações que exigem feedback imediato após modificações nas preferências do usuário.
Casos de uso
Atualizações de interface em tempo real com base nas preferências do usuário
Painéis de configuração reativos
Alternância dinâmica de temas
Sincronização de estado persistente do aplicativo
monitoramento de preferência de fundo
Armazenamento local multiplataforma com fluxos
Principais recursos
Atualizações de valor baseadas em fluxo
Gerenciamento de estado reativo
Integração fácil com o shared_preferences existente
Suporta todas as principais plataformas
Mínimo boilerplate
Reatividade automática em alterações de preferência
Indicado para
Aplicativos que precisam de atualizações ao vivo a partir do armazenamento local
Desenvolvedores usando padrões de programação reativa
Projetos com configurações dinâmicas de usuário
Gerenciamento de estado com complexidade mínima
Aplicações multiplataforma com dados persistentes
Considerações
Requer conhecimento sobre streams do Dart
Pode aumentar o uso de memória com muitos ouvintes
Não adequado para grandes conjuntos de dados
Limitado a pares chave-valor
Depende do shared_preferences por trás dos panos
Impacto potencial no desempenho com atualizações frequentes
README original
Snapshot do README original do repositório-fonte vinculado. Consulte o repositório para o conteúdo mais recente.
streaming_shared_preferences
A reactive key-value store for Flutter projects.
streaming_shared_preferences adds reactive functionality on top of 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.
Getting started
First, add streaming_shared_preferences into your pubspec.yaml.
If you're already using shared_preferences, you should replace the dependency with streaming_shared_preferences.
dependencies:streaming_shared_preferences:^2.0.0
To get a hold of StreamingSharedPreferences, await on instance:
import'package:streaming_shared_preferences/streaming_shared_preferences.dart';
...
WidgetsFlutterBinding.ensureInitialized();
final preferences = awaitStreamingSharedPreferences.instance;
Caveat: The change detection works only in Dart side.
This means that if you want to react to changes in values, you should always use StreamingSharedPreferences (notSharedPreferences) to store your values.
Your first streaming preference
Here's the simplest possible plain Dart example on how you would print a value to console every time a counter integer changes:
// Get a reference to the counter value and provide a default value // of 0 in case it is null.Preference<int> counter = preferences.getInt('counter', defaultValue: 0);
// "counter" is a Preference<int> - it can do anything a Stream<int> can.// We're just going to listen to it and print the value to console.
counter.listen((value) {
print(value);
});
// Somewhere else in your code, update the value.
counter.setValue(1);
// This is exactly same as above, but the above is more convenient.
preferences.setInt('counter', 2);
The public API follows the same convention as regular SharedPreferences, but every getter returns a Preference object which is a special type of Stream.
Assuming that there's no previously stored value (=it's null), the above example will print 0, 1 and 2 to the console.
Getting a value synchronously
No problem! Just call getValue() on whatever the preferences.getInt(..) (or getString(), getBool(), etc.) returns you.
Connecting values to Flutter widgets
Althought it works perfectly fine with a StreamBuilder, the recommended way is to use the PreferenceBuilder widget.
If you have only one value you need to store in your app, it might make sense to listen to it inline:
classMyCounterWidgetextendsStatelessWidget{
@overrideWidgetbuild(BuildContext context) {
/// PreferenceBuilder is like StreamBuilder, but with less boilerplate.////// We don't have to provide `initialData` because it can be fetched synchronously/// from the provided Preference. There's also no initial flicker when transitioning/// between initialData and the stream.////// If you want, you could use a StreamBuilder too.returnPreferenceBuilder<int>(
preference: preferences.getInt('counter', defaultValue: 0),
builder: (BuildContext context, int counter) {
returnText('Button pressed $counter times!');
}
);
}
}
Use a wrapper class when having multiple preferences
If you have multiple preferences, the recommended approach is to create a class that holds all your Preference objects in a single place:
/// A class that holds [Preference] objects for the common values that you want/// to store in your app. This is *not* necessarily needed, but it makes your/// code more neat and fool-proof.classMyAppSettings{
MyAppSettings(StreamingSharedPreferences preferences)
: counter = preferences.getInt('counter', defaultValue: 0),
nickname = preferences.getString('nickname', defaultValue: '');
finalPreference<int> counter;
finalPreference<String> nickname;
}
In our app entry point, you'll obtain an instance to StreamingSharedPreferences once and pass that to our settings class.
Now we can pass MyAppSettings down to the widgets that use it:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
/// Obtain instance to streaming shared preferences, create MyAppSettings, and/// once that's done, run the app.final preferences = awaitStreamingSharedPreferences.instance;
final settings = MyAppSettings(preferences);
runApp(MyApp(settings));
}
This makes the calling code become quite neat:
classMyCounterWidgetextendsStatelessWidget{
MyCounterWidget(this.settings);
finalMyAppSettings settings;
@overrideWidgetbuild(BuildContext context) {
returnColumn(
children: [
PreferenceBuilder<String>(
preference: settings.nickname,
builder: (context, nickname) => Text('Hey $nickname!'),
),
PreferenceBuilder<int>(
preference: settings.counter,
builder: (context, counter) => Text('You have pushed the button $counter times!'),
),
FloatingActionButton(
onPressed: () {
/// To obtain the current value synchronously, we can call ".getValue()".final currentCounter = settings.counter.getValue();
/// To update the value, we can call ".setValue()" - no need to provide a key!/// Alternatively, we could just call "preferences.setInt('counter', currentCounter + 1)".
settings.counter.setValue(currentCounter + 1);
},
child: Icon(Icons.add),
),
],
);
}
}
When your widget hierarchy becomes deep enough, you would want to pass MyAppSettings around with an InheritedWidget or provider instead.
"But what about muh abstraction!"
If 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.
Here's one way to make Uncle Bob proud.
/// The contract for persistent values in your app that can be shared/// to your pure business logic classesabstractclassSettingsContract{
Stream<int> streamCounterValues();
voidsetCounter(int value);
}
/// ... somewhere else in your codebaseclassMyBusinessLogic{
MyBusinessLogic(SettingsContract settings) {
// Do something with "streamCounterValues()" and "setCounter()" along with// whatever your business use case needs
}
}
No StreamingSharedPreferences specifics went in there.
If 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.
Here's how the implementation based on StreamingSharedPreferences would look like:
/// One implementation of SettingsContract backed by StreamingSharedPreferencesclassMyAppSettingsimplementsSettingsContract{
MyAppSettings(StreamingSharedPreferences preferences)
: counter = preferences.getInt('counter', defaultValue: 0);
finalPreference<int> _counter;
@overrideStream<int> streamCounterValues() => _counter;
@overridevoidsetCounter(int value) => _counter.setValue(value);
}
Storing custom types with JsonAdapter
The entire library is built to support storing custom data types easily with a PreferenceAdapter.
In fact, every built-in type has its own PreferenceAdapter - so every type is actually a custom value.
For most cases, there's a convenience adapter that handles common pitfalls when storing and retrieving custom values called JsonAdapter.
It helps you to store your values in JSON and it also saves you from duplicating if (value == null) return null for your custom adapters.
For example, if we have a class called SampleObject:
As seen from the above example, SampleObject implements both fromJson and toJson.
When the toJson method is present, JsonAdapter will call toJson automatically.
For reviving, you need to provide a deserializer that calls fromJson manually:
Depending 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.
Using JsonAdapter with built_value
You can do custom serialization logic before JSON encoding the object by providing a serializer.
Similarly, you can use deserializer to map the decoded JSON map into any object you want.
For example, if the previous SampleObject didn't have toJson and fromJson methods, but was a built_value model instead: