Mastering Jetpack DataStore in 2025 — Replace SharedPreferences with Modern APIs
📌 Introduction
SharedPreferences served Android developers well for over a decade. From saving theme preferences to caching login states, it was the go-to tool for key-value data persistence.
But times have changed.
In modern Android development — especially with Kotlin, Jetpack, and Compose — developers need tools that are asynchronous, safe, and scalable. That’s where Jetpack DataStore comes in.
In this article, you’ll learn:
- Why SharedPreferences is no longer ideal
- What Jetpack DataStore is and how it works
- How to implement both Preferences DataStore and Proto DataStore
- Best practices for using DataStore in modern Android apps
- Real-world use cases with Kotlin + Flow + Hilt
✅ What Is Jetpack DataStore?
Jetpack DataStore is Google’s modern data persistence library for small, structured data.
It offers two flavors:
- Preferences DataStore: Key-value pairs (like SharedPreferences but better)
- Proto DataStore: Uses Protocol Buffers for strongly typed, schema-based storage
Key Features
- Kotlin-first
- Coroutine-friendly
- Lifecycle-aware
- Built on Flow (fully observable)
- Safe from ANRs and race conditions
🚀 Getting Started with Preferences DataStore
Step 1: Add dependencies
dependencies {
implementation("androidx.datastore:datastore-preferences:1.1.0")
}
Step 2: Create the DataStore instance
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
Step 3: Define your keys
val THEME_KEY = booleanPreferencesKey("dark_mode_enabled")
Step 4: Read and write values
// Save preference
suspend fun setDarkMode(context: Context, enabled: Boolean) {
context.dataStore.edit { preferences ->
preferences[THEME_KEY] = enabled
}
}
// Observe preference
val darkModeFlow: Flow<Boolean> = context.dataStore.data
.map { preferences -> preferences[THEME_KEY] ?: false }
💡 You can easily collect this flow inside a ViewModel using stateIn() or collectAsState() in Compose.
💪 Using Proto DataStore for Type Safety
Proto DataStore is more structured and suitable for complex settings or configurations.
Step 1: Add dependencies
dependencies {
implementation("androidx.datastore:datastore:1.1.0")
implementation("com.google.protobuf:protobuf-javalite:3.21.12")
}
Step 2: Define your .proto schema
user_prefs.proto:
syntax = "proto3";
option java_package = "com.example.settings";
option java_multiple_files = true;
message UserPreferences {
bool dark_mode = 1;
string username = 2;
}
Compile this with the protobuf plugin to generate classes.
Step 3: Create the DataStore
val Context.userPrefsStore: DataStore<UserPreferences> by dataStore(
fileName = "user_prefs.pb",
serializer = UserPreferencesSerializer
)
The UserPreferencesSerializer must implement Serializer<UserPreferences>.
🧠 Best Practices
- Use Proto DataStore for structured configs, and Preferences DataStore for simple key-value flags.
- Never access DataStore on the main thread — but don’t worry, the APIs are coroutine-based and safe.
- Avoid editing preferences in tight loops or high-frequency triggers.
- Use Flow with StateFlow or LiveData to observe changes reactively.
🔧 DataStore + Hilt + ViewModel Example
In a ViewModel:
@HiltViewModel
class SettingsViewModel @Inject constructor(
private val dataStore: DataStore<Preferences>
) : ViewModel() {
val darkMode: Flow<Boolean> = dataStore.data
.map { it[THEME_KEY] ?: false }
fun setDarkMode(enabled: Boolean) {
viewModelScope.launch {
dataStore.edit { it[THEME_KEY] = enabled }
}
}
}
In Compose:
val darkMode by viewModel.darkMode.collectAsState(initial = false)
Switch(
checked = darkMode,
onCheckedChange = { viewModel.setDarkMode(it) }
)
🎯 When Should You Use DataStore?
- ✅ Saving user settings (theme, language, onboarding, etc.)
- ✅ Storing preferences like sort order, filters, toggles
- ✅ Handling feature flags or configuration
- ✅ Managing login flags or cache markers
- ❌ Not for large data sets or relational data — use Room for that
🏁 Conclusion
Jetpack DataStore is the modern way to persist small amounts of data in Android apps. It’s faster, safer, more flexible, and built for the reactive, coroutine-powered future of Android development.
If you’re still using SharedPreferences in 2025, it’s time to evolve.