The How

To get started, there is a need to add the following dependencies to your application level build.gradle:

 implementation "androidx.datastore:datastore-preferences:1.0.0"
implementation "com.google.protobuf:protobuf-javalite:3.18.0"

Then, you will need to create a proto directory inside your project. This directory needs to be a sibling of the java folder in your project structure. Inside of the proto directory, you will be creating a .proto file. This file is responsible for generating the data types you wish to store in Proto DataStore.

Inside the proto directory, create a file with the .proto extension. Our .proto file will hold objects representing a Todo list (what else?). So we will call our file todo.proto and it will look like this:

syntax = "proto3";

option java_package = "com.yourPackageName.todo";
option java_multiple_files = true;

message TodoItem {
string itemId = 1;
string itemDescription = 2;
}

message TodoItems {
repeated TodoItem items = 1;
}

Notice how we defined two message objects:

  1. TodoItem – that defines a todo item
  2. TodoItems – that defines a list of TodoItem objects

Next, build the project so that classes will be generated for TodoItem and TodoItems.

After our data objects have been defined, we need to create a class to serialize them. This class will tell the DataStore how to read/write our objects.

// 1
object TodoItemSerializer: Serializer<TodoItems> {
// 2
override val defaultValue: TodoItems = TodoItems.getDefaultInstance()
// 3
override suspend fun readFrom(input: InputStream): TodoItems {
try {
return TodoItems.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", exception)
}
}
// 3
override suspend fun writeTo(
t: TodoItems,
output: OutputStream
) = t.writeTo(output)
}

Let’s review what we have in this class:

  1. When we declare the class, we need to implement the Serializer<T> interface with our object as the type (T)
  2. We define a default value for the serializer in case the file is not created
  3. We override the readFrom/writeTo methods and we make sure to have our object as the data type there

We have our .proto file with our data types and our serializer, so the next step is to instantiate the DataStore. We do this by using the property delegate created by dataStore, which requires giving a filename where our data will be saved and our serializer class (which we defined above).

private const val DATA_STORE_FILE_NAME = "todo.pb"

private val Context.todoItemDatastore: DataStore<TodoItems> by dataStore(
fileName = DATA_STORE_FILE_NAME,
serializer = TodoItemSerializer,
)

This piece of code needs to reside at the top of a class of your choosing above the definition of the class itself. I.E.

private const val DATA_STORE_FILE_NAME = "todo.pb"

private val Context.todoItemDatastore: DataStore<TodoItems> by dataStore(
fileName = DATA_STORE_FILE_NAME,
serializer = TodoItemSerializer,
)

class YourClassName {

}

To access this object in the rest of our application, we will need to use a context. An example is to use the application context in your viewmodel class:

class MyViewModel(application: Application): AndroidViewModel(application) {

val todoDataStore = application.todoItemDataStore
//...
}

Source link