DataStore and Kotlin serialization

Simona Milanović
Android Developers
Published in
3 min readFeb 9, 2022

--

In the following posts from our Jetpack DataStore series, we will cover several additional concepts to understand how DataStore interacts with other APIs, so that you’d have everything at your disposal to use it in a production environment. In this post, we will be focusing on Kotlin serialization. We will be referring to the Preferences and Proto codelabs throughout this post, for code samples.

Data Store with Kotlin Serialization

In our previous posts, we’ve covered how Preferences and Proto DataStore approach structuring and serializing your persisted data: Proto uses typed objects backed by Protocol Buffers, while Preferences uses key-value pairs as our data representation, similarly to SharedPreferences. Under the hood, both implementations save data on disk, in a file, using protocol buffers. But, DataStore also allows you to customize this and use data classes and Kotlin serialization, giving you the type safety advantage of Proto DataStore, but without having to use protobufs. Let’s see how serialization works by default for each of these approaches:

Serialization options with DataStore

Preferences DataStore simplifies working with protobufs by adding an additional layer on top of its low level Proto implementation. This way, you get a lot of the benefits of working with DataStore, but using a SharedPreferences-like way of structuring data, using key-value pairs.

If we look at the Preferences API’s PreferencesSerializer and our custom ProtoUserPreferencesSerializer, you will notice that they pretty much do the same thing. PreferencesSerializer just has an additional step of transforming the key-value pairs into protobufs and vice versa:

Implementing DataStore with Kotlin serialization

If you’d like to use Kotlin serialization to structure your data, all you need to do is define a fully immutable data class and implement a DataStore Serializer.

DataStore relies on equals and hashCode which are automatically generated for data classes. Data classes also generate toString and copy functions which are useful for debugging and updating data:

🚨 It is very important to ensure that your class is immutable since DataStore is not compatible with mutable types. Using mutable types with DataStore will result in bugs due to data inconsistency and race conditions. Data classes aren’t necessarily immutable by default, so make sure you use vals everywhere instead of vars:

Arrays are mutable, so you shouldn’t expose them. Even if we use the read-only List as a member of our data class, it’s still mutable. Instead you should consider using immutable/persistent collections:

Using mutable types as a member of your data class makes it mutable. Instead, you should ensure that all members are immutable types.

Kotlin Serialization supports multiple formats, including JSON and Protocol Buffers. In this example, we’ll continue with JSON.

In order to read and write your data class to JSON using Kotlin serialization, you need to annotate your data class with @Serializable and override the Serializer’s writeTo() and readFrom(). Here’s an example with UserPreferences:

⚠️ Parcelables are not safe to use with DataStore because the data format may change between Android versions.

Pass the newly created UserPreferencesSerializer into DataStore when constructing it:

Reading data looks the same as with protobufs:

You can use the generated .copy() function to update data:

Using DataStore with Kotlin Serialization and data classes can reduce boilerplate and help simplify your code, however, you must be careful not to introduce bugs through mutability. All you need to do is define your data class and implement the serializer.

To be continued

We’ve covered Kotlin Serialization and the necessary steps on how to use it for structuring DataStore persisted data — using fully immutable data classes, writing them to JSON with the@Serializable annotation, overriding our Serializer’s writeTo() and readFrom() and finally, passing this to our DataStore instance.

Join us for the next post of our series where we will be looking into how to do synchronous work with DataStore.

You can find all posts from our Jetpack DataStore series here:
Introduction to Jetpack DataStore
All about Preferences DataStore
All about Proto DataStore
DataStore and dependency injection
DataStore and Kotlin serialization
DataStore and synchronous work
DataStore and data migration
DataStore and testing

--

--

Simona Milanović
Android Developers

Android Developer Relations Engineer @Google, working on Jetpack Compose