Appearance
Kapitel 10: Lokale Speicherung
10.1 Anwendungsfälle der lokalen Speicherung
Warum lokale Speicherung?
Szenarien:
- ✅ Benutzereinstellungen speichern (Theme, Sprache)
- ✅ Login-Status speichern (Eingeloggt bleiben)
- ✅ Offline-Daten speichern (Neuigkeiten-Cache)
- ✅ Benutzerdaten speichern (Profilinformationen)
Verschiedene Speicherlösungen
| Lösung | Anwendungsfall | Vorteile | Nachteile |
|---|---|---|---|
| shared_preferences | Einfache Key-Value-Daten | Einfach, leichtgewichtig | Nur primitive Typen |
| sqflite | Strukturierte Daten (Datenbank) | Leistungsstark, SQL | Komplexer |
| Hive | NoSQL, schnelle Leistung | Sehr schnell, einfach | Benötigt Code-Generierung |
| path_provider | Dateisystem-Zugriff | Zugriff auf Dokumente/Cache | Manuelles Dateihandling |
Für Anfänger: Beginnen Sie mit shared_preferences, dann sqflite.
10.2 shared_preferences (Key-Value-Speicherung)
Installation
pubspec.yaml:
yaml
dependencies:
flutter:
sdk: flutter
shared_preferences: ^2.2.0bash
flutter pub getGrundlegende Verwendung
Speichern (SET):
dart
import 'package:shared_preferences/shared_preferences.dart';
Future<void> speichereDaten() async {
final prefs = await SharedPreferences.getInstance();
// Verschiedene Datentypen speichern
await prefs.setString('name', 'Max Mustermann'); // String
await prefs.setInt('alter', 25); // Integer
await prefs.setBool('istEingeloggt', true); // Boolean
await prefs.setDouble('gewicht', 75.5); // Double
await prefs.setStringList('hobbys', ['Lesen', 'Sport']); // Liste<String>
}Lesen (GET):
dart
Future<void> ladeDaten() async {
final prefs = await SharedPreferences.getInstance();
final name = prefs.getString('name') ?? 'Gast'; // Standardwert
final alter = prefs.getInt('alter') ?? 0;
final istEingeloggt = prefs.getBool('istEingeloggt') ?? false;
final hobbys = prefs.getStringList('hobbys') ?? [];
print('Name: $name, Alter: $alter');
}Löschen (REMOVE):
dart
Future<void> loescheDaten() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove('name'); // Einzelnen Schlüssel löschen
await prefs.clear(); // Alles löschen
}Praxisbeispiel: Login-Status speichern
dart
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
@override
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
bool _istEingeloggt = false;
@override
void initState() {
super.initState();
_pruefeLoginStatus(); // Login-Status beim Start prüfen
}
// Login-Status prüfen
Future<void> _pruefeLoginStatus() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_istEingeloggt = prefs.getBool('istEingeloggt') ?? false;
});
}
// Login durchführen
Future<void> _login() async {
final username = _usernameController.text;
final password = _passwordController.text;
if (username == 'admin' && password == '123456') {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('username', username);
await prefs.setBool('istEingeloggt', true);
setState(() {
_istEingeloggt = true;
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Login erfolgreich!')),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Login fehlgeschlagen!')),
);
}
}
// Logout durchführen
Future<void> _logout() async {
final prefs = await SharedPreferences.getInstance();
await prefs.clear(); // Alle Daten löschen
setState(() {
_istEingeloggt = false;
_usernameController.clear();
_passwordController.clear();
});
}
@override
Widget build(BuildContext context) {
if (_istEingeloggt) {
// Bereits eingeloggt → Profilseite anzeigen
return _buildProfilSeite();
} else {
// Nicht eingeloggt → Login-Formular anzeigen
return _buildLoginFormular();
}
}
Widget _buildLoginFormular() {
return Scaffold(
appBar: AppBar(title: const Text('Login')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
controller: _usernameController,
decoration: const InputDecoration(labelText: 'Benutzername'),
),
const SizedBox(height: 16),
TextField(
controller: _passwordController,
obscureText: true,
decoration: const InputDecoration(labelText: 'Passwort'),
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: _login,
child: const Text('Login'),
),
],
),
),
);
}
Widget _buildProfilSeite() {
return Scaffold(
appBar: AppBar(title: const Text('Profil')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Icon(Icons.person, size: 100, color: Colors.blue),
const SizedBox(height: 20),
FutureBuilder<SharedPreferences>(
future: SharedPreferences.getInstance(),
builder: (context, snapshot) {
if (snapshot.hasData) {
final username = snapshot.data!.getString('username');
return Text(
'Willkommen, $username!',
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
);
}
return const Text('Laden...');
},
),
const SizedBox(height: 30),
ElevatedButton(
onPressed: _logout,
child: const Text('Logout'),
),
],
),
),
);
}
@override
void dispose() {
_usernameController.dispose();
_passwordController.dispose();
super.dispose();
}
}10.3 sqflite (Lokale Datenbank)
Installation
pubspec.yaml:
yaml
dependencies:
sqflite: ^2.3.0
path_provider: ^2.1.0 # Um Datenbankpfad zu erhaltenbash
flutter pub getDatenbank initialisieren
dart
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
class DatabaseHelper {
static Database? _database;
// Singleton-Muster (nur eine Datenbank-Instanz)
Future<Database> get database async {
if (_database != null) return _database!;
_database = await _initDatabase();
return _database!;
}
// Datenbank initialisieren
Future<Database> _initDatabase() async {
// Datenbankpfad erhalten
final documentsDirectory = await getApplicationDocumentsDirectory();
final path = join(documentsDirectory.path, 'app_database.db');
return await openDatabase(
path,
version: 1,
onCreate: _onCreate,
);
}
// Tabellen erstellen
Future<void> _onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE benutzer(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
alter INTEGER,
email TEXT UNIQUE
)
''');
}
}CRUD-Operationen (Create, Read, Update, Delete)
1. Erstellen (INSERT):
dart
Future<int> fuegeBenutzerHinzu(String name, int alter, String email) async {
final db = await database;
return await db.insert(
'benutzer',
{
'name': name,
'alter': alter,
'email': email,
},
conflictAlgorithm: ConflictAlgorithm.replace, // Bei Konflikt ersetzen
);
}2. Lesen (SELECT):
dart
Future<List<Map<String, dynamic>>> holeAlleBenutzer() async {
final db = await database;
return await db.query('benutzer'); // Alle Benutzer abrufen
}
Future<Map<String, dynamic>?> holeBenutzerPerId(int id) async {
final db = await database;
final result = await db.query(
'benutzer',
where: 'id = ?',
whereArgs: [id],
);
return result.isNotEmpty ? result.first : null;
}3. Aktualisieren (UPDATE):
dart
Future<int> aktualisiereBenutzer(int id, String name, int alter) async {
final db = await database;
return await db.update(
'benutzer',
{
'name': name,
'alter': alter,
},
where: 'id = ?',
whereArgs: [id],
);
}4. Löschen (DELETE):
dart
Future<int> loescheBenutzer(int id) async {
final db = await database;
return await db.delete(
'benutzer',
where: 'id = ?',
whereArgs: [id],
);
}Praxisbeispiel: Einfache Kontaktverwaltung
dart
import 'package:flutter/material.dart';
import 'package:sqflite/sqflite.dart';
class Kontakt {
final int? id;
final String name;
final String telefon;
Kontakt({this.id, required this.name, required this.telefon});
Map<String, dynamic> toMap() {
return {
'id': id,
'name': name,
'telefon': telefon,
};
}
factory Kontakt.fromMap(Map<String, dynamic> map) {
return Kontakt(
id: map['id'],
name: map['name'],
telefon: map['telefon'],
);
}
}
class KontaktPage extends StatefulWidget {
const KontaktPage({super.key});
@override
State<KontaktPage> createState() => _KontaktPageState();
}
class _KontaktPageState extends State<KontaktPage> {
List<Kontakt> _kontakte = [];
final _nameController = TextEditingController();
final _telefonController = TextEditingController();
Database? _database;
@override
void initState() {
super.initState();
_initDatabase();
}
Future<void> _initDatabase() async {
_database = await openDatabase(
join(await getDatabasesPath(), 'kontakte.db'),
onCreate: (db, version) {
return db.execute(
'CREATE TABLE kontakte(id INTEGER PRIMARY KEY, name TEXT, telefon TEXT)',
);
},
version: 1,
);
_ladeKontakte();
}
Future<void> _ladeKontakte() async {
final kontakte = await _database?.query('kontakte');
setState(() {
_kontakte = kontakte?.map((map) => Kontakt.fromMap(map)).toList() ?? [];
});
}
Future<void> _hinzufuegenKontakt() async {
await _database?.insert(
'kontakte',
Kontakt(name: _nameController.text, telefon: _telefonController.text).toMap(),
);
_nameController.clear();
_telefonController.clear();
_ladeKontakte();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Kontakte')),
body: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(controller: _nameController, decoration: const InputDecoration(labelText: 'Name')),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(controller: _telefonController, decoration: const InputDecoration(labelText: 'Telefon')),
),
ElevatedButton(onPressed: _hinzufuegenKontakt, child: const Text('Hinzufügen')),
Expanded(
child: ListView.builder(
itemCount: _kontakte.length,
itemBuilder: (context, index) {
final kontakt = _kontakte[index];
return ListTile(
title: Text(kontakt.name),
subtitle: Text(kontakt.telefon),
);
},
),
),
],
),
);
}
}10.4 Lokale Speicherung mit Zustandsverwaltung kombinieren
Ziel: Zustand speichern, App-Neustart überleben
Beispiel: Theme-Einstellung speichern
dart
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class ThemeProvider extends ChangeNotifier {
bool _isDarkMode = false;
bool get isDarkMode => _isDarkMode;
ThemeData get themeData => _isDarkMode ? ThemeData.dark() : ThemeData.light();
ThemeProvider() {
_ladeTheme(); // Beim Start Theme laden
}
Future<void> _ladeTheme() async {
final prefs = await SharedPreferences.getInstance();
_isDarkMode = prefs.getBool('isDarkMode') ?? false;
notifyListeners();
}
Future<void> toggleTheme() async {
_isDarkMode = !_isDarkMode;
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('isDarkMode', _isDarkMode);
notifyListeners();
}
}10.5 Häufige Fehler
Fehler 1: SharedPreferences.getInstance() nicht awaiten
Fehlerhaft:
dart
void ladeDaten() {
final prefs = SharedPreferences.getInstance(); // Fehler: Future nicht awaited!
final name = prefs.getString('name');
}Richtig:
dart
Future<void> ladeDaten() async {
final prefs = await SharedPreferences.getInstance(); // await verwenden!
final name = prefs.getString('name');
}Fehler 2: Datenbank nicht initialisiert
Fehler: App stürzt ab, weil Datenbank noch nicht erstellt wurde.
Lösung: Immer sicherstellen, dass Datenbank initialisiert ist:
dart
Future<void> _initDatabase() async {
_database = await openDatabase(...); // Initialisieren
}
Future<void> fuegeDatenHinzu() async {
if (_database == null) {
await _initDatabase(); // Falls noch nicht initialisiert
}
// Jetzt Datenbank verwenden
}Zusammenfassung
In diesem Kapitel haben Sie:
- ✅ Anwendungsfälle für lokale Speicherung verstanden
- ✅
shared_preferencesfür Key-Value-Speicherung gemeistert - ✅ Ein Praxisbeispiel (Login-Status speichern) implementiert
- ✅
sqflitefür lokale Datenbank-Operationen gelernt - ✅ CRUD-Operationen (Create, Read, Update, Delete) implementiert
- ✅ Lokale Speicherung mit Zustandsverwaltung kombiniert
- ✅ Häufige Fehler behoben
Nächstes Kapitel: Wir werden Styling & Themes lernen (TextStyle, BoxDecoration, ThemeData).
Übungsaufgaben:
- Erstellen Sie eine App, die Benutzereinstellungen (Sprache, Theme) mit
shared_preferencesspeichert - Erstellen Sie eine einfache Notiz-App mit
sqflite(Notizen hinzufügen, anzeigen, löschen) - Kombinieren Sie
Providermitshared_preferences, um Theme-Einstellungen dauerhaft zu speichern - Erstellen Sie eine App, die Kontaktdaten in einer
sqflite-Datenbank verwaltet
Häufige Fehler:
- ❌
SharedPreferences.getInstance()ohneawaitverwenden → App stürzt ab - ❌ Datenbankoperationen ohne
awaitverwenden → Daten werden nicht gespeichert - ❌
setState()nach asynchronen Operationen vergessen → UI aktualisiert sich nicht - ❌ Datenbank nicht vor Verwendung initialisieren → NullPointer-Ausnahme
