Lektion 05 - Flutter Speicherung
JSON in Flutter speichern: Model, State und shared_preferences
In Flutter brauchst du Speicherung, wenn Daten nach dem Schliessen der App
erhalten bleiben sollen. In dieser Lektion speicherst du ein Dart-Model als
JSON-Text mit shared_preferences.
1. Was speichern wir in Flutter?
Flutter zeigt Oberflaechen. Die Daten dahinter liegen in Variablen, Objekten oder State. Ohne Speicherung ist der State nach einem Neustart weg. Mit JSON kannst du einfache App-Daten sichern.
Fuer kleine Daten ist shared_preferences gut. Fuer grosse Datenmengen
nimmt man spaeter eher Datenbanken wie SQLite, Isar, Hive oder Server-APIs.
2. Paket installieren
shared_preferences ist ein Flutter-Paket. Es speichert einfache Werte
dauerhaft auf dem Geraet. Fuer JSON speichern wir einen String.
[bash]
flutter pub add shared_preferences
[/bash]
Danach fuehrst du normalerweise aus:
[bash]
flutter pub get
[/bash]
In deiner Dart-Datei importierst du:
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
3. Das Model fuer gespeicherte Daten
Wir speichern einen Lern-User. Das Model kennt Name, Punkte und erledigte
Lektionen. Es hat fromJson, toJson und copyWith.
class LearningUser {
final String name;
final int points;
final List<String> completedLessons;
const LearningUser({
required this.name,
required this.points,
required this.completedLessons,
});
factory LearningUser.empty() {
return const LearningUser(
name: 'Gast',
points: 0,
completedLessons: [],
);
}
factory LearningUser.fromJson(Map<String, dynamic> json) {
return LearningUser(
name: json['name'] as String,
points: json['points'] as int,
completedLessons: List<String>.from(json['completedLessons'] as List),
);
}
Map<String, dynamic> toJson() {
return {
'name': name,
'points': points,
'completedLessons': completedLessons,
};
}
LearningUser copyWith({
String? name,
int? points,
List<String>? completedLessons,
}) {
return LearningUser(
name: name ?? this.name,
points: points ?? this.points,
completedLessons: completedLessons ?? this.completedLessons,
);
}
}
4. Repository: Speichern und Laden auslagern
Damit dein Widget-Code lesbar bleibt, lagern wir Speicherung in eine eigene Klasse aus. Diese Klasse nennt man haeufig Repository oder Storage-Service.
class UserStorage {
static const String _key = 'learning_user';
Future<void> saveUser(LearningUser user) async {
final preferences = await SharedPreferences.getInstance();
final jsonText = jsonEncode(user.toJson());
await preferences.setString(_key, jsonText);
}
Future<LearningUser> loadUser() async {
final preferences = await SharedPreferences.getInstance();
final jsonText = preferences.getString(_key);
if (jsonText == null) {
return LearningUser.empty();
}
final jsonMap = jsonDecode(jsonText) as Map<String, dynamic>;
return LearningUser.fromJson(jsonMap);
}
Future<void> clearUser() async {
final preferences = await SharedPreferences.getInstance();
await preferences.remove(_key);
}
}
Future bedeutet: Das Ergebnis kommt nicht sofort. Flutter wartet
asynchron, weil Speicherzugriff Zeit brauchen kann.
5. Warum async und await?
Speicherzugriffe sind nicht immer sofort fertig. Mit async markierst du
eine Funktion als asynchron. Mit await wartest du auf das Ergebnis,
ohne die App komplett zu blockieren.
Future<void> loadData() async {
final user = await storage.loadUser();
print(user.name);
}
Future<T>Verspricht spaeter einen Wert vom Typ T.asyncFunktion darf await nutzen.awaitWarte auf ein Future-Ergebnis.6. Widget-State laden
Beim Start eines Screens willst du gespeicherte Daten laden. Das passiert oft in
initState. Weil initState selbst nicht async
sein soll, rufen wir darin eine eigene async-Methode auf.
class LearningHomePage extends StatefulWidget {
const LearningHomePage({super.key});
@override
State<LearningHomePage> createState() => _LearningHomePageState();
}
class _LearningHomePageState extends State<LearningHomePage> {
final UserStorage storage = UserStorage();
LearningUser user = LearningUser.empty();
bool isLoading = true;
@override
void initState() {
super.initState();
loadUser();
}
Future<void> loadUser() async {
final loadedUser = await storage.loadUser();
setState(() {
user = loadedUser;
isLoading = false;
});
}
}
isLoading verhindert, dass die UI so tut, als waeren Daten schon da,
obwohl sie noch geladen werden.
7. State aktualisieren und speichern
Wenn eine Lektion abgeschlossen wird, erstellen wir eine neue User-Version und speichern sie danach. Wichtig: Erst State sauber aendern, dann speichern.
Future<void> completeLesson(String lessonId) async {
if (user.completedLessons.contains(lessonId)) {
return;
}
final updatedUser = user.copyWith(
points: user.points + 10,
completedLessons: [
...user.completedLessons,
lessonId,
],
);
setState(() {
user = updatedUser;
});
await storage.saveUser(updatedUser);
}
Die Zeile ...user.completedLessons nutzt den Spread-Operator. Er kopiert
die alten Lektionen in eine neue Liste. Danach wird die neue Lektion angehaengt.
8. UI-Beispiel
Dieses Beispiel zeigt Name, Punkte und einen Button. Beim Klick wird die Lektion gespeichert und der Punktestand erhoeht.
@override
Widget build(BuildContext context) {
if (isLoading) {
return const Center(
child: CircularProgressIndicator(),
);
}
return Scaffold(
appBar: AppBar(
title: const Text('JSON Speicherung'),
),
body: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Name: ${user.name}'),
Text('Punkte: ${user.points}'),
Text('Fertig: ${user.completedLessons.length}'),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
completeLesson('flutter-json-storage');
},
child: const Text('Lektion abschliessen'),
),
],
),
),
);
}
9. Komplettes Mini-Beispiel als Dateistruktur
Damit dein Projekt ordentlich bleibt, trennst du Model, Storage und Screen. Das
macht den Code lesbar und verhindert, dass main.dart riesig wird.
lib/
main.dart
models/
learning_user.dart
storage/
user_storage.dart
screens/
learning_home_page.dart
learning_user.dartModel mit Feldern, toJson, fromJson.user_storage.dartSpeichern, Laden und Loeschen.learning_home_page.dartUI, State, Button-Aktionen.main.dartApp starten und Screen anzeigen.10. Typische Fehler in Flutter
jsonDecode(null)Vorher pruefen, ob gespeicherter Text existiert.setState nutzen.List<String>.from(...) nutzen.contains pruefen.11. Uebungen
- Lege ein Flutter-Projekt an und installiere
shared_preferences. - Erstelle
LearningUsermittoJson,fromJsonundcopyWith. - Erstelle
UserStoragemitsaveUser,loadUserundclearUser. - Baue einen Screen, der Name, Punkte und erledigte Lektionen anzeigt.
- Baue einen Button, der Punkte erhoeht und speichert.
- Baue einen Reset-Button, der den Speicherstand loescht.
- Starte die App neu und pruefe, ob die Daten erhalten bleiben.
- Erklaere schriftlich den Weg: Model - JSON - String - Speicher - Laden - Model.