DataStore ist eine Komponente aus der Jetpack-Bibliothek, die zum Speichern kleiner Datenmengen in Android-Anwendungen entwickelt wurde. Im Gegensatz zu SharedPreferences arbeitet es asynchron und garantiert Datenkonsistenz bei gleichzeitigem Zugriff. Laut Google, 2024 verwendet DataStore Kotlin Coroutines und Flow, was es sicher für den Hauptthread und geeignet für reaktive Architekturen macht.
Wichtige Punkte
DataStore ist eine Lösung von Google für die lokale Datenspeicherung in Android, die 2020 als Alternative zu SharedPreferences vorgestellt wurde. Es unterstützt zwei Modi: Preferences DataStore (einfache Schlüssel-Wert-Paare) und Proto DataStore (ein typisiertes Schema basierend auf Protocol Buffers).
Der Hauptvorteil ist die vollständige Asynchronität: Alle Lesevorgänge geben einen Flow aus Kotlin Coroutines zurück, und Schreibvorgänge werden in einem Coroutine-Kontext ausgeführt. Dies eliminiert die Blockierung des Hauptthreads, die ein typisches Problem von SharedPreferences bei der Verarbeitung großer Datenmengen war.
DataStore garantiert die Atomizität von Operationen: Gleichzeitige Schreibvorgänge führen dank des Transaktionsmodells nicht zu Datenverlust. Wenn zwei Komponenten gleichzeitig denselben Wert ändern, behandelt DataStore den Konflikt korrekt über einen Compare-and-Swap-Mechanismus.
Laut Google I/O 2023 wird DataStore in 40 % der neuen Android-Projekte verwendet, und Google empfiehlt die Migration von SharedPreferences in allen Anwendungen, bei denen Stabilität der Einstellungsspeicherung erforderlich ist.
Im Kern von DataStore liegt SingleProcessDataStore — eine Implementierung, die innerhalb eines einzelnen Prozesses arbeitet. Es verwendet dateibasierten Speicher mit Dateisperre: Beim Schreiben von Daten wird die Datei gesperrt, was Beschädigungen bei gleichzeitigem Zugriff verhindert.
DataStore behandelt Deserialisierungsfehler automatisch: Wenn die Datei beschädigt ist, gibt es einen Standardwert zurück und überschreibt die Datei. Dieses Verhalten ist über corruptionHandler konfigurierbar, der beim Erstellen des DataStore festgelegt werden kann.
SharedPreferences leidet unter drei grundlegenden Problemen: synchrones Lesen von der Festplatte im Hauptthread, fehlende Atomizitätsgarantien bei gleichzeitigen Schreibvorgängen und die Unfähigkeit, Änderungen reaktiv zu verfolgen. DataStore löst alle drei: Flow zur Beobachtung, Dateisperre für Atomizität und asynchrone API für Thread-Sicherheit.
DataStore speichert Daten in Dateien im internen Speicher des Geräts. Preferences DataStore verwendet ein Dateiformat ähnlich wie SharedPreferences, jedoch mit zusätzlichen Metadaten zur Integritätsprüfung. Proto DataStore verwendet das binäre Protocol Buffers-Format, was die Dateigröße reduziert und die Serialisierung beschleunigt.
Beim Lesen von Daten lädt DataStore die gesamte Datei einmal in den Speicher, woraufhin Abonnenten den aktuellen Zustand über Flow erhalten. Änderungen werden automatisch an alle aktiven Abonnenten übertragen — keine manuelle Listener-Registrierung wie bei SharedPreferences erforderlich.
Preferences DataStore verwendet einen integrierten Serialisierungsmechanismus basierend auf einer Map. Jeder Eintrag ist ein Paar aus einem String und einem primitiven Typ (Int, Boolean, Float, Long, String, Set). Die Daten werden in einer XML-Datei ähnlich wie SharedPreferences gespeichert, jedoch mit atomarem Schreiben durch Dateisperre.
Beispiel zum Erstellen von Preferences DataStore: Die Erweiterung preferencesDataStore auf Context erstellt einen Singleton mit dem Dateinamen. Bei wiederholten Aufrufen wird dieselbe Instanz zurückgegeben — dies eliminiert Dateiduplizierung und Verwirrung zwischen verschiedenen Speicherinstanzen.
Proto DataStore erfordert die Definition eines Datenschemas über eine .proto-Datei und die Kompilierung mit dem protobuf-Plugin. Die generierte Java-Klasse wird als einziger Eintrittspunkt für alle Felder verwendet — dies eliminiert Tippfehler in Schlüsseln, die bei SharedPreferences üblich sind.
Das Proto DataStore-Schema wird einmal definiert und unterstützt das Hinzufügen neuer Felder ohne Verlust alter Daten. Wenn eine neue Version der App ein Feld mit einem Standardwert hinzufügt, wird die alte Datei korrekt deserialisiert — Rückwärtskompatibilität ist in das Protokoll eingebaut.
Die Wahl zwischen Preferences DataStore und Proto DataStore hängt von der Datenkomplexität und den Typisierungsanforderungen ab. Beide Optionen sind asynchron und transaktional, unterscheiden sich jedoch in der Typsicherheit und Serialisierungsleistung.
| Eigenschaft | Preferences DataStore | Proto DataStore |
|---|---|---|
| Typisierung | Schwach (Schlüssel-Wert) | Stark (generierte Klasse) |
| Serialisierung | XML (integriert) | Protocol Buffers (protobuf) |
| Dateigröße | Groß (lesbares XML) | Klein (binär) |
| Komplexität | Niedrig (ohne .proto) | Mittel (.proto erforderlich) |
| Schema-Migration | Kein Schema | Automatisch (proto) |
| Kompatibilität | SharedPreferences (via Migration) | Nur Proto DataStore |
Preferences DataStore eignet sich für einfache Einstellungen: Feature-Flags, Autorisierungstoken-String, Anzahl der App-Starts. Wenn die Daten klein sind (bis zu 10–15 Schlüssel) und kein strenges Schema erfordern, bietet Preferences DataStore eine minimale Einstiegshürde ohne Anschluss des protobuf-Plugins.
Proto DataStore ist gerechtfertigt, wenn die Datenstruktur komplex ist oder sich zwischen App-Versionen ändern kann. Zum Beispiel Benutzerprofileinstellungen oder A/B-Testkonfiguration mit 20+ Feldern. Protobuf bietet starke Typisierung und automatische Migrationen, wodurch Laufzeitfehler aufgrund von Schlüsselkonflikten vermieden werden.
Google bietet einen integrierten Migrationsmechanismus über die SharedPreferencesMigration-Klasse. Die Migration wird einmal beim ersten Start nach dem App-Update durchgeführt: DataStore liest Daten aus SharedPreferences, schreibt sie in sein eigenes Format und markiert die Migration als abgeschlossen.
Die Migration unterstützt benutzerdefinierte Transformationen: Wenn Schlüssel in SharedPreferences nicht mit den gewünschten DataStore-Schlüsseln übereinstimmen, kann eine Transformationsfunktion über SharedPreferencesMigration angegeben werden. Dies ermöglicht das Umbenennen von Schlüsseln und Ändern von Datentypen während der Migration.
Fügen Sie zunächst DataStore zu build.gradle hinzu und erstellen Sie eine DataStore-Instanz mit Migration: SharedPreferencesMigration akzeptiert den SharedPreferences-Dateinamen und einen Satz von zu übertragenden Schlüsseln. Entfernen Sie zweitens allen Code, der über SharedPreferences funktioniert, und ersetzen Sie ihn durch DataStore-Aufrufe. Testen Sie drittens die Migration: Beim ersten Start sollten Daten in DataStore erscheinen und die alte SharedPreferences-Datei sollte nicht mehr verwendet werden.
val Context.dataStore by preferencesDataStore(
name = "settings",
produceMigrations = { context ->
listOf(
SharedPreferencesMigration(context, "old_prefs")
)
}
)
DataStore lässt sich einfach in ein bestehendes Projekt integrieren. Nachfolgend finden Sie praktische Beispiele für Preferences DataStore und Proto DataStore — beide demonstrieren Lesen, Schreiben und reaktive Beobachtung von Daten.
In diesem Beispiel speichert Preferences DataStore drei Einstellungen: dunkles Design, Benutzername und Startanzahl. Das Lesen erfolgt über die Erweiterung .data, die einen Flow zurückgibt. Das Schreiben erfolgt über die Suspend-Funktion .edit, die die Atomizität der Änderungen garantiert.
val Context.settingsDataStore by preferencesDataStore(name = "settings")
val isDarkMode: Flow<Boolean> = settingsDataStore.data
.map { preferences ->
preferences[booleanPreferencesKey("dark_mode")] ?: false
}
suspend fun toggleDarkMode() {
settingsDataStore.edit { prefs ->
val current = prefs[booleanPreferencesKey("dark_mode")] ?: false
prefs[booleanPreferencesKey("dark_mode")] = !current
}
}
Proto DataStore erfordert die Definition einer .proto-Datei. Nach der Kompilierung wird eine UserSettings-Klasse erstellt, die zum Lesen und Schreiben verwendet wird. Versionsmigrationen des Schemas werden in derselben .proto-Datei beschrieben und automatisch angewendet.
// user_preferences.proto
syntax = "proto3";
message UserPreferences {
string display_name = 1;
int32 notification_count = 2;
bool notifications_enabled = 3;
}
// Lesen aus DataStore
val userPreferencesFlow: Flow<UserPreferences> =
protoDataStore.data
// Schreiben neuer Werte
suspend fun updateDisplayName(name: String) {
protoDataStore.updateData { prefs ->
prefs.toBuilder()
.setDisplayName(name)
.build()
}
}
DataStore integriert sich über ViewModel in die MVVM-Architektur. Der Flow von DataStore wird über .stateIn gesammelt und in der UI verwendet. Bei jeder Datenänderung wird die UI automatisch aktualisiert — keine manuellen Updates oder LiveData erforderlich.
class SettingsViewModel(
private val dataStore: DataStore<Preferences>
) : ViewModel() {
val uiState: StateFlow<SettingsUiState> =
dataStore.data
.map { prefs ->
SettingsUiState(
isDarkMode = prefs[booleanPreferencesKey("dark_mode")] ?: false,
counter = prefs[intPreferencesKey("launch_count")] ?: 0
)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = SettingsUiState()
)
}
Häufig gestellte Fragen
DataStore arbeitet asynchron (blockiert nicht den UI-Thread), unterstützt gleichzeitigen Zugriff über Transaktionen und ermöglicht reaktives Abonnieren von Änderungen über Flow. SharedPreferences ist eine synchrone API mit ANR-Risiko bei großen Datenmengen und ohne integrierte Reaktivitätsunterstützung.
DataStore ist in Kotlin geschrieben und erfordert Kotlin Coroutines. Die Verwendung aus Java ist möglich, aber umständlich: Man müsste Wrapper mit CompletableFuture erstellen oder Coroutines manuell verwalten. Für Java-Projekte empfiehlt Google, SharedPreferences zu behalten oder Kotlin zum Modul hinzuzufügen.
DataStore lädt die gesamte Datei beim Lesen in den Speicher, daher ist es nicht geeignet für Listen oder große Objekte. Für solche Szenarien verwenden Sie Room oder SQLite. DataStore ist optimiert für Einstellungen und kleine strukturierte Daten — bis zu Hunderten von Kilobyte.
Beim Erstellen eines DataStore können Sie einen corruptionHandler übergeben — eine Funktion, die bei Beschädigung der Datei aufgerufen wird. Standardmäßig wirft DataStore eine CorruptionException. Im corruptionHandler können Sie leere Daten zurückgeben, woraufhin DataStore die Datei mit einem korrekten Zustand überschreibt.
Ja, Proto DataStore erfordert die Definition eines Schemas in einer .proto-Datei und die Verbindung des protobuf-gradle-plugins. Wenn das Projekt klein und die Daten einfach sind, ist es einfacher, Preferences DataStore zu verwenden — es erfordert keine zusätzliche Build-Konfiguration.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.