Nella precedente lezione abbiamo preso familiarità con il plugin path_provider
e la classe File
per effettuare operazioni di lettura e scrittura su un file di testo salvato nella document folder gestita dalla applicazione.
Questo ovviamente non è l’unico modo per salvare le informazioni dell’utente che utilizza la nostra applicazione. Un altro approccio definito in Flutter per il salvataggio dei dati è offerto dalle SharedPreferences
.
Le shared preferences permettono di salvare delle coppie chiave-valore (key-value, KV) costituite da valori primitivi come stringhe, interi, valori in virgola mobile o booleani, che rappresentano ad esempio delle impostazioni o preferenze inserite dall’utente. I dati archiviati utilizzando le shared preferences sono mantenuti privati nell'ambito dell'app rendendo le shared preferences adatte in diversi scenari come:
- salvataggio delle impostazioni dell'utente per la nostra app;
- archiviare dati che possono essere utilizzati in diverse attività all'interno dell'app.
In questa lezione capiremo come utilizzare le shared preferences per salvare le impostazioni dell’utente.
Il plugin shared_preferences e la classe SharedPreferences
In Flutter, per effettuare la lettura e la scrittura di file è necessario utilizzare:
Plugin/Classe | Descrizione |
---|---|
shared_preferences |
questo plugin permette allo sviluppatore di:
|
SharedPreferences |
È la classe che permette la scrittura e la lettura asincrona delle coppie KV e offre un insieme di metodi per:
|
Esempio pratico
Come sempre, creiamo un nuovo progetto come descritto nella lezione 6.
Procediamo adesso step by step per creare un’applicazione che ci permetta di aggiungere e rimuovere coppie KV dalle nostre shared preferences immaginando di creare una semplice pagina di settings.
Installazione del plugin shared_preferences
Installiamo il plugin shared_preferences
.
Apriamo il file pubspec.yaml e inseriamo sotto la voce dependencies
il nome del plugin come segue
dependencies:
shared_preferences: ^0.5.6+3
Eseguiamo dal nostro terminale il comando
flutter pub get
per installare il plugin.
Definizione della struttura dell’applicazione
Come mostrato nella lezione 32, definiamo la corretta struttura del nostro progetto e creiamo la seguente struttura di cartelle e file .dart, come mostrato di seguito.
lib/
│── screens/
│ │── screen1
│ │ │── components
│ │ │ │── body.dart
│ │ │── screen1.dart
│── theme/
│ │── style.dart
│── services/
│ │── utilities.dart
│── routes.dart
Definizione dell’UI
Ai fini di questo esempio, concentriamo l’attenzione solo ed esclusivamente sulla creazione della nostra interfaccia, lasciando l’implementazione dei temi, delle rotte e del refactoring del file main.dart al lettore. Una possibile implementazione di questi file sarà comunque disponibile su GitHub.
La schermata screen1
sarà costituita da:
- un
TextField
per scrivere una email; - uno
SwitchListTile
per abilitare/disabilitare le notifiche; - un
TextField
in cui riportare il numero di elementi da mostrare in una lista; - un
RaisedButton
per reimpostare i settings di default.
Definiamo quindi la struttura principale della schermata in screen1.dart
class Screen1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Screen1'),
),
body: Body(),
);
}
}
Nel file body.dart invece definiamo il contenuto della nostra pagina. Vediamo una possibile implementazione di seguito.
class Body extends StatefulWidget {
Body({Key key}) : super(key: key);
@override
_BodyState createState() => _BodyState();
}
class _BodyState extends State<Body> {
String _email = "";
bool _isNotification = false;
int _totalItems = 0;
final myEmailController = TextEditingController();
final myTotalItemController = TextEditingController();
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Column(children: <Widget>[
TextField(
decoration: InputDecoration(
hintText: 'write your email',
hintStyle: TextStyle(color: Colors.grey),
icon: Icon(Icons.email),
),
maxLines: 1,
maxLength: 100,
controller: myEmailController,
),
Divider(),
SwitchListTile(
value: _isNotification,
title: Text("Notification"),
onChanged: (value) { });
},
secondary: Icon(Icons.notifications),
),
Divider(),
TextField(
decoration: InputDecoration(
hintText: '0',
hintStyle: TextStyle(color: Colors.grey),
icon: Icon(Icons.list),
),
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
WhitelistingTextInputFormatter.digitsOnly
],
maxLines: 1,
maxLength: 3,
controller: myTotalItemController,
Divider(),
RaisedButton(
onPressed: () { },
child: Text('Reset Settings')
)
]);
}
}
Come di consueto, non abbiamo ancora implementato i metodi necessari per gestire al meglio la nostra interfaccia per:
- aggiornare lo stato con i valori correnti;
- caricare dalle
SharedPreferences
gli eventuali valori disponibili; - inserire i valori nelle
SharedPreferences
; - resettare i valori dei settings a quelli di default.
Ora che la nostra interfaccia è pronta passiamo allo sviluppo del servizio di gestione delle shared preferences.
Definizione della logica per la gestione delle shared preferences
Creiamo all’interno della cartella services il file utilities.dart in cui definire la classe statica SharedPreferencesManager
, che sarà responsabile:
- della definizione delle variabili statiche e finali che rappresentano le chiavi dei valori che vogliamo salvare;
- del recupero dell’istanza di
SharedPreferences
; - del salvataggio dei dati in base al tipo;
- di rimuovere dalle
SharedPreferences
una lista di chiavi.
Compresi i requisiti, vediamo una semplice implementazione.
class SharedPreferencesManager {
static final String emailKey = "email";
static final String notificationKey = "alert";
static final String totalItems = "totalItems";
static Future<SharedPreferences> getSharedPreferencesInstance() async {
return await SharedPreferences.getInstance();
}
static void saveKV(String key, dynamic value) async {
SharedPreferences sharedPreferences = await getSharedPreferencesInstance();
if (value is bool) {
sharedPreferences.setBool(key, value);
} else if (value is String) {
sharedPreferences.setString(key, value);
} else if (value is int) {
sharedPreferences.setInt(key, value);
} else if (value is double) {
sharedPreferences.setDouble(key, value);
} else if (value is List<String>) {
sharedPreferences.setStringList(key, value);
}
}
static void resetSharedPreferences(List<String> list) async{
SharedPreferences sharedPreferences = await getSharedPreferencesInstance();
for(String key in list)
sharedPreferences.remove(key);
}
}
In questo scenario, abbiamo definito i metodi getSharedPreferencesInstance()
e saveKV()
affinché fossero statici e asincroni per permettere il recupero dell’istanza di SharedPreferences
. Inoltre, abbiamo centralizzato nel metodo saveKV()
la scrittura delle coppie KV in base al tipo di valore passato in input. Ad esempio, nel caso di un valore di tipo bool
verrà utilizzato il metodo sharedPreferences.setBool()
.
Infine, nel metodo resetSharedPreferences()
abbiamo offerto la possibilità di cancellare dalle SharedPreferences
una lista di coppie KV a partire da un array di stringhe rappresentanti le chiavi che si vogliono rimuovere. Per farlo abbiamo usato il metodo sharedPreferences.remove()
.
L’unico aspetto non gestito da questa classe è l’estrazione dei dati dalle SharedPreferences in base alla chiave passata come parametro. Questo aspetto è troppo specifico per essere generalizzato.
Aggiornamento della UI con la logica dei servizi
Creiamo i tre metodi che ci permetteranno di:
- caricare i settings dalle
SharedPrefences
; - salvare i dati inseriti dall’utente nei
TextField
quando l’eventoonChanged
viene invocato dal framework di Flutter.
Definiamo il metodo _loadSettings()
che recupera un’istanza di SharedPreferences
tramite il metodo SharedPreferencesManager.getSharedPreferencesInstance()
e carica i possibili valori esistenti in SharedPreferences
assegnandoli ai TextController
dei TextView
e alla variabile bool
_isNotification
.
_loadSettings() async {
SharedPreferences sharedPrefs =
await SharedPreferencesManager.getSharedPreferencesInstance();
setState(() {
myEmailController.text = (sharedPrefs.getString(SharedPreferencesManager.emailKey) ?? "");
_isNotification = (sharedPrefs.getBool(SharedPreferencesManager.notificationKey) ??false);
myTotalItemController.text = (sharedPrefs.getInt(SharedPreferencesManager.totalItems) ?? 0).toString();
});
}
Come possiamo notare, tramite SharedPreferences
abbiamo invocato i metodi getString
, getInt
e getBool
per estrapolare le informazioni di interesse, se salvate.
Aggiorniamo quindi il metodo initState
e la proprietà onPressed
del RaisedButton
.
void initState() {
super.initState();
_loadSettings();
}
// . . .
RaisedButton(
onPressed: () {
SharedPreferencesManager.resetSharedPreferences([
SharedPreferencesManager.emailKey,
SharedPreferencesManager.notificationKey,
SharedPreferencesManager.totalItems
]);
setState(() {
_loadSettings();
});
},
child: Text('Reset Settings')
)
// . . .
In questo modo:
- al caricamento della pagina verrà caricato lo stato iniziale per tutti i campi dei nostri settings;
- abbiamo utilizzato per il
RaisedButton
il metodoSharedPreferencesManager.resetSharedPreferences()
passando la lista di chiavi da rimuovere dalla shared preferences e abbiamo definito l’aggiornamento dello stato.
A questo punto dobbiamo permettere ai TextView
di aggiornare le SharedPreferences
quando l’utente effettua dei cambiamenti nel testo.
void _onTotalItemChanged(String value) {
SharedPreferencesManager.saveKV(SharedPreferencesManager.totalItems, int.parse(value));
}
void _onEmailChanged(String value) {
SharedPreferencesManager.saveKV(SharedPreferencesManager.emailKey, value);
}
Aggiorniamo quindi la proprietà onChanged dei TextView come segue.
TextField(
// . . .
onChanged: _onEmailChanged),
// . . .
TextField(
// . . .
onChanged: _onTotalItemChanged),
Aggiorniamo infine il metodo onChanged
dello SwitchListTile
che si occuperà di aggiornare lo stato e di effettuare il salvataggio della coppia KV per le notifiche.
SwitchListTile(
// . . .
onChanged: (value) {
setState(() {
_isNotification = value;
SharedPreferencesManager.saveKV(SharedPreferencesManager.notificationKey, value);
});
}
),
Eseguiamo l’applicazione per vedere il risultato delle modifiche.
Il codice di questa lezione è disponibile su GitHub.