Jetpack Proto DataStore with Kotlin generated classes for Proto schema

Jetpack DataStore allows us to store minimal key-value pairs or typed objects and acts as a replacement for SharedPreferences. It fixes all the downsides of SharedPreferences and uses Kotlin Flow to provide a flexible experience.

The official documentation is a good place to start learning about Jetpack DataStore.

Jetpack DataStore provides two ways to store and retrieve data.

  1. Store and retrieve values using keys. It has no type-safety.
  2. Store and retrieve values using custom-defined schema using protocol buffers

We are going to concentrate on the second one since it offers type-safety.

Let’s say we have an app to view movie list and movie details. And we want to save id of the movie the user last visited.

Proto definition

We’ll define our proto schema as below and save it under src/main/proto

You have to create the proto folder manually if this is the first proto file you are placing in your project.

And make sure you commit this proto file into Git.

To know about how to define data types, default values, and additional information about proto language, you can find the official guide here.

Gradle setup to generate Java files

Now we have to add the following in the app/module level build.gradle file.

Now if you rebuild the project, a class named UserPreferences.java would have been generated. It will have the variables we declared in the proto file and it will also have a nice Builder class generated to create the UserPreferences instance.

But we don’t use the Builder pattern anymore since Kotlin came into the picture, thanks to the default arguments and copy function in the data class. So how do we generate Kotlin files for our proto schema?

Official Kotlin support for protobuf

I remember seeing about Kotlin support for protobuf in one of the videos from Google I/O that happened this year (2021).

In the protobuf gradle plugin Github repo, Kotlin support was mentioned as experimental. Still, I wanted to use it to see how it works. But I couldn’t any example code snippet to configure proto tasks in gradle to output Kotlin files.

I tried to tweak the default configuration to generate Java files to see if could generate Kotlin files from them, but it was a dead-end because I’m not an expert in gradle.

Wire gradle plugin to the rescue

Then I remember reading an article about Square’s Wire years ago before Kotlin became mainstream. Wire generated Java files alone then, but the main advantage I saw was, it didn’t generate unnecessary getters and setters which eliminated a lot of methods and it supported generating Java classes with Parcelable implementation.

Lots happened between then and now. Now Wire supports generating Kotlin files from proto schema (wire started supporting Kotlin long back it seems).

So I tried using Wire to see how easy the setup is.

Here is the Wire’s guide to setup the gradle plugin to generate Java/Kotlin files from proto files.

And here is the configuration to generate Kotlin files from the proto files.

That’s it. On rebuilding the project, UserPreferences.kt file would have been generated. This file is much smaller than the Java one we generated before.

It has no Builder class, and no getter and setters. All fields are final (val) and we can access them directly. To create a new instance, we can just call the constructor and it already has default arguments for all fields.

It also has a copy function which replaces newBuilder() method from the Java-generated files.

Plugging generated Kotlin classes to Jetpack DataStore

So how do we plug this into Jetpack DataStore?

The official documentation mentions that we need to create a class that extends Serializer for our generated proto classes to let the DataStore know how to read/write our custom data type.

By using the above snippet mentioned in the documentation as a reference, we can create one for the Kotlin generated types.

Here are the things we needed to change from the Java class serializer type.

  1. The Kotlin classes generated by Wire don’t have getDefaultInstance() method. Instead, we can just use the constructor of the Kotlin class. If we don’t pass any parameter, it will use the default values for all fields and it will return a default instance for the type.
  2. parseFrom and writeTo are named as decode and encode respectively. And those methods will be inside an ADAPTER class for each type

That’s it. We generated Kotlin classes for our proto schema using Wire and plugged it into Jetpack DataStore. Now we can follow the steps mentioned in the official docs to read/write values using this Serializer.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Muthu Raj

Muthu Raj

Android Developer at Zoho Corp