Douglas Fornaro
Posted on December 29, 2020
A new way to store data on Android.
Overview
- A new and improved data storage solution aimed at replacing SharedPreferences.
- Kotlin coroutines and Flow.
- Data is stored asynchronously, consistently, and transactionally.
SharePreferences vs DataStore
Features
It provides two different implementations:
- Preferences DataStore: stores primitive data key-value pairs.
- Proto DataStore: stores typed objects.
In both implementations, DataStore saves the preferences in a file and performs all data operations on Dispatchers.IO
unless specified otherwise.
Pre requirement
- Familiarity with coroutines and Kotlin Flow.
Steps
Setup
Gradle:
// Preferences DataStore
implementation βandroidx.datastore:datastore-preferences:1.0.0-alpha05β
// Proto DataStore
implementation βandroidx.datastore:datastore-core:1.0.0-alpha05β
For Proto DataStore:
- Setup protobuf gradle plugin: https://github.com/google/protobuf-gradle-plugin
- Define your schema in a proto file in the
app/src/main/proto/
directory.
Create the DataStore
Preference DataStore:
val dataStore: DataStore<Preferences> = context.createDataStore(name = "settings")
Proto DataStore:
object SettingsSerializer : Serializer<Settings> {
override fun readFrom(input: InputStream): Settings {
try {
return Settings.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", exception)
}
}
override fun writeTo(
t: Settings,
output: OutputStream) = t.writeTo(output)
}
val settingsDataStore: DataStore<Settings> = context.createDataStore(
fileName = "settings.pb",
serializer = SettingsSerializer
)
Read data
Preference DataStore:
val MY_COUNTER = preferencesKey<Int>("my_counter")
val myCounterFlow: Flow<Int> = dataStore.data
.map { currentPreferences ->
// Unlike Proto DataStore, there's no type safety here.
currentPreferences[MY_COUNTER] ?: 0
}
Proto DataStore:
val myCounterFlow: Flow<Int> = settingsDataStore.data
.map { settings ->
// The myCounter property is generated for you from your proto schema!
settings.myCounter
}
Write data
Preference DataStore:
suspend fun incrementCounter() {
dataStore.edit { settings ->
// We can safely increment our counter without losing data due to races!
val currentCounterValue = settings[MY_COUNTER] ?: 0
settings[MY_COUNTER] = currentCounterValue + 1
}
}
Proto DataStore:
suspend fun incrementCounter() {
settingsDataStore.updateData { currentSettings ->
// We can safely increment our counter without losing data due to races!
currentSettings.toBuilder()
.setMyCounter(currentSettings.myCounter + 1)
.build()
}
}
Migrate from SharedPreferences
Preference DataStore:
val dataStore: DataStore<Preferences> = context.createDataStore(
name = "settings",
migrations = listOf(SharedPreferencesMigration(context, "settings_preferences"))
)
Proto DataStore:
val settingsDataStore: DataStore<Settings> = context.createDataStore(
produceFile = { File(context.filesDir, "settings.preferences_pb") },
serializer = SettingsSerializer,
migrations = listOf(
SharedPreferencesMigration(
context,
"settings_preferences"
) { sharedPrefs: SharedPreferencesView, currentData: Settings ->
// Map your sharedPrefs to your type here
}
)
)
Conclusion
Jetpack DataStore is a replacement for SharedPreferences that address some problems such as a synchronous API that can appear to be safe to call on UI thread, no mechanism for signaling errors, lack of transactional and more. DataStore is fully asynchronous API using Kotlin Coroutines and Flow, consistent, handle data migration and data corruption.
Posted on December 29, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.