Preference Datastore Android — An implementation guide
What is Datastore?
Datastore is a new and improved data storage solution aimed at replacing Shared Preferences. Built on Kotlin coroutines and Flow.
There are two types of Datastore provided by google: Proto Datastore and Preference Datastore.
Points to note here:
- Preference Datastore is relatively simple and easy to use where Proto Datastore takes a bit longer to configure.
- Proto Datastore uses schema to store objects and it generate classes at compile time, so you have to rebuild project every time you change in schema.
- As Proto Datastore uses schema to store whole object, that’s why Its type-safe, where Preference Datastore uses key-value pairs (No type safety).
- In both types, Data is stored asynchronously, transactionally and consistently that solves problems the Shared Preferences have.
You can find plenty of theory about it on the internet. So, without wasting time, let’s do the practice step-by-step.
1Add the following dependency in app level build.gradle
and sync project.
dependencies { //…
implementation "androidx.datastore:datastore-preferences:1.0.0-alpha05"}
You can find the latest dependency version here
2Create datastore
instance to read and write data. Let’s create a class to manage reading and writing data.
class PreferenceManager(val context: Context) {
val datastore = context.createDataStore(name = "SettingsPrefs")}
name
is the preferences datastore file name
That’s it! 🙏 You are done with configurations. Let’s start reading and writing data from Preference Datastore.
✒️ Preferences Datastore uses
preferencesKey<T>()
rather normal key-value pairs. A special method for preferences datastore.
Reading data from Preference Datastore
Preferences Datastore provides 2 ways to read data.
- It exposes the data stored in a streamed way, a
Flow<T>
typed that will emit every time a preference has changed. So, we do not need to read the whole preference object. - It exposes data in a standalone way, the whole preference then we extract key-value paired data.
Create an object class to maintain your preferencesKey<T>()
object Keys {
val isDarkTheme = preferencesKey<Boolean>("IS_DARK_THEME")
val authToken = preferencesKey<String>("AUTH_TOKEN")
.
.
.
}
Now, Let’s create a public method to get data in Flow<T>
way.
val isDarkThemeFlow : Flow<Boolean> = datastore.data
.map { pref ->
pref[Keys.isDarkTheme] ?: false
}
Then, you can use kotlin’s coroutines suspending function collect
to receive data or can observe in UI by converting Flow toasLiveData()
Handling exception while reading data:
As Datastore reads data from a file, IOException
are thrown when an error occurs while reading data. We can handle these by using the catch
Flow before map
and can emit emptyPreferences()
or throw exception.
val isDarkThemeFlow : Flow<Boolean> = datastore.data
.catch { e ->
e.printStackTrace()
emit(emptyPreferences())
}
.map { pref ->
pref[Keys.isDarkTheme] ?: false
}
Reading data from Preference Datastore can be in standalone [Without Flow]:
To read data as standalone, Datastore provide a suspending first()
function, where we get the data from store [Does not stream]. let’s create a method from where we get data from preference datastore.
suspend fun getIsDarkTheme() : Boolean {
val preferences = datastore.data.first()
return preferences[Keys.isDarkTheme] ?: false
}
Writing data to Preference Datastore
To write data, Datastore provide a suspending edit()
function, where we get MutablePreferences
as parameter the current state of specific key. So, we use MutablePreferences
to update the value of specific key.
suspend fun setIsDarkTheme(isDarkTheme: Boolean) {
datastore.edit { pref ->
pref[Keys.isDarkTheme] = isDarkTheme
}
}
To remove any specific key:
datastore.edit {
it.remove(Keys.isDarkTheme)
}
To clear the preferences file:
datastore.edit {
it.clear()
}
I have create a sample project. There is a generic abstract class BasePreferenceManager.kt that contains a generic implementation of read and write data. you may go and check.