Proto Datastore Android — A simple guide to implement

A new and improved way to store data:

Amir Raza
4 min readDec 26, 2020
Image from here

What is Datastore?

Datastore is a new and improved data storage solution aimed at replacing Shared Preferences. Built on Kotlin coroutines and Flow.

There are two types of Datastore provided by google: Proto Datastore and Preference Datastore. You can find plenty of theory about it on the internet. So, without wasting time, let’s do the practice step-by-step.

1Add the following in your app level build.gradle and Sync project.

plugins { 
//…
id "com.google.protobuf" version "0.8.12"
}
dependencies {
//…
implementation "androidx.datastore:datastore:1.0.0-alpha05"
implementation "com.google.protobuf:protobuf-javalite:3.10.0"
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.10.0"
}
//Generates the java Protobuf-lite code for the Protobufs in this project
generateProtoTasks {
all().each { task ->
task.builtins {
java {
option 'lite'
}
}
}
}
}

You can find the latest dependency version here

2Create a folder proto under main directory ../app/src/main . Then create a file with any name you like. e.g. settings.proto and write the following code in this file.

syntax = "proto3"; option java_package = "com.example.prototutorialproject";
option java_multiple_files = true;
message SettingsProto {
bool isDarkTheme = 1;
int32 appVersion = 2;
string created_at = 3;
}

Brief Explanation:

  • proto3 is the protocol buffers language version. See more
  • java_package is your application package
  • java_multiple_files to support multiple files
  • message schema design
  • 1 2 3 are NOT the values but the unique identifiers in the schema

💡 TIP 1: you can create any number of .proto file under proto folder or you can have any number of schema inside one file.

💡 TIP 2: you may need to add Protocol Buffer Editor pluging to support .proto files. To add in Android Studio: File > Settings > Plugins > [search plugin]

⚠️ Note: The class for your stored objects is generated at compile time from the message defined in the proto file. Make sure you rebuild your project any time you change in schema.

3Create a Serializer for your defined schema, see the following:

import androidx.datastore.core.Serializer
import com.google.protobuf.InvalidProtocolBufferException
import java.io.InputStream
import java.io.OutputStream
object SettingsProtoSerializer : Serializer<SettingsProto> {
override val defaultValue: SettingsProto
get() = SettingsProto.getDefaultInstance()

override fun readFrom(input: InputStream): SettingsProto {
return try {
SettingsProto.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
exception.printStackTrace()
defaultValue
}
}

override fun writeTo(t: SettingsProto, output: OutputStream) {
t.writeTo(output)
}
}

4Create datastore instance to read and write data. Let’s create a class to manage reading and writing data.

class ProtoManager(val context: Context) {

val datastore =
context.createDataStore(
fileName = "SettingsPrefFile",
serializer = SettingsProtoSerializer)
}

Brief Explanation:

  • fileName is the file where data can be read/write
  • serializer is the serializing format for the object to which data read/write.

Now, All setups are done. Let’s read and write data from Proto Datastore.

Reading data from Proto Datastore

Proto Datastore exposes the data stored in a Flow<T>. Let's create a public settingsFlow: Flow<SettingsProto> value that gets assigned datastore.data

val settingsFlow: Flow<SettingsProto> = datastore.data

Handling exceptions while reading data
As Datastore reads data from a file, IOException are thrown when an error occurs while reading data. We can handle these by using the catch Flow transformation and just log the error:

val settingsFlow: Flow<SettingsProto> = datastore.data
.catch { e ->
if (e is IOException) {
Log.e("TAG", "Error reading sort order preferences: $e")
emit(SettingsProto.getDefaultInstance())
} else {
throw e
}
}

Reading data from Proto Datastore can be in standalone [Without Flow]:

To read data as standalone, Datastore provide a suspending first() function, where we get the object from store. let’s create a method from where we get object from datastore.

suspend fun getData() : SettingsProto {
return try {
datastore.data.first()
} catch (e : Exception) {
e.printStackTrace()
SettingsProto.getDefaultInstance()
}
}

Writing data to Proto Datastore

To write data, Datastore provide a suspending updateData() function, where we get as parameter the current state of SettingsProto. To update it, we'll have to transform the preferences object to builder, set the new value and then build the new preferences.

updateData() updates the data transactionally in an atomic read-write-modify operation. The coroutine completes once the data is persisted on disk.

suspend fun updateSettings() {
datastore.updateData { pref ->
pref.toBuilder()
.setIsDarkTheme(false)
.setAppVersion(1)
.setCreatedAt("Some Date")
.build()
}
}

TIP: Proto Datastore is NOT storing data in xml format as Shared Preferences does, Its a .pb protocol buffers (protobuf)

That’s It. You have done with the initial practice of Proto Datastore.

--

--