Daten speichern
Dauer: 30 Minuten
shared_preferences
Plugin installieren- Todos werden als JSON-Daten gespeichert
- Daten aus
shared_preferences
speichern und laden
Ziel: Daten in Flutter speichern
In der nativen Android-Entwicklung mit Kotlin haben wir Room eingesetzt und Daten in einer relationalen SQLite-Datenbank gespeichert. Auch in Flutter kann mit SQLite gearbeitet werden, siehe dazu das Cookbook zu SQLite.
Mit unserer Flutter-App wollen wir jedoch einfache Daten wie
die Todo-Liste lokal in einem key/value-Store speichern.
Dafür bietet sich das shared_preferences
-Plugin an, welches
auf allen Flutter-Plattformen (iOS, Android, Web, usw.) funktioniert.
Wir lernen somit noch einen weiteren Ansatz kennen, Daten zu
verwalten. Auch in der nativen Android-Entwicklung gibt es
entsprechend die SharedPreferences
.
shared_preferences
Plugin installieren
Terminal in Project IDX öffnen (View
- Terminal
) und
folgenden Befehl ausführen:
flutter pub add shared_preferences
Dadurch wird das Plugin mit der aktuellen Version in der Datei
pubspec.yaml
hinzugefügt.
Todos als JSON-Daten speichern
Daten werden in shared_preferences
als key/value-Paare
gespeichert, d.h. ein Eintrag mit dem Schlüssel myKey
könnte
den Wert myValue
haben. Sowohl Schlüssel als auch Werte sind
vom Typ String
.
Wir speichern die Todo-Liste als JSON-Daten, d.h. als Zeichenkette.
Dazu wird die soll die gesamte Liste in ein JSON-Objekt umgewandelt
und dieses als String unter dem Schlüsseleintrag todos
gespeichert
werden.
Im Prinzip sieht die Verwendung der shared_preferences
so aus:
final prefs = await SharedPreferences.getInstance();
// Laden der Daten aus dem Speicher (ergibt JSON)
final String? todosString = prefs.getString('todos');
// Danach müssten die JSON-Daten in eine Liste umgewandelt werden
// Speichern der Daten als JSON
await prefs.setString('todos', todosString);
Damit die TodoItem
s in JSON umgewandelt werden können,
müssen wir diese in ein Map konvertieren können und umgekehrt.
Wir fügen daher der Klasse TodoItem
Hilfsmethoden zum
Serialisieren und Deserialisieren eines TodoItems
hinzu:
class TodoItem extends StatefulWidget {
final String title;
const TodoItem({super.key, required this.title});
// TodoItem in Map umwandeln (serialisieren)
Map<String, dynamic> toMap() {
return {
'title': title,
};
}
// Map in TodoItem umwandeln (deserialisieren)
// Factory-Konstruktor, der ein TodoItem aus einer Map erstellt
factory TodoItem.fromMap(Map<String, dynamic> map) {
return TodoItem(
title: map['title'],
);
}
@override
State<TodoItem> createState() => _TodoItemState();
}
Daten aus shared_preferences
speichern und laden
Zunächst müssen wir die shared_preferences
in lib/main.dart
importieren. Zusätzlich wird das dart:convert
-Paket benötigt,
um JSON-Daten zu verarbeiten:
// lib/main.dart
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
Wir definieren in der State-Klasse _MyHomePageState
die Methoden
zum Laden und Speichern der Daten:
class _MyHomePageState extends State<MyHomePage> {
final List<TodoItem> _todos = [];
Future<void> _loadTodos() async {
final prefs = await SharedPreferences.getInstance();
final String? todosString = prefs.getString('todos');
if (todosString != null) {
final List decoded = jsonDecode(todosString);
setState(() {
_todos.clear();
_todos.addAll(decoded.map((item) => TodoItem.fromMap(item)).toList());
});
}
}
Future<void> _saveTodos() async {
final prefs = await SharedPreferences.getInstance();
final String todosString =
jsonEncode(_todos.map((item) => item.toMap()).toList());
await prefs.setString('todos', todosString);
}
// ... restlicher Code ...
}
Stateful Widgets erben eine Methode initState()
, die beim
Erstellen des Widgets aufgerufen wird. Hier können wir die
Daten laden, indem wir dort die eben definierte Methode _loadTodos()
aufrufen:
class _MyHomePageState extends State<MyHomePage> {
final List<TodoItem> _todos = [];
@override
void initState() {
super.initState();
_loadTodos();
}
Dann müssen wir nur noch die Methode _saveTodos()
aufrufen,
wenn sich die Daten ändern. Dazu fügen wir die Methode _saveTodos()
im then
-Block im onPressed
-Handler des FABs hinzu:
// im onPressed-Handler des FABs
.then((value) {
if (value != null && value is String) {
setState(() {
_todos.add(TodoItem(title: value));
});
_saveTodos(); // Daten in shared_preferences speichern
}
});
Nun werden die Daten beim Start der App geladen und beim Hinzufügen eines neuen Todos gespeichert.
Datenklasse für Todos
Beim Speichern der Todos wird momentan nur der Titel berücksichtigt. Der Status in done
wird nicht abgespeichert, da sich dieser im State befindet. Eine Lösung hierfür besteht darin, für das eigentliche Todo eine Datenklasse (Todo
) zu schreiben, die dann im Widget verwendet wird:
class Todo {
final String title;
bool done;
Todo({required this.title, this.done = false});
// Todo in Map umwandeln (serialisieren)
Map<String, dynamic> toMap() {
return {
'title': title,
'done': done,
};
}
// Map in Todo umwandeln (deserialisieren)
factory Todo.fromMap(Map<String, dynamic> map) {
return Todo(
title: map['title'],
done: map['done'] ?? false,
);
}
}
class TodoItem extends StatefulWidget {
final Todo todo;
const TodoItem({super.key, required this.todo});
@override
State<TodoItem> createState() => _TodoItemState();
}
Im _TodoItemState
muss der Zugriff auf done
nun anstatt widget.done
so aussehen:
widget.todo.done
In main.dart
sind drei Zeilen zu ändern:
- in
_loadTodos
:
_todos.addAll(decoded.map((item) => TodoItem(todo: Todo.fromMap(item))).toList());
- in
_saveTodos
:
jsonEncode(_todos.map((item) => item.todo.toMap()).toList());
- im
then
-Block vononPressed
des FABs:
_todos.add(TodoItem(todo: Todo(title: value)));
TODO: Im Code müsste noch umgesetzt werden, dass die Todos erneut abgespeichert werden, wenn sich der Zustand eines Todos ändert (done
).
Zum Thema state management ist umfangreich mit verschiedenen Ansätzen. Dazu gibt es einen Link auf der folgenden Seite im Ausblick.
Zusammenfassung
- In Flutter können einfache Daten als Strings in einem key/value-Store gespeichert werden
- Wir speichern die TODOs komplett als JSON-Daten ab
- Das
shared_preferences
-Plugin ist plattformübergreifend, d.h. unsere Lösung funktioniert auch auf iOS und im Browser