Jetpack DataStore Preferences

Authors
  • Amit Shekhar
    Name
    Amit Shekhar
    Published on
Jetpack DataStore Preferences

I am Amit Shekhar, I have taught and mentored many developers, and their efforts landed them high-paying tech jobs, helped many tech companies in solving their unique problems, and created many open-source libraries being used by top companies. I am passionate about sharing knowledge through open-source, blogs, and videos.

Join my program and get high paying tech job: amitshekhar.me

Before we start, I would like to mention that, I have released a video playlist to help you crack the Android Interview: Check out Android Interview Questions and Answers.

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.

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 = "example-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 "example-prefs" is the name of SharedPreferences, we will have to do like below:

val dataStore: DataStore<Preferences> =
    context.createDataStore(
        name = "example-data-store-prefs",
        migrations = listOf(SharedPreferencesMigration(context, "example-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.

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

Prepare yourself for Android Interview: Android Interview Questions

That's it for now.

Thanks

Amit Shekhar

You can connect with me on:

Read all of my high-quality blogs here.