Skip to Content
Mobile Apps4 - FlutterDaten speichern

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

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_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 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

Cookbook zu shared_preferences