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.
- Store and retrieve values using keys. It has no type-safety.
- 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.
We’ll define our proto schema as below and save it under
You have to create the
protofolder 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
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
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
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.
- 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.
writeToare named as
encoderespectively. And those methods will be inside an
ADAPTERclass 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