iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🙌

Trying Out Jetpack DataStore

に公開

What is DataStore?

An evolution of SharedPreferences that allows you to store key-value pairs or typed objects using protocol buffers.

There are two types of DataStore:

  • Preferences DataStore
    • Stores and accesses data using keys. This implementation does not require a
      predefined schema, but it is not type-safe.
  • Proto DataStore
    • Stores data as instances of a custom data type. This implementation requires you to
      define a schema using protocol buffers, but it is type-safe.

Environment

I have created a repository here for the sample.

macOS Catalina ver 10.15.7 (Intel version)
Android Studio Arctic Fox | 2020.3.1 Patch 2

Preferences DataStore

First, let's try Preferences DataStore. Create a project and add the following to app/build.gradle.

implementation "androidx.datastore:datastore-preferences:1.0.0"

Let's try to persist simple key-value pairs to disk using the DataStore and Preferences classes.
Add the following to the top of your Kotlin file to treat DataStore as a singleton.

import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore

val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")

Writing code to save and read values

Simply save -> read and log the output.

val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
val TEXT_KEY = stringPreferencesKey("example_text")

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...

        GlobalScope.launch {
            saveText(this@MainActivity, "sample")

            val textFlow: Flow<String> = dataStore.data.map { p -> p[TEXT_KEY] ?: "" }
            textFlow.collect { Log.d("DataStore", "text = $it") }
        }
    }
}

suspend fun saveText(context: Context, text: String) {
    context.dataStore.edit { settings ->
        settings[TEXT_KEY] = text
    }
}

It's a success if DataStore: text = sample is output.

Proto DataStore

Next, let's try Proto DataStore.

Setup

First, modify the build.gradle within the module as follows.

plugins {
    id "com.google.protobuf"
}

dependencies {
    implementation "androidx.datastore:datastore:1.0.0"
    implementation  "com.google.protobuf:protobuf-javalite:3.14.0"
}

protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.14.0"
    }

    // Generates the java Protobuf-lite code for the Protobufs in this project. See
    // https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation
    // for more information.
    generateProtoTasks {
        all().each { task ->
            task.builtins {
                java {
                    option 'lite'
                }
            }
        }
    }
}

Add the following to the dependencies in the project-root build.gradle.

dependencies {
        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.17'
}

Next, create settings.proto in app/src/main/proto.

syntax = "proto3";

option java_package = "com.slowhand.datastoresample.model";
option java_multiple_files = true;

message Settings {
  int32 example_counter = 1;
}

Create Settings.kt with the following content.

package com.slowhand.datastoresample.model

import android.content.Context
import androidx.datastore.core.CorruptionException
import androidx.datastore.core.DataStore
import androidx.datastore.core.Serializer
import androidx.datastore.dataStore
import androidx.datastore.preferences.protobuf.InvalidProtocolBufferException
import java.io.InputStream
import java.io.OutputStream

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
    ) = t.writeTo(output)
}

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

Let's try saving and reading.

GlobalScope.launch {
    incrementCounter(this@MainActivity)
    val exampleCounterFlow: Flow<Int> = settingsDataStore.data
        .map { settings ->
            settings.exampleCounter
        }
    exampleCounterFlow.collect { Log.d("DataStore", "counter = $it")}
}

suspend fun incrementCounter(context: Context) {
    context.settingsDataStore.updateData { currentSettings ->
        currentSettings.toBuilder()
            .setExampleCounter(currentSettings.exampleCounter + 1)
            .build()
    }
}

It's a success if the log for counter = is output.

References

Discussion