Kotlin delegated property for Datastore Preferences library

Recently AndroidX Datastore 1.0 was released. The moment has come, it’s time to migrate away from SharedPreferences.
As for me, the key benefit is the built-in support of Kotlin Flow and Coroutines.
Previously we had to write our own wrapper under SharedPreferences.OnSharedPreferenceChangeListener, emit changes into ConflatedBroadcastChannel and then convert it into Flow.
Firstly, this approach produces a lot of intermediate variables. Secondly, ConflatedBroadcastChannel deprecated and should be replaced with StateFlow. In this case, DataStore comes out as the winner.
The migrated to DataStore version looks clean and simple.
Read/write single value using SharedPreferences
Let’s imagine, we need to store the current App theme as String. For this purpose, we need to initialize SharedPreferences, then make two functions to read and write these values.
Well, two functions it is not so bad, but we can try to merge this into single Property.
To make it fully clear and supportable we can use the Kotlin delegation feature. As we want to read and write simultaneously, ReadWriteProperty will suit our goal.
Please take a look at the final compact version, where all logic is encapsulated inside the delegate.
DataStore Preferences and missing API
During migration, it turned out that the library doesn’t provide an API for reading a single property.
DataStore proposes to use Flow for all cases. Developers are now required to read/write the value in the scope of a suspend function.
What was easy in SharedPreferences like getString(key, defaultValue) became impossible in DataStore. I just wanna read a single value 😅
Let’s make a small crutch
As DataStore returns only Flow<T> we have to work with that. We want to read a single value from Flow. Coroutines library already contains necessary extensions and Flow<T>.first() perfect for our purpose.
The terminal operator that returns the first element emitted by the flow and then cancels flow’s collection. Throws NoSuchElementException if the flow was empty.
And as first() is suspend function, we can use the runBlocking{} block.
Now let’s wrap this functionality into old delegates, replace SharedPreferences with DataStore and change the key.
Note: DataStore has special wrapper under Key, in this case we can use generics. For SharedPreferences delegates it is necessary to create separate delegation class for each privitive type as we need to use typed functions getString()/putString(), getInt()/putInt() and etc.
The final result with datastore delegates will look like this:
Full listing available on:
Please don’t forget to start the project:
Link to delegates source code: