Datastore   Teil von Android Jetpack.

Mit Kotlin Multiplatform testen
Mit Kotlin Multiplatform kann die Datenschicht mit anderen Plattformen geteilt werden. Informationen zum Einrichten und Verwenden von Datastore in KMP

Jetpack Datastore ist eine Datenspeicherlösung, mit der Sie Schlüssel/Wert Paare oder typisierte Objekte mit Protokollpuffern speichern können. Datastore verwendet Kotlin-Coroutinen und Flow, um Daten asynchron, konsistent und transaktional zu speichern.

Wenn Sie SharedPreferences zum Speichern von Daten verwenden, sollten Sie stattdessen zu Datastore migrieren.

Datastore API

Die DataStore-Schnittstelle bietet die folgende API:

  1. Ein Flow, mit dem Daten aus dem Datastore gelesen werden können

    val data: Flow<T>
    
  2. Eine Funktion zum Aktualisieren von Daten im Datastore

    suspend updateData(transform: suspend (t) -> T)
    

Datastore-Konfigurationen

Wenn Sie Daten mit Schlüsseln speichern und darauf zugreifen möchten, verwenden Sie die Preferences Datastore-Implementierung. Diese erfordert kein vordefiniertes Schema und bietet keine Typsicherheit. Sie hat eine SharedPreferences-ähnliche API, aber nicht die Nachteile, die mit freigegebenen Einstellungen verbunden sind.

Mit Datastore können Sie benutzerdefinierte Klassen beibehalten. Dazu müssen Sie ein Schema für die Daten definieren und einen Serializer bereitstellen, um sie in ein dauerhaftes Format zu konvertieren. Sie können Protokollpuffer, JSON oder eine andere Serialisierungsstrategie verwenden.

Einrichtung

Wenn Sie Jetpack Datastore in Ihrer App verwenden möchten, fügen Sie Ihrer Gradle-Datei je nach gewünschter Implementierung Folgendes hinzu:

Preferences DataStore

Fügen Sie der Datei „build.gradle“ die folgenden Zeilen hinzu:

Groovy

    dependencies {
        // Preferences DataStore (SharedPreferences like APIs)
        implementation "androidx.datastore:datastore-preferences:1.2.1"

        // Alternatively - without an Android dependency.
        implementation "androidx.datastore:datastore-preferences-core:1.2.1"
    }
    

Kotlin

    dependencies {
        // Preferences DataStore (SharedPreferences like APIs)
        implementation("androidx.datastore:datastore-preferences:1.2.1")

        // Alternatively - without an Android dependency.
        implementation("androidx.datastore:datastore-preferences-core:1.2.1")
    }
    

Wenn Sie die optionale RxJava-Unterstützung hinzufügen möchten, fügen Sie die folgenden Abhängigkeiten hinzu:

Groovy

    dependencies {
        // optional - RxJava2 support
        implementation "androidx.datastore:datastore-preferences-rxjava2:1.2.1"

        // optional - RxJava3 support
        implementation "androidx.datastore:datastore-preferences-rxjava3:1.2.1"
    }
    

Kotlin

    dependencies {
        // optional - RxJava2 support
        implementation("androidx.datastore:datastore-preferences-rxjava2:1.2.1")

        // optional - RxJava3 support
        implementation("androidx.datastore:datastore-preferences-rxjava3:1.2.1")
    }
    

DataStore

Fügen Sie der Datei „build.gradle“ die folgenden Zeilen hinzu:

Groovy

    dependencies {
        // Typed DataStore for custom data objects (for example, using Proto or JSON).
        implementation "androidx.datastore:datastore:1.2.1"

        // Alternatively - without an Android dependency.
        implementation "androidx.datastore:datastore-core:1.2.1"
    }
    

Kotlin

    dependencies {
        // Typed DataStore for custom data objects (for example, using Proto or JSON).
        implementation("androidx.datastore:datastore:1.2.1")

        // Alternatively - without an Android dependency.
        implementation("androidx.datastore:datastore-core:1.2.1")
    }
    

Fügen Sie die folgenden optionalen Abhängigkeiten für die RxJava-Unterstützung hinzu:

Groovy

    dependencies {
        // optional - RxJava2 support
        implementation "androidx.datastore:datastore-rxjava2:1.2.1"

        // optional - RxJava3 support
        implementation "androidx.datastore:datastore-rxjava3:1.2.1"
    }
    

Kotlin

    dependencies {
        // optional - RxJava2 support
        implementation("androidx.datastore:datastore-rxjava2:1.2.1")

        // optional - RxJava3 support
        implementation("androidx.datastore:datastore-rxjava3:1.2.1")
    }
    

Wenn Sie Inhalte serialisieren möchten, fügen Sie Abhängigkeiten für die Protokollzwischenspeicher- oder JSON-Serialisierung hinzu.

JSON-Serialisierung

Wenn Sie die JSON-Serialisierung verwenden möchten, fügen Sie Ihrer Gradle-Datei Folgendes hinzu:

Groovy

    plugins {
        id("org.jetbrains.kotlin.plugin.serialization") version "2.2.20"
    }

    dependencies {
        implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0"
    }
    

Kotlin

    plugins {
        id("org.jetbrains.kotlin.plugin.serialization") version "2.2.20"
    }

    dependencies {
        implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
    }
    

Protobuf-Serialisierung

Wenn Sie die Protobuf-Serialisierung verwenden möchten, fügen Sie Ihrer Gradle-Datei Folgendes hinzu:

Groovy

    plugins {
        id("com.google.protobuf") version "0.9.5"
    }
    dependencies {
        implementation "com.google.protobuf:protobuf-kotlin-lite:4.32.1"

    }

    protobuf {
        protoc {
            artifact = "com.google.protobuf:protoc:4.32.1"
        }
        generateProtoTasks {
            all().forEach { task ->
                task.builtins {
                    create("java") {
                        option("lite")
                    }
                    create("kotlin")
                }
            }
        }
    }
    

Kotlin

    plugins {
        id("com.google.protobuf") version "0.9.5"
    }
    dependencies {
        implementation("com.google.protobuf:protobuf-kotlin-lite:4.32.1")
    }

    protobuf {
        protoc {
            artifact = "com.google.protobuf:protoc:4.32.1"
        }
        generateProtoTasks {
            all().forEach { task ->
                task.builtins {
                    create("java") {
                        option("lite")
                    }
                    create("kotlin")
                }
            }
        }
    }
    

Datastore richtig verwenden

Beachten Sie bei der Verwendung von Datastore immer die folgenden Regeln:

  1. Erstellen Sie niemals mehr als eine Instanz von DataStore für eine bestimmte Datei in demselben Prozess. Andernfalls kann die gesamte Datastore-Funktionalität beeinträchtigt werden. Wenn im selben Prozess mehrere Datastores für eine bestimmte Datei aktiv sind, löst Datastore beim Lesen oder Aktualisieren von Daten IllegalStateException aus.

  2. Der generische Typ von DataStore<T> muss unveränderlich sein. Wenn Sie einen in Datastore verwendeten Typ ändern, wird die von Datastore bereitgestellte Konsistenz ungültig und es können potenziell schwerwiegende, schwer zu findende Fehler entstehen. Wir empfehlen die Verwendung von Protokollpuffern, die für Unveränderlichkeit, eine klare API und eine effiziente Serialisierung sorgen.

  3. Verwenden Sie nicht SingleProcessDataStore und MultiProcessDataStore für dieselbe Datei. Wenn Sie von mehr als einem Prozess auf den DataStore zugreifen möchten, müssen Sie MultiProcessDataStore verwenden.

Datendefinition

Preferences Datastore

Definieren Sie einen Schlüssel, der verwendet wird, um Daten auf dem Laufwerk zu speichern.

val EXAMPLE_COUNTER = intPreferencesKey("example_counter")

JSON Datastore

Fügen Sie für JSON Datastore eine @Serialization-Annotation zu den Daten hinzu, die Sie beibehalten möchten.

@Serializable data class Settings(val exampleCounter: Int)

Definieren Sie eine Klasse, die Serializer<T> implementiert, wobei T der Typ der Klasse ist, zu der Sie die vorherige Annotation hinzugefügt haben. Fügen Sie einen Standardwert für den Serializer hinzu, der verwendet werden soll, wenn noch keine Datei erstellt wurde.

object SettingsSerializer : Serializer<Settings> {

