A Flutter widget that provides a simple and synchronous interface to store persistent variables, relying on shared preferences under the hood.
To start using persistent variables in one of your widgets simply wrap it inside
a PersistentContext widget. You can access the instance of PersistentContext
and its methods through PersistentContext.of(context) from any descendant
widget.
Simply call the method set(key, value) to set a persistent variable named
key with value value and get(key) to access the value. Be aware that
value can only have the following types, due to shared preferences
limitations:
intdoubleboolString
Every time a value is set, it will be reflected immediately on the UI, and made persistent asynchronously in background.
When your app is first started, however, data will be loaded asynchronously from
local storage, so the get method may return null (or the default value, if
specified) if called before PersistentContext is initialized.
You can check the ready future to wait for initialization completion and avoid
any inconvenience.
The following example illustrates how to use PersistentContext to build a simple counter app that holds the count persistently. Note that no stateful widget is required, as PersistentContext acts also as a simple state manager.
import 'package:flutter/material.dart';
import 'package:persistent_context/persistent_context.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'PersistentContext Demo',
home: Scaffold(
appBar: AppBar(
title: Text('PersistentContext Demo'),
),
body: Center(
child: PersistentContext(
child: CounterWidget(),
),
),
),
);
}
}
class CounterWidget extends StatelessWidget {
void _increment(BuildContext context) {
final currentCount = PersistentContext.of(context).get('counter') ?? 0;
PersistentContext.of(context).set(
'counter',
currentCount + 1,
);
}
void _reset(BuildContext context) {
PersistentContext.of(context).set('counter', 0);
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'This counter will persist after restarting the app:',
),
Text(
'${PersistentContext.of(context).get('counter') ?? 0}',
style: Theme.of(context).textTheme.headline4,
),
ButtonBar(
alignment: MainAxisAlignment.center,
children: [
ElevatedButton.icon(
onPressed: () => _increment(context),
icon: Icon(Icons.add),
label: Text('Increment'),
),
ElevatedButton.icon(
onPressed: () => _reset(context),
icon: Icon(Icons.refresh),
label: Text('Reset'),
),
],
),
],
);
}
}PersistentContext constructor features the following optional arguments, that may be useful in advanced use cases:
-
defaultValues: a map of keys and values that define default values for persistent records. Note that if a default value has been defined, a variable can only be set to values of the same type. -
prefix: a string prefix that will be added to all keys in shared preferences. It should be used in case of multiple instances of PersistentContext to avoid collisions. -
sharedPreferencesInstance: an instance ofSharedPreferencesthat can be used immediately, if already available. If not provided, the constructor will instantiate a newSharedPreferencesobject asynchronously, resulting in a small delay.
The following snippet illustrates how to use such arguments in the previous example:
PersistentContext(
child: CounterWidget(),
prefix: 'counterContext',
defaultValues: {
'counter': 0,
},
sharedPreferencesInstance: sharedPrefs,
)To keep your code as clean and robust as possible, it is recommended that you
don't use PersistentContext directly, but rather extend it with a custom class
that calls get and set methods through dedicated getters and setters for
each persistent variable, and also defines statically the default values.
Consider for example the following implementation for the counter app example:
class CustomPersistentContext extends PersistentContext {
// Default values
static final _counterDefault = 0;
// Constructor
CustomPersistentContext({
Key? key,
required Widget child,
}) : super(
key: key,
child: child,
defaultValues: {
'counter': _counterDefault,
},
);
// Getters and setters
int get counter => get('counter') ?? _counterDefault;
set counter(int value) {
set('counter', value);
}
// This is needed for `of` method to work
static CustomPersistentContext of(BuildContext context) {
return context
.dependOnInheritedWidgetOfExactType<CustomPersistentContext>()!;
}
}The methods _increment and _reset from CounterWidget can then be
simplified as follows:
void _increment(BuildContext context) {
CustomPersistentContext.of(context).counter++;
}
void _reset(BuildContext context) {
CustomPersistentContext.of(context).counter = 0;
}