Managing data on Android has evolved significantly over the years. From SharedPreferences to Room, we’ve seen the full spectrum. But when it comes to storing structured, complex data in a lightweight and efficient way, Proto DataStore steps in as a game-changer.
In this blog, we’ll walk through Proto DataStore, how it works under the hood, and how to use it with Protocol Buffers to store complex objects. We’ll also look at how it stacks up against the older SharedPreferences and why it’s the better modern choice.
Let’s break it down step by step.
What is Proto DataStore?
Proto DataStore is a Jetpack library from Google that helps you store typed objects persistently using Protocol Buffers (protobuf), a fast and efficient serialization format.
It’s:
- Type-safe
- Asynchronous
- Corruption-resistant
- Better than SharedPreferences
Unlike Preferences DataStore, which stores data in key-value pairs (similar to SharedPreferences), Proto DataStore is ideal for storing structured data models.
Why Use Proto DataStore?
Here’s why developers love Proto DataStore:
- Strong typing — Your data models are generated and compiled, reducing runtime errors.
- Speed — Protocol Buffers are faster and more compact than JSON or XML.
- Safe and robust — Built-in corruption handling and data migration support.
- Asynchronous API — Uses Kotlin coroutines and Flow, keeping your UI smooth.
Store Complex Objects with Proto DataStore
Let’s go hands-on. Suppose you want to save a user profile with fields like name, email, age, and preferences.
Step 1: Add the Dependencies
Add these to your build.gradle (app-level):
dependencies {
implementation "androidx.datastore:datastore:1.1.0"
implementation "androidx.datastore:datastore-core:1.1.0"
implementation "com.google.protobuf:protobuf-javalite:3.25.1"
}In your build.gradle (project-level), enable Protobuf:
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.25.1"
}
generateProtoTasks {
all().each { task ->
task.builtins {
java { }
}
}
}
}Also apply plugins at the top:
plugins {
id 'com.google.protobuf' version '0.9.4'
id 'kotlin-kapt'
}Step 2: Define Your .proto File
Create a file named user.proto inside src/main/proto/:
syntax = "proto3";
option java_package = "com.softaai.datastore";
option java_multiple_files = true;
message UserProfile {
string name = 1;
string email = 2;
int32 age = 3;
bool isDarkMode = 4;
}This defines a structured data model for the user profile.
Step 3: Create the Serializer
Create a Kotlin class that implements Serializer<UserProfile>:
object UserProfileSerializer : Serializer<UserProfile> {
override val defaultValue: UserProfile = UserProfile.getDefaultInstance()
override suspend fun readFrom(input: InputStream): UserProfile {
return UserProfile.parseFrom(input)
}
override suspend fun writeTo(t: UserProfile, output: OutputStream) {
t.writeTo(output)
}
}This handles how the data is read and written to disk using protobuf.
Step 4: Initialize the Proto DataStore
Create a DataStore instance in your repository or a singleton:
val Context.userProfileDataStore: DataStore<UserProfile> by dataStore(
fileName = "user_profile.pb",
serializer = UserProfileSerializer
)Now you can access this instance using context.userProfileDataStore.
Step 5: Read and Write Data
Here’s how you read the stored profile using Kotlin Flow:
val userProfileFlow: Flow<UserProfile> = context.userProfileDataStore.dataTo update the profile:
suspend fun updateUserProfile(context: Context) {
context.userProfileDataStore.updateData { currentProfile ->
currentProfile.toBuilder()
.setName("Amol Pawar")
.setEmail("[email protected]")
.setAge(28)
.setIsDarkMode(true)
.build()
}
}Easy, clean, and fully type-safe.
Bonus: Handling Corruption and Migration
Handle Corruption Gracefully
You can customize the corruption handler if needed:
val Context.safeUserProfileStore: DataStore<UserProfile> by dataStore(
fileName = "user_profile.pb",
serializer = UserProfileSerializer,
corruptionHandler = ReplaceFileCorruptionHandler {
UserProfile.getDefaultInstance()
}
)Migrate from SharedPreferences
If you’re switching from SharedPreferences:
val Context.migratedUserProfileStore: DataStore<UserProfile> by dataStore(
fileName = "user_profile.pb",
serializer = UserProfileSerializer,
produceMigrations = { context ->
listOf(SharedPreferencesMigration(context, "old_prefs_name"))
}
)When to Use Proto DataStore
Use Proto DataStore when:
- You need to persist complex, structured data.
- You care about performance and file size.
- You want a modern, coroutine-based data solution.
Avoid it for relational data (instead use Room) or for simple flags (Preferences DataStore may suffice).
Conclusion
Proto DataStore is the future-forward way to store structured data in Android apps. With Protocol Buffers at its core, it combines speed, safety, and type-safety into one clean package.
Whether you’re building a user profile system, app settings, or configuration storage, Proto DataStore helps you stay efficient and future-ready.
TL;DR
Q: What is Proto DataStore in Android?
A: Proto DataStore is a modern Jetpack library that uses Protocol Buffers to store structured, type-safe data asynchronously and persistently.
Q: How do I store complex objects using Proto DataStore?
A: Define a .proto schema, set up a serializer, initialize the DataStore, and read/write using Flow and coroutines.
Q: Why is Proto DataStore better than SharedPreferences?
A: It’s type-safe, faster, handles corruption, and integrates with Kotlin coroutines.
