Jetpack DataStore Preferences

Banner Jetpack Datastore Preferences

In this blog, we are going to see why we need Jetpack DataStore Preferences, learn how to implement Jetpack DataStore Preferences in our Android application and how can we migrate our SharedPreferences to DataStore Preferences.

I will be sharing what I learned from the Android Official documentation and why I am recommending the new Jetpack DataStore from my practical experience.

Topics to be covered in the blog:

  • Why Jetpack DataStore?
  • SharedPreferences vs DataStore Preferences
  • Implementing Jetpack DataStore Preferences
  • Migration from SharedPreferences to DataStore Preferences

First things first, why this new Jetpack DataStore?

Why Jetpack DataStore?

As per the official documentation:

  • Jetpack DataStore is a new and improved data storage solution aimed at replacing SharedPreferences.
  • It is built on Kotlin Coroutines and Flow.
  • Data is stored asynchronously, consistently, and transactionally, overcoming most of the drawbacks of SharedPreferences.

The above-mentioned improvements are great.

I will tell you a reason, why I am recommending the new Jetpack DataStore from my practical experience.

I was working on an Android application having more than 200 million downloads, the app is on Google Play Store for more than 7 years. In this long time period, the app went through huge development, many releases, and many bug fixes.

So, at some point, the app started getting ANR(Application Not Responding).

The reason for ANR: The app is doing a long-running task on the UI Thread. (more than 5 seconds).

Why this ANR was coming?

The major reason was that our shared preferences file had become too big as we kept adding new key-value one after one. We were trying to access the value for a particular key as soon as the app opens on the UI Thread. But the thing is that when you access the SharedPreferences for the first time, it reads the whole file, brings the data in memory. And for us, this was happening on UI Thread.

This is an I/O operation. It can take time. For the bigger file in our case, it was leading to ANR.

We built our own solution for that. This is out of the scope of this blog. I might plan a detailed blog on that solution.

And the current implementation of Jetpack DataStore does not encourage the reading of the data on UI Thread. This is something I really liked about it.

And now, we are adding Jetpack DataStore to our application: CuriousJr - Coding for Kids.

Now, let's talk about SharedPreferences vs DataStore Preferences.

SharedPreferences vs DataStore Preferences

The following are the difference between SharedPreferences and DataStore Preferences:

  • Both provide the Async API.
  • SharedPreferences provide a simple Synchronous API but not safe to call on UI thread. DataStore Preferences do not encourage this.
  • Consistency is guaranteed in DataStore Preferences.
  • Error handling is supported in DataStore Preferences.
  • DataStore Preferences support Kotlin Coroutines Flow API by default.

This is how the DataStore Preferences is an improved solution over the SharedPreferences.

Now, let's move to the implementation part.

Implementing Jetpack DataStore Preferences

Add the following dependency in your app level build.gradle.

implementation "androidx.datastore:datastore-preferences:1.0.0-alpha01"

Note: Make sure to use the latest version for the most stable release.

Now, similar to the SharedPreferences object, we need to create the object of DataStore Preferences.

val dataStore: DataStore<Preferences> =
    context.createDataStore(name = "mindorks-data-store-prefs")

Then, we create two extension functions to use them to read and write data. This is just for convenience.

fun <T> DataStore<Preferences>.getValueFlow(
    key: Preferences.Key<T>,
    defaultValue: T
): Flow<T> {
    return this.data
        .catch { exception ->
            if (exception is IOException) {
                emit(emptyPreferences())
            } else {
                throw exception
            }
        }.map { preferences ->
            preferences[key] ?: defaultValue
        }
}

suspend fun <T> DataStore<Preferences>.setValue(key: Preferences.Key<T>, value: T) {
    this.edit { preferences ->
        preferences[key] = value
    }
}

Then, we create Preferences Keys like below:

companion object {
    private val USERNAME = preferencesKey<String>("username")
}

Now, writing the value to the DataStore Preferences:

viewModelScope.launch {
    dataStore.setValue(USERNAME, "Amit Shekhar")
}

Now, reading the data from the DataStore Preferences:

viewModelScope.launch {
    dataStore.getValueFlow(USERNAME, "")
        .collect { value ->
            // use the value
        }
}

Here, we can handle the error by using the catch operator on the flow.

viewModelScope.launch {
    dataStore.getValueFlow(USERNAME, "")
        .catch {
            // handle error
        }
        .collect { value ->
            // use the value
        }
}

This is how we can easily use it in our Android Application.

Now, let's talk about the migration from SharedPreferences to DataStore Preferences.

Migration from SharedPreferences to DataStore Preferences

When it comes to migration, DataStore handles it for us. We just have to provide the names of the SharedPreferences. For example, if "mindorks-prefs" is the name of SharedPreferences, we will have to do like below:

val dataStore: DataStore<Preferences> =
    context.createDataStore(
        name = "mindorks-data-store-prefs",
        migrations = listOf(SharedPreferencesMigration(context, "mindorks-prefs"))
    )

When we check the SharedPreferencesMigration function:

fun SharedPreferencesMigration(
    context: Context,
    sharedPreferencesName: String,
    keysToMigrate: Set<String>? = MIGRATE_ALL_KEYS,
    deleteEmptyPreferences: Boolean = true
)

We can see that there are more options available, we can use them based on our use-cases.

This way migrating from SharedPreferences to DataStore Preferences is very easy.

I will update this blog when I find and learn more about this topic.

Video for this blog: Check here.

That's it for now.

Happy Learning :)

Amit Shekhar

Co-Founder, MindOrks

Show your love by sharing this blog with your fellow developers.