Jetpack DataStore — Storing Data Asynchronously

Jaydeep Pipaliya
Simform Engineering
4 min readOct 14, 2022
Source: Google Images

Today’s applications use local data storage in one way or another. In almost every application, there would be instances where we need to store data locally, for example, storing the user’s settings. Traditionally we were using the SharedPreferences API to store the data in key-value pairs.

SharedPreferences worked back then; however, the main drawback was its synchronous API to read/write operations that were being performed on the UI thread. But now we have a far better solution to store data locally using Jetpack DataStore.

In this article, we will learn about the Jetpack DataStore, a data storing solution that allows you to store data using key-value pairs and typed objects with protocol buffers known as Proto Datastore.

There are two types of DataStore:

  • Preferences DataStore
  • Proto DataStrore

Difference between SharedPrefrences and Jetpack DataStore

Source: Android Developers blog

Benefits of using Jetpack DataStore

  • Jetpack DataStore performs operations asynchronously using Kotlin coroutines.
  • It performs read/write operations in the background without blocking the main thread.
  • It has an error signaling mechanism, as it uses Kotlin’s Flow API.

Enough talking; let’s get started with some practical examples!

Preferences DataStore

Preferences datastore allows us to store data using key-value pairs. It saves data asynchronously.

Implementation

  1. Add the required dependency for the dataStore in the app-level build.gradle file:
// DataStore
dependencies {
implementation("androidx.datastore:datastore-preferences:1.0.0")
}

2. Create an instance of DataStore:

// Add at the top level of kotlin file
val Context.userPreferredLanguageDataStore: DataStore<Preferences> by preferencesDataStore(name = DATASTORE_NAME)

3. Create keys to use for reading/writing from the DataStore:

companion object {
//DataStore name
const val DATASTORE_NAME = "app_language"
//Keys to use for saving/retrieving the data from DataStore
const val LANGUAGE = stringPreferencesKey("language")
}

4. Write into the DataStore:

DataStore provides the edit() function, which updates the data in DataStore. As it is a suspend function, it must be called from the coroutine scope.

suspend fun saveAppLangauge(language : String) {
context.userPreferredLanguageDataStore.edit { preferences ->
preferences[LANGUAGE] = language
}
}

5. Read from the DataStore:

DataStore uses the Flow API to retrieve data from it. The key type function defines a key for each value that you need to store in the DataStore.

val language: Flow<String> = context.userPreferredLanguageDataStore.data
.map { preferences ->
preferences[LANGUAGE] ?: ""
}

Proto DataStore

Proto Datastore is another way to store data; it allows us to store typed objects using Protocol Buffers.

Implementation

  1. Add the required dependency for the dataStore in the app-level build.gradle file:
plugins {
id "com.google.protobuf" version "0.8.12"
}
dependencies {
implementation "androidx.datastore:datastore-core:1.0.0"
implementation "com.google.protobuf:protobuf-javalite:3.18.0"
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.14.0"
}
generateProtoTasks {
all().each { task ->
task.builtins {
java {
option 'lite'
}
}
}
}
}

2. Create a data class & define schema:

data class Language(
val language: String
)

The schema should be created under the directory app/main/proto, and the file name should have a .proto extension. In our case, it will be LanguagePreferences.proto.

syntax = "proto3";

option java_package = "com.jetpackdatastore";
option java_multiple_files = true;

message LanguagePreferences {
string language = 1;
}

Note: Once a schema is created then, you must have to rebuild the project.

3. Create a DataStore serializer:

We need to define a serializer so that the DataStore can know how to save and read the data.

object LanguagePreferencesSerializer : Serializer<LanguagePreferences> {

override val defaultValue: LanguagePreferences = LanguagePreferences.getDefaultInstance()

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

override suspend fun writeTo(t: LanguagePreferences, output: OutputStream) = t.writeTo(output)
}

4. Create an instance of DataStore:

The file name should have the .pb extension. In our case, it will be language.pb

val Context.userPreferredLanguageDataStore: DataStore<LanguagePreferences> by dataStore(
fileName = "language.pb",
serializer = LanguagePreferencesSerializer
)

5. Write into the DataStore:

suspend fun saveAppLanguage(language: String) {
context.userPreferredLanguageDataStore.updateData {
it
.toBuilder()
.setLanguage(language)
.build()
}
}

6. Read from the DataStore:

val appLanguage: Flow<String> = context.userPreferredLanguageDataStore.data
.map {
it
.language
}

Conclusion

SharedPrefrences has its drawbacks, such as synchronous API calls and no way to handle errors.

DataStorage is a better replacement for SharedPrefrences, as it solves all its shortcomings.

DataStore has a fully asynchronous API using Kotlin coroutines and Flow, which also provides a way to handle errors.

So let’s start migrating to Jetpack DataStore now. Look into the code on GitHub.

Happy Coding!

--

--