AndroidX DataStore – More than just SharedPreferences
With the recent promotion of the new AndroidX DataStore library to “beta” status, it is time to see what it is all about. Introduced as a better replacement of the the trusty old SharedPreferences, it quickly becomes apparent that the DataStore API is capable of more than just storing key/value data.
One of the main culprits of the SharedPreferences API is its mode of persistence. It can either be synchronous or an async operation but without any callback upon completion nor any messages in the case of an error. The SharedPreferences also lack the ability to create type-safe APIs to access the stored data. The table below provides an overview of the feature set of both solutions.
The chart shows two flavors of the DataStore API. One for Preferences and one for protocol buffers. In fact, this is not entirely accurate. The DataStore API comes with a default implementation of its storage mechanism, which uses the established SharedPreferences under the hood. While the protocol buffer-based approach is certainly viable, it has to be integrated manually. In addition, you can plug in any storage solution you like.
Adding support for different storage implementations
In the following sections we will see how we can use the data store class androidx.datastore.core.Serializer
to add storage support via protocol buffers or via the Kotlin serialization library. Both solutions support type safe retrieval of data.
To get familiar with the Serializer
we have to be aware of its core use. The Serializer
provides two methods: One to read from an InputStream
and to convert the stored data to a specific type (in our case, this is the Settings
class) and another method to convert the data type to bytes and to write it to an OutputStream
.
Serializing with Protocol Buffers
The java-based protocol buffers library generates the Settings
class for us. It already contains the required parseFrom
and writeTo
methods, making integration fairly simple.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
object SettingsSerializer : Serializer<Settings> { override val defaultValue: Settings = Settings.getDefaultInstance() override suspend fun readFrom(input: InputStream): Settings { try { return Settings.parseFrom(input) } catch (exception: InvalidProtocolBufferException) { throw CorruptionException("Cannot read proto.", exception) } } override suspend fun writeTo(t: Settings, output: OutputStream) = t.writeTo(output) } |
To make use of our SettingsSerializer
, we initialize the DataStore as seen below.
1 2 3 4 |
private val dataStore by dataStore( fileName = "settings.pb", serializer = SettingsSerializer ) |
Serializing with Kotlin serialization
The Kotlin serialization library integration follows a very similar pattern. The library generates a Settings
class with a Serializer
that we can use with the provided Json
class to convert the data.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
object SettingsSerializer : Serializer<Settings> { override val defaultValue = Settings() override suspend fun readFrom(input: InputStream): Settings { try { return Json.decodeFromString( Settings.serializer(), input.readBytes().decodeToString() ) } catch (exception: SerializationException) { throw CorruptionException("Unable to read Settings", exception) } } override suspend fun writeTo(t: Settings, output: OutputStream) { output.write(Json.encodeToString(Settings.serializer(), t).encodeToByteArray()) } } |
Activating the code looks very similar to the protocol buffers approach:
To make use of the SettingsSerializer
we initialize the DataStore
as seen below.
1 2 3 4 |
private val dataStore by dataStore( fileName = "settings.json", serializer = SettingsSerializer ) |
Consuming data
To read and write data follows the same scheme regardless of the implementation. To read you access the data
object.
1 2 3 |
suspend fun getNightMode(): Boolean { context.dataStore.data.first().nightMode } |
To write you use the updateData
method (here using the Kotlin serialization).
1 2 3 |
suspend fun setNightMode(nightMode: Boolean) { context.dataStore.updateData { it.copy(newNightMode = nightMode) } } |
Conclusion
The AndroidX DataStore mechanism is a great replacement for the aging SharedPreferences
. Not only does it solve several of its shortcomings but it also allows for a more flexible storage mechanism. What do you think of the DataStore library? Have you used it yet? Let us know.
Feedback is welcome!
Want to join the discussion?Feel free to contribute!
Leave a Reply
Want to join the discussion?Feel free to contribute!