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 morejava_package
is your application packagejava_multiple_files
to support multiple filesmessage
schema design1 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.OutputStreamobject 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/writeserializer
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.
I have create a sample project for Proto Datastore, you may go and check