Daten speichern
Dauer: 30 Minuten
shared_preferencesPlugin installieren- Todos werden als JSON-Daten gespeichert
- Daten aus
shared_preferencesspeichern und laden
Ziel: Daten in Flutter speichern
Vorstellung des Frameworks
- Installation und Einrichtung der Entwicklungsumgebung
- Prinzipien des Frameworks (Projektstruktur, Komponenten/Widgets, …)
- App mit einem Screen: UI-Elemente, Layout, Interaktion und State
- Daten lokal speichern
- Navigation zwischen mehreren Screens
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_preferencesDadurch 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 Todos in JSON umgewandelt werden können,
müssen wir diese in ein Map konvertieren können und umgekehrt.
Wir fügen daher der Klasse Todo Hilfsmethoden zum
Serialisieren und Deserialisieren eines Todos hinzu:
class Todo {
final String title;
final bool done;
const Todo({required this.title, this.done = false});
Todo copyWith({String? title, bool? done}) =>
Todo(title: title ?? this.title, done: done ?? this.done);
Map<String, dynamic> toMap() {
return {'title': title, 'done': done};
}
factory Todo.fromMap(Map<String, dynamic> map) {
return Todo(title: map['title'], done: map['done'] ?? false);
}
}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 und dart:async, um mit
Future-Objekten zu arbeiten:
// lib/main.dart: weitere imports hinzufügen
import 'dart:async';
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<Todo> _todos = [];
final TextEditingController _textController = TextEditingController();
@override
void initState() {
super.initState();
_loadTodos();
}
Future<void> _loadTodos() async {
final prefs = await SharedPreferences.getInstance();
final String? todosString = prefs.getString('todos');
if (todosString == null || todosString.isEmpty) {
return;
}
try {
final dynamic decoded = jsonDecode(todosString);
if (decoded is! List) {
return;
}
final restored = decoded
.map((item) {
if (item is Map<String, dynamic>) {
return Todo.fromMap(item);
}
if (item is Map) {
return Todo.fromMap(Map<String, dynamic>.from(item));
}
return null;
})
.whereType<Todo>()
.toList();
if (!mounted) {
return;
}
setState(() {
_todos
..clear()
..addAll(restored);
});
} on FormatException {
await prefs.remove('todos');
if (!mounted) {
return;
}
setState(() {
_todos.clear();
});
}
}
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:
@override
void initState() {
super.initState();
_loadTodos();
}Dann müssen wir nur noch die Methode _addTodo() anpassen:
void _addTodo() {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Neues Todo'),
content: TextField(
controller: _textController,
autofocus: true,
decoration: const InputDecoration(hintText: 'Todo-Text eingeben'),
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
_textController.clear();
},
child: const Text('Abbrechen'),
),
TextButton(
onPressed: () async {
final text = _textController.text.trim();
if (text.isEmpty) return;
setState(() {
_todos.add(Todo(title: text));
});
await _saveTodos();
_textController.clear();
if (!mounted) return;
Navigator.of(context).pop();
},
child: const Text('Hinzufügen'),
),
],
);
},
);
}
void _toggle(int index, bool value) {
setState(() {
_todos[index] = _todos[index].copyWith(done: value);
});
unawaited(_saveTodos());
}Nun werden die Daten beim Start der App geladen und beim Hinzufügen eines neuen Todos gespeichert.
Das 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