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ć.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
DataStore Tutorial For Android: Getting Started
30 mins
- Getting Started
- Enabling Auto Import
- Implementing Theme Change
- Observing Theme Changes
- Introducing Jetpack DataStore
- Comparing Jetpack DataStore and SharedPreferences
- Migrating SharedPreferences to Preferences DataStore
- Creating an Abstraction for Prefs DataStore
- Creating Prefs DataStore
- Reading Data From Prefs DataStore
- Observing Values From Prefs DataStore
- Writing Data to DataStore
- Introducing Proto DataStore
- Preparing Gradle for Proto DataStore
- Creating Proto Files
- Defining Proto Objects
- Creating a Serializer
- Preparing ProtoStore
- Creating Proto DataStore
- Storing Filter Options
- Reading Filter Options
- Reacting To Filter Changes
- Where to Go From Here?
Migrating SharedPreferences to Preferences DataStore
First, open the app-level build.gradle file and verify you have the following dependency:
implementation "androidx.datastore:datastore-preferences:1.0.0-alpha05"
This dependency lets you use the Prefs DataStore API.
Now you’ll create an abstraction for Prefs DataStore.
Creating an Abstraction for Prefs DataStore
In the project pane on the left, navigate to learningcompanion. Then create a new prefsstore package. In the newly created package, create an Kotlin interface called PrefsStore
and a Kotlin class called PrefsStoreImpl
.
You’ll see a structure like this:
Now open PrefsStore.kt and add:
fun isNightMode(): Flow<Boolean>
suspend fun toggleNightMode()
To import Flow
, select the import from the kotlinx.coroutines.flow package.
You created an interface as an abstraction layer for all of your interactions with the Preferences DataStore. Then you added functons to the interface to represent your DataStore operations. It contains isNightMode()
that returns a Flow
. The Flow
will represent the app setting that tells you if the night mode is on or off.
You also create toggleNightMode()
with the suspend modifier to change the night mode option when the user taps on the theme icon. You added a suspend modifier because the function will contain another suspend function later.
It’s time to write the interface implementation!
Creating Prefs DataStore
Open PrefsStoreImpl.kt and update the class name with a constructor and @Inject
:
class PrefsStoreImpl @Inject constructor(
@ApplicationContext context: Context) : PrefsStore {
}
Here you provide the context you’ll use to create a DataStore. @Inject
lets you use Context
here. @ApplicationContext
and @Inject
are annotations from Hilt, which let you provide the app-level context to your components.
Now, add a constant above the class name:
private const val STORE_NAME = "learning_data_store"
You’ll use this constant as the DataStore name in the next step.
Then, add the following code inside the class:
private val dataStore = context.createDataStore(
name = STORE_NAME,
migrations = listOf(SharedPreferencesMigration(context, PREFS_NAME))
)
Here, you add code for creating an instance of DataStore
using createDataStore()
. Then you pass in the constant value for name
and a list of migrations you want to run upon creation. You use a built-in SharedPreferencesMigration()
to create a migration. This will move all the data from your SharedPreferences
to the DataStore.
To add the missing import, press Option-Return on macOS or Alt-Enter on Windows. Import createDataStore()
without the Serializer
parameter and SharedPreferencesMigration
from androidx.datastore.preferences.
That’s it! It’s that easy to migrate your data from SharedPreferences
to DataStore
. :]
Now that you have your DataStore, it’s time to learn how to read data from it.
Reading Data From Prefs DataStore
In PrefsStoreImpl.kt, at the end of the file, add:
private object PreferencesKeys {
val NIGHT_MODE_KEY = preferencesKey<Boolean>("dark_theme_enabled")
}
This piece of code creates a Kotlin object
which holds the keys you’ll use to read and write data.
Below your dataStore
value, add:
override fun isNightMode() = dataStore.data.catch { exception -> // 1
// dataStore.data throws an IOException if it can't read the data
if (exception is IOException) { // 2
emit(emptyPreferences())
} else {
throw exception
}
}.map { it[PreferencesKeys.NIGHT_MODE_KEY] ?: false } // 3
Here’s a code breakdown:
If the key isn’t set when you try to read the data it returns null
. You use the Elvis operator to handle this and return false
instead.
- On the first line, you access the
data
ofDataStore
. This property returns aFlow
. Then you callcatch()
from the Flow API to handle any errors. - In the lambda block, you check if the exception is an instance of
IOException
. If it is, you catch the exception and return an empty instance ofPreferences
. If the exception isn’tIOException
, you rethrow it or handle it in a way that works for you. - Finally,
map()
returns aFlow
which contains the results of applying the given function to each value of the originalFlow
. In your case, you get the data by using a certain key, thePreferencesKeys.NIGHT_MODE_KEY
.If the key isn’t set when you try to read the data it returns
null
. You use the Elvis operator to handle this and returnfalse
instead.
Now implement toggleNightMode()
from the interface to avoid errors. Add isNightMode()
below:
override suspend fun toggleNightMode() {
}
This code is only a declaration of the method you’ll implement later. So leave the body empty for now.
Next, you’ll observe values from Prefs DataStore.
Observing Values From Prefs DataStore
In the previous step, you wrote the code to read the value from the store, but you didn’t use it. Now, you’ll collect the flow in your ViewModel
.
Open CoursesViewModel.kt. Then remove sharedPrefs
from the constructor and replace it with:
private val prefsStore: PrefsStore
With this code, Hilt library injects the instance for you.
Hooray! You’re not using Shared Preferences anymore so remove everything related to it. Delete:
init
- Everything inside
viewModelScope.launch {}
intoggleNightMode()
- Both
darkThemeEnabled
and_darkThemeEnabled
values
You need to make one more change before Hilt can inject the instance.
Open StoreModule.kt. Uncomment @Binds
and bindPrefsStore()
. While outside of the scope for this tutorial, this code told Hilt how to provide the instances.
Now that you have access to the store, go back to CoursesViewModel.kt. Below the class declaration add:
val darkThemeEnabled = prefsStore.isNightMode().asLiveData()
asLiveData()
lets you convert Flow
to LiveData
.
Flow
won’t emit any values until you subscribe to LiveData
. You can also call collect()
to get the data directly from Flow
without converting it to LiveData
.Build and run. The app should look like before, but now it’s reading from the DataStore instead of SharedPreferences
.
This also proves your migration worked! Now it’s time to write the data to DataStore and toggle the night mode for the app.
Writing Data to DataStore
You finally prepared the code to store the current theme value into the DataStore.
Now, open PrefsStoreImpl.kt. Find toggleNightMode()
and add the following code:
dataStore.edit {
it[PreferencesKeys.NIGHT_MODE_KEY] = !(it[PreferencesKeys.NIGHT_MODE_KEY] ?: false)
}
Here’s a code breakdown:
- To write data to the Prefs DataStore you call
edit()
, which is a suspend and extension function onDataStore
. - It saves data transactionally in an atomic, read-modify-write operation. Atomic means you don’t have to worry about threading, as all operations are safe and there are no race conditions.
- When called, this function suspends the current coroutine until the data persists to disk.
- When this process finishes,
DataStore.data
reflects the change and you get a notification about the changes you subscribed to. - To change the value, you obtain the current value by using
PreferencesKeys.NIGHT_MODE_KEY
. You invert and store it again.
To complete this step, open CoursesViewModel.kt. Then locate toggleNightMode()
and add the following code to launch()
:
prefsStore.toggleNightMode()
Here you call the method you implemented to toggle the current theme.
Build and run. Change the theme and then restart the app. You’ll notice the theme persists.
Congratulations! You successfully implemented the theme change functionality by reading and writing to Prefs DataStore.
Next, you’ll take a look at Proto DataStore, to store more complex types of data.