Hands-on Android Jetpack DataStore — Part 2

Hitesh Das
3 min readNov 28, 2020
Image by Merio from Pixabay

In the first part, we have seen how we can set up the Jetpack DataStore Preferences and also the Proto/Typed DataStore in your android project. In this blog we will see how we can migrate SharedPreferences to Jetpack DataStore Preferences and Proto/Typed DataStore in continuation to the same use cases.

In case you have missed the previous blog, I would recommend readers to read it first so as to have a introduction to Datastore and the set up process.

The Use Case

Our live app has a counter, that keeps track of the number of times our screen is launched and when launched displays the counter from SharedPreferences.

Let’s start the migration…

SharedPreferences -> Preference DataStore

DataStore provides us with SharedPreferenceMigration class, which accepts context and the OLD SHARED PREFERENCES NAME as parameters. For migrating our existing SharedPreferences, we need to create a list of such SharedPreferenceMigration objects.

On being passed as a list to the dataStore builder as shown below, DataStore will automatically migrate all our keys for us.

Please note that migrations are run before any data access can occur in DataStore.

This means that your migration must have succeeded before DataStore.data emits any values and before DataStore.edit() can update data same as we saw in our previous blog.

private val DATASTORE_PREFERENCES_NAME = "migrated_app_datastore_preferences"private val MY_APP_SHARED_PREFERENCES_NAME = "app_shared_preferences"

private val dataStoreObjectForMigration: DataStore<Preferences> =
context.createDataStore(
name = DATASTORE_PREFERENCES_NAME,
migrations = listOf(SharedPreferencesMigration(context, MY_APP_SHARED_PREFERENCES_NAME))
)

private object PreferencesKeys {
val keyAppLaunchCounter =
preferencesKey<Int>(name = "app_launch_counter")
}

And, thats it!!! We can now access the app_launch_counter datastore preferences using the dataStoreObjectForMigration object as shown in the previous blog.

SharedPreferences -> Proto/Typed DataStore

Before the migration, as shown in the previous blog, when working with Proto DataStore, we need to define our schema in a proto file (e.g filter.proto) in the app/src/main/proto/ directory.

For this migration type, we need to provide a mapper along with the SharedPreferenceMigration in order to map the already existing value in our app SharedPreferences to our filter.proto schema as below.

private val sharedPrefsMigration =
SharedPreferencesMigration(
context,
"<old preference file to migrate>"
) { sharedPrefs: SharedPreferencesView, currentData: Filter.AdFilterPreference ->
currentData.toBuilder()
.setAdType(
Filter.AdFilterPreference.AdType.valueOf(
sharedPrefs.getString(
"<old ad type filter key name>",
"TYPE_ALL"
)!!
)
)
.build()
}

Now, we update the DataStore builder and assign to the migrations parameter a new list that contains an instance of our SharedPreferencesMigration

private val dataStoreObjectForMigration: DataStore<Filter.AdFilterPreference> =
context.createDataStore(
fileName = DATASTORE_PREFERENCES_NAME,
serializer = AdFilterPreferenceSerializer(),
migrations = listOf(sharedPrefsMigration)
)

Now, your app is ready to use the Proto/Typed DataStore. Create, Read, Update and Delete using the dataStoreObjectForMigration object thus created.

Using DataStore in synchronous code

If you’re working with an existing codebase that uses synchronous SharedPreferences I/O.

  • Use Kotlin runBlocking() coroutine builder
val exampleData = runBlocking { dataStore.data.first() }

Although it s not recommended to perform synchronous I/O operations on the UI thread which can cause ANRs, you can mitigate these issues by asynchronously preloading the data from DataStore:

  • Asynchronously Preloading

Performing synchronous I/O operations on the UI thread is always not recommended. You can overcome these issues by asynchronously preloading the data from DataStore and later synchronous reads using runBlocking() may be faster or may avoid a disk I/O operation altogether if the initial read has completed.

override fun onCreate(savedInstanceState: Bundle?) {
lifecycleScope.launch {
dataStore.data.first()
}
}

Conclusion

Voila!!! We have just completed the second milestone of migrating to the new Jetpack DataStore.

Please do not forget to hit the clap 👏 👏 👏 👏 👏 button as many times you find this blog helpful. Keep exploring, keep sharing!

--

--

Hitesh Das

Mobile Apps Engineering "When we share, we open doors to a new beginning”