    override val defaultValue: Settings = Settings(exampleCounter = 0)

    override suspend fun readFrom(input: InputStream): Settings =
        try {
            Json.decodeFromString(Settings.serializer(), input.readBytes().decodeToString())
        } catch (serialization: SerializationException) {
            throw CorruptionException("Unable to read Settings", serialization)
        }

    override suspend fun writeTo(t: Settings, output: OutputStream) {
        output.write(Json.encodeToString(Settings.serializer(), t).encodeToByteArray())
    }
}

Proto Datastore

Die Proto Datastore-Implementierung verwendet Datastore und Protokollpuffer, um typisierte Objekte auf dem Laufwerk zu speichern.

Für Proto Datastore ist ein vordefiniertes Schema in einer Proto-Datei im Verzeichnis app/src/main/proto/ erforderlich. Dieses Schema definiert den Typ für die Objekte, die Sie in Ihrem Proto Datastore beibehalten. Weitere Informationen zum Definieren eines Proto Schemas finden Sie im Leitfaden zur Protokollpuffer-Sprache.

Fügen Sie im Ordner src/main/proto eine Datei mit dem Namen settings.proto hinzu:

syntax = "proto3";

option java_package = "com.example.datastoresampleapp";
option java_multiple_files = true;

message Settings {
  int32 counter = 1;
  bool foo = 2;
}

Definieren Sie eine Klasse, die Serializer<T> implementiert, wobei T der in der Proto-Datei definierte Typ ist. Diese Serializer-Klasse definiert, wie Datastore Ihren Datentyp liest und schreibt. Fügen Sie einen Standardwert für den Serializer hinzu, der verwendet werden soll, wenn noch keine Datei erstellt wurde.

object SettingsSerializer : Serializer<Settings> {
    override val defaultValue: Settings = Settings.getDefaultInstance()

    override suspend fun readFrom(input: InputStream): Settings {
        try {
            return Settings.parseFrom(input)
        } catch (exception: InvalidProtocolBufferException) {
            throw CorruptionException("Cannot read proto.", exception)
        }
    }

    override suspend fun writeTo(t: Settings, output: OutputStream) {
        return t.writeTo(output)
    }
}

Datastore erstellen

Sie müssen einen Namen für die Datei angeben, die zum Speichern der Daten verwendet wird.

Preferences Datastore

Die Preferences Datastore-Implementierung verwendet die DataStore und Preferences Klassen, um Schlüssel/Wert-Paare auf dem Laufwerk zu speichern. Verwenden Sie den Eigenschaftsdelegaten, der von preferencesDataStore erstellt wurde, um eine Instanz von DataStore<Preferences> zu erstellen. Rufen Sie ihn einmal auf der obersten Ebene Ihrer Kotlin-Datei auf. Greifen Sie über diese Eigenschaft auf den Datastore zu. So können Sie Ihren Datastore einfacher als Singleton beibehalten. Der obligatorische Parameter name ist der Name des Preferences Datastore.

// At the top level of your kotlin file:
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")

JSON Datastore

Verwenden Sie den von dataStore erstellten Eigenschaftsdelegaten, um eine Instanz von DataStore<T> zu erstellen, wobei T die serialisierbare Datenklasse ist. Rufen Sie ihn einmal auf der obersten Ebene Ihrer Kotlin-Datei auf und greifen Sie über diesen Eigenschaftsdelegaten auf den Rest Ihrer App zu. Der Parameter fileName gibt an, welche Datei zum Speichern der Daten verwendet werden soll, und der Parameter serializer gibt den Namen der zuvor definierten Serializer-Klasse an.

val Context.dataStore: DataStore<Settings> by
    dataStore(
        fileName = "settings.json",
        serializer = SettingsSerializer,
        scope = CoroutineScope(Dispatchers.IO + SupervisorJob()),
    )

Proto Datastore

Verwenden Sie den von dataStore erstellten Eigenschaftsdelegaten, um eine Instanz von DataStore<T> zu erstellen, wobei T der in der Proto-Datei definierte Typ ist. Rufen Sie ihn einmal auf der obersten Ebene Ihrer Kotlin-Datei auf und greifen Sie über diesen Eigenschaftsdelegaten auf den Rest Ihrer App zu. Der Parameter fileName gibt an, welche Datei zum Speichern der Daten verwendet werden soll, und der Parameter serializer gibt den Namen der zuvor definierten Serializer-Klasse an.

