DataStore Tutorial For Android: Getting Started

In this tutorial you’ll learn how to read and write data to Jetpack DataStore, a modern persistance solution from Google. By Luka Kordić.

4.2 (9) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 3 of 5 of this article. Click here to view the first page.

Introducing Proto DataStore

Proto DataStore uses protocol buffers to serialize data. Protocol buffers are Google’s language-neutral and platform-neutral mechanism for serializing structured data.

You define how you want your data structured once. Then you use special, generated source code to write and read your structured data to and from various data streams while using a variety of languages.

This tutorial only covers the code you need to implement filtering. For more information, visit this protocol buffers tutorial.

The first step to using a Proto DataStore is to prepare your Gradle files. Let’s do that!

Preparing Gradle for Proto DataStore

To work with Proto DataStore gradle files need:

  • The Protobuf plugin
  • The Protobuf and Proto DataStore dependencies
  • Protobuf configuration

The protobuf plugin and dependencies are already in the project. Open the app-level build.gradle file and below plugins sections on the top, paste:

protobuf {
  protoc {
    artifact = "com.google.protobuf:protoc:3.10.0"
  }

  generateProtoTasks {
    all().each { task ->
      task.builtins {
        java {
          option 'lite'
        }
      }
    }
  }
}

Sync the project. You need all of these changes to enable code generation for the files you’ll write in the next section.

Creating Proto Files

Before you jump into the filtering option’s implementation, you need to create a file in which you define proto objects.

Switch to Project view in the Project pane on the left side of Android Studio. Create a proto directory in app/src/main. Inside the new directory, create a file named filter_options.proto.

Your structure will look like this:

Location of the proto file in the project view

Note: You can install an Android Studio plugin which will enable syntax highlighting for proto files. To install the plugin, go to Plugins in AS preferences and search for Protocol Buffer Editor. Select it and click Install. Android Studio might also prompt you to install the plugin once you open the proto file, so that’s an easier way to do it.

Now that you have your proto file, you’ll define the proto objects.

Defining Proto Objects

To implement filtering, you need an object that holds filter data. >You’ll persist this object in Proto DataStore. You’ll describe how you want this object to look, and the Proto Buffer plugin will generate the code for you.

Open filter_options.proto and add:

syntax = "proto3";

option java_package = "com.raywenderlich.android.learningcompanion.data";
option java_multiple_files = true;

message FilterOption {

  enum Filter {
    NONE = 0;
    BEGINNER = 1;
    ADVANCED = 2;
    COMPLETED = 3;
    BEGINNER_ADVANCED = 4;
    BEGINNER_COMPLETED = 5;
    ADVANCED_COMPLETED = 6;
    ALL = 7;
  }

  Filter filter = 1;
}

The first line signals you’re using proto3 syntax. To learn more check out this protocol buffers documentation.

java_package specifies where you want the compiler to put generated files. java_multiple_files set to true means the code generator will create a separate file for each top-level message.

In protobufs, you define every structure using a message keyword followed by its name. You define each member, or field, of the structure inside that message. To define a field, you specify a type, name and unique number.

You can also put enums in a message. When defining enums, the first value always needs a unique number set to 0. Setting it to 0 tells the compiler you want this to be the default value.

For this use-case, you define eight enum values for eight possible filtering combinations. After you create Filter enum, the last line of code above defines a field of type Filter in FilterOption message.

Build the project now by selecting BuildMake Project to generate the classes you described. Nothing will change in the app at this point.

Now it’s time to create the serializer.

Creating a Serializer

To tell DataStore how to read and write the data type you define in filter_options.proto, you need Serializer.

First, in java/learningcompanion create a new package called protostore which will hold all of the code related to Proto DataStore. Create a new Kotlin file in this package called FilterSerializer. Inside the new file add:

class FilterSerializer : Serializer<FilterOption> {
  override fun readFrom(input: InputStream): FilterOption {
    try {
      return FilterOption.parseFrom(input)
    } catch (e: InvalidProtocolBufferException) {
      throw CorruptionException("Cannot read proto.", e)
    }
  }

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

  override val defaultValue: FilterOption = FilterOption.getDefaultInstance()
}

Import InvalidProtocolBufferException from com.google.protobuf.

To create FilterSerializer you implement the Serializer and override its two functions: readFrom() and writeTo(). These two simple functions serialize and deserialize the objects you want to read and write.

The protobuf code generator generates parseFrom() which you can use to deserialize objects. Similarly, it generates writeTo() which you can use to serialize FilterOption and write it to an OutputStream. And finally, return the defaultValue if there’s no data on disk.

Next, you’ll prepare the ProtoStore.

Preparing ProtoStore

Now that you’ve defined your filter object in a proto file and implemented serialization and deserialization mechanisms, it’s time to create an abstraction for the Proto DataStore. In protostore create a new Kotlin file called ProtoStore. Inside this file add:

interface ProtoStore {

  val filtersFlow: Flow<FilterOption>

  suspend fun enableBeginnerFilter(enable: Boolean)

  suspend fun enableAdvancedFilter(enable: Boolean)

  suspend fun enableCompletedFilter(enable: Boolean)
}

This interface exposes the currently selected filter through filtersFlow. It also exposes three methods that let you enable or disable each of the filtering options. You mark each method with suspend because you’ll have to call another suspend function in the implementation.

Now you’ll create the Proto DataStore instance.

Creating Proto DataStore

In protostore, create a new file called ProtoStoreImpl. Open the file and add:

class ProtoStoreImpl @Inject constructor(
  @ApplicationContext private val context: Context) : ProtoStore {}

Here you create a class that implements ProtoStore and uses Hilt to inject the Context. When you create the class, Android Studio gives you an error saying you need to implement all of the methods from the interface.

Put your cursor on the class name and press Option-Return in macOS or Alt-Enter in Windows. In the pop-up that appears, select Implement members. Then, select all methods and press OK.

Leave the generated TODOs for now. You’ll fix them in a moment.

To create a Proto DataStore, add the following code right below the class definition:

private val dataStore: DataStore<FilterOption> = context.createDataStore(
      fileName = "courses.pb",
      serializer = FilterSerializer()
)

Now import createDataStore() with the Serializer parameter.

This code creates a new DataStore by using createDataStore(). You pass in the name of a file where you’ll save data and a serializer you created in the previous step.

It’s finally time for you to save your selected filters.