val Context.dataStore: DataStore<Settings> by
    dataStore(fileName = "settings.pb", serializer = SettingsSerializer)

Aus Datastore lesen

Sie müssen einen Namen für die Datei angeben, die zum Speichern der Daten verwendet wird.

Preferences Datastore

Da Preferences Datastore kein vordefiniertes Schema verwendet, müssen Sie die entsprechende Funktion für den Schlüsseltyp verwenden, um einen Schlüssel für jeden Wert zu definieren, den Sie in der DataStore<Preferences> Instanz speichern möchten. Verwenden Sie beispielsweise einen Schlüssel für einen Integerwert zu definieren, verwenden Sie intPreferencesKey. Verwenden Sie dann die DataStore.data Eigenschaft, um den entsprechenden gespeicherten Wert mit einem Flow verfügbar zu machen.

fun counterFlow(): Flow<Int> =
    context.dataStore.data.map { preferences -> preferences[EXAMPLE_COUNTER] ?: 0 }

JSON Datastore

Verwenden Sie DataStore.data, um einen Flow der entsprechenden Eigenschaft aus Ihrem gespeicherten Objekt verfügbar zu machen.

fun counterFlow(): Flow<Int> =
    context.dataStore.data.map { settings -> settings.exampleCounter }

Proto Datastore

Verwenden Sie DataStore.data, um einen Flow der entsprechenden Eigenschaft aus Ihrem gespeicherten Objekt verfügbar zu machen.

fun counterFlow(): Flow<Int> = context.dataStore.data.map { settings -> settings.counter }

Verwenden Sie collectAsStateWithLifecycle, um den Flow zu verwenden, der von einem ViewModel in einem zusammensetzbaren Element erzeugt wird. Dadurch wird der Datastore-Flow sicher in einen Compose-Zustand konvertiert, der eine Neukomposition auslöst.

@Composable
fun SomeScreen(counterFlow: Flow<Int>) {
  val counter by counterFlow.collectAsStateWithLifecycle(initialValue = 0)
  Text(text = "Example counter: ${counter}")
}

Weitere Informationen zu collectAsStateWithLifecycle, finden Sie unter Status und Jetpack Compose.

Schreibzugriff auf Datastore

Datastore bietet eine `updateData`-Funktion, mit der ein gespeichertes Objekt transaktional aktualisiert wird. updateData gibt den aktuellen Status der Daten als Instanz Ihres Datentyps zurück und aktualisiert die Daten transaktional in einem atomaren Lese-, Schreib- und Änderungsvorgang. Der gesamte Code im updateData-Block wird als eine einzelne Transaktion behandelt.

Preferences Datastore

suspend fun incrementCounter() {
    context.dataStore.updateData {
        it.toMutablePreferences().also { preferences ->
            preferences[EXAMPLE_COUNTER] = (preferences[EXAMPLE_COUNTER] ?: 0) + 1
        }
    }
}

JSON Datastore

suspend fun incrementCounter() {
    context.dataStore.updateData { settings ->
        settings.copy(exampleCounter = settings.exampleCounter + 1)
    }
}

Proto Datastore

suspend fun incrementCounter() {
    context.dataStore.updateData { settings ->
        settings.toBuilder().setCounter(settings.counter + 1).build()
    }
}

Datastore in einer Compose-App verwenden

Wenn Sie Datastore in einer Compose-App verwenden möchten, folgen Sie den Richtlinien für die Android-App-Architektur. Bewahren Sie Datastore-Vorgänge in Ihrer Datenschicht (z. B. einem Repository) auf und machen Sie die Daten über ein ViewModel für Ihre UI verfügbar.

Vermeiden Sie es, direkt in Ihren zusammensetzbaren Funktionen aus Datastore zu lesen oder in Datastore zu schreiben.

  1. Datastore über ein ViewModel verfügbar machen. Übergeben Sie Ihr Repository (das den Datastore umschließt) an Ihr ViewModel und konvertieren Sie den Flow in einen StateFlow, damit die UI ihn beobachten kann, wie in dem folgenden Snippet gezeigt:

    class SettingsViewModel(
        private val userPreferencesRepository: UserPreferencesRepository
    ) : ViewModel() {
    
        // Expose the DataStore flow as a StateFlow for Compose
        val userSettings: StateFlow<UserSettings> = userPreferencesRepository.userSettingsFlow
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(5000),
                initialValue = UserSettings.getDefaultInstance()
            )
    
        fun updateCounter(newValue: Int) {
            viewModelScope.launch {
                userPreferencesRepository.updateCounter(newValue)
            }
        }
    }
    
  2. Aus Ihrem zusammensetzbaren Element beobachten und schreiben. Verwenden Sie collectAsStateWithLifecycle, um den StateFlow sicher in Ihrer UI zu beobachten, und rufen Sie die ViewModel-Funktionen auf, um Schreibvorgänge zu verarbeiten, wie im folgenden Snippet gezeigt:

    @Composable
    fun SettingsScreen(
        viewModel: SettingsViewModel = viewModel()
    ) {
        // Safely collect the state
        val settings by viewModel.userSettings.collectAsStateWithLifecycle()
    
        Column(modifier = Modifier.padding(16.dp)) {
            Text(text = "Current counter: ${settings.counter}")
    
            Spacer(modifier = Modifier.height(8.dp))
    
            Button(onClick = { viewModel.updateCounter(settings.counter + 1) }) {
                Text("Increment Counter")
            }
        }
    }
    

Datastore in Code mit mehreren Prozessen verwenden

Sie können Datastore so konfigurieren, dass auf dieselben Daten in verschiedenen Prozessen mit denselben Datenkonsistenzeigenschaften wie in einem einzelnen Prozess zugegriffen wird. Datastore bietet insbesondere die folgenden Eigenschaften:

  • Lesevorgänge geben nur die Daten zurück, die auf dem Laufwerk gespeichert wurden.
  • Read-after-write-Konsistenz
  • Schreibvorgänge werden serialisiert.
  • Lesevorgänge werden nie durch Schreibvorgänge blockiert.

Betrachten Sie eine Beispielanwendung mit einem Dienst und einer Aktivität, wobei der Dienst in einem separaten Prozess ausgeführt wird und den Datastore regelmäßig aktualisiert.

In diesem Beispiel wird ein JSON-Datastore verwendet, Sie können aber auch einen Preferences- oder Proto-Datastore verwenden.

@Serializable data class Time(val lastUpdateMillis: Long)

Ein Serializer gibt DataStore an, wie Ihr Datentyp gelesen und geschrieben werden soll. Fügen Sie einen Standardwert für den Serializer hinzu, der verwendet werden soll, wenn noch keine Datei erstellt wurde. Im Folgenden finden Sie eine Beispielimplementierung mit kotlinx.serialization:

object TimeSerializer : Serializer<Time> {

    override val defaultValue: Time = Time(lastUpdateMillis = 0L)

    override suspend fun readFrom(input: InputStream): Time =
        try {
            Json.decodeFromString(Time.serializer(), input.readBytes().decodeToString())
        } catch (serialization: SerializationException) {
            throw CorruptionException("Unable to read Time", serialization)
        }

    override suspend fun writeTo(t: Time, output: OutputStream) {
        output.write(Json.encodeToString(Time.serializer(), t).encodeToByteArray())
    }
}

Damit Sie DataStore in verschiedenen Prozessen verwenden können, müssen Sie das Datastore-Objekt mit MultiProcessDataStoreFactory für den App und den Dienstcode erstellen:

val dataStore =
    MultiProcessDataStoreFactory.create(
        serializer = TimeSerializer,
        produceFile = { context.dataStoreFile("time.pb") },
        corruptionHandler = null,
    )

Fügen Sie AndroidManifiest.xml Folgendes hinzu:

<service
    android:name="com.example.datastore.snippets.TimestampUpdateService"
    android:exported="false"
    android:process=":service" />

Der Dienst ruft regelmäßig updateLastUpdateTime auf, wodurch mit updateData in den Datastore geschrieben wird.

suspend fun updateLastUpdateTime() {
    dataStore.updateData { time -> time.copy(lastUpdateMillis = System.currentTimeMillis()) }
}

Die App liest den vom Dienst geschriebenen Wert mit dem Datenfluss:

fun timeFlow(): Flow<Long> = dataStore.data.map { time -> time.lastUpdateMillis }

Jetzt können wir alle diese Funktionen in einer Klasse namens MultiProcessDataStore zusammenfassen und in einer App verwenden.

Hier ist der Dienstcode:

class TimestampUpdateService : Service() {
    val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
    val multiProcessDataStore by lazy { MultiProcessDataStore(applicationContext) }


    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        serviceScope.launch {
            while (true) {
                multiProcessDataStore.updateLastUpdateTime()
                delay(1000)
            }
        }
        return START_NOT_STICKY
    }

    override fun onDestroy() {
        super.onDestroy()
        serviceScope.cancel()
    }
}

Und der App-Code:

val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val multiProcessDataStore = remember(context) { MultiProcessDataStore(context) }

// Display time written by other process.
val lastUpdateTime by
    multiProcessDataStore
        .timeFlow()
        .collectAsState(initial = 0, coroutineScope.coroutineContext)
Text(text = "Last updated: $lastUpdateTime", fontSize = 25.sp)

DisposableEffect(context) {
    val serviceIntent = Intent(context, TimestampUpdateService::class.java)
    context.startService(serviceIntent)
    onDispose { context.stopService(serviceIntent) }
}

Sie können die Hilt-Abhängigkeitsinjektion verwenden, damit Ihre Datastore -Instanz pro Prozess eindeutig ist:

@Provides
@Singleton
fun provideDataStore(@ApplicationContext context: Context): DataStore<Settings> =
   MultiProcessDataStoreFactory.create(...)

Dateibeschädigung verarbeiten

In seltenen Fällen kann die dauerhafte Datei von Datastore auf dem Laufwerk beschädigt werden. Standardmäßig wird die Beschädigung von Datastore nicht automatisch behoben. Wenn Sie versuchen, daraus zu lesen, löst das System eine CorruptionException aus.

Datastore bietet eine API für die Beschädigungsbehandlung, mit der Sie sich in einem solchen Szenario ordnungsgemäß erholen und die Ausnahme vermeiden können. Wenn konfiguriert, ersetzt die Beschädigungsbehandlung die beschädigte Datei durch eine neue Datei mit einem vordefinierten Standardwert.

Um diese Behandlung einzurichten, geben Sie beim Erstellen der Datastore-Instanz in by dataStore oder in der Factory-Methode DataStoreFactory einen corruptionHandler an:

val dataStore: DataStore<Settings> = DataStoreFactory.create(
   serializer = SettingsSerializer(),
   produceFile = {
       File("${context.filesDir.path}/myapp.preferences_pb")
   },
   corruptionHandler = ReplaceFileCorruptionHandler { Settings(lastUpdate = 0) }
)

Gerätesicherung und ‑wiederherstellung

Preferences Datastore-Dateien (*.preferences_pb) und Proto Datastore-Dateien werden im Verzeichnis files/datastore/ der App gespeichert. Standardmäßig sind diese Dateien in der automatischen Android-Sicherung und bei Gerät-zu-Gerät-Übertragungen (D2D) enthalten.

Sicherungsregeln für Datastore konfigurieren

Wenn Ihr Datastore neben sensiblen Daten auch nicht sensible Einstellungen (z. B. Nutzerdesign oder Feature-Flags) enthält, trennen Sie sie in separate Datastore-Dateien auf und konfigurieren Sie res/xml/data_extraction_rules.xml:

<data-extraction-rules>
    <cloud-backup>
        <!-- Include general settings -->
        <include domain="file" path="datastore/user_settings.preferences_pb"/>
        <!-- Exclude sensitive local state -->
        <exclude domain="file" path="datastore/secure_state.preferences_pb"/>
    </cloud-backup>
    <device-to-device>
        <!-- Transfer settings during device-to-device transfer -->
        <include domain="file" path="datastore/"/>
    </device-to-device>
</data-extraction-rules>

Feedback geben

Teilen Sie uns Ihr Feedback und Ihre Ideen über diese Ressourcen mit:

Problemverfolgung:
Melden Sie Probleme, damit wir Fehler beheben können.

Zusätzliche Ressourcen

Weitere Informationen zu Jetpack Datastore finden Sie in den folgenden zusätzlichen Ressourcen:

Beispiele

Blogs

Codelabs