Skip to content
DeveloperMemos

Migrating from SharedPreferences to DataStore

Android Development, DataStore, SharedPreferences, Migration1 min read

DataStore is a modern data storage solution provided by Jetpack. It replaces SharedPreferences and offers several advantages, including type safety, asynchronous operations using Kotlin Coroutines and Flow, and better error handling. There are two types of DataStore: Preferences DataStore (for simple key-value pairs) and Proto DataStore (for more complex data structures).

In this article, we'll focus on Preferences DataStore.

Prerequisites

Before we proceed, make sure you have the following prerequisites:

  1. Android Studio (preferably Arctic Fox or later).
  2. Familiarity with Architecture Components like LiveData, ViewModel, and View Binding.
  3. Basic knowledge of coroutines and Kotlin Flow.

Migration Steps

  1. Add Dependencies: First, add the following dependency to your build.gradle file:

    1implementation "androidx.datastore:datastore-preferences:1.1.0"

    Note that you might need to set the Kotlin target version to 1.8 in your kotlinOptions as well.

  2. Create DataStore Instance: Initialize your DataStore instance by specifying the name (similar to how you used to create SharedPreferences):

    1private val dataStore: DataStore<Preferences> by preferencesDataStore(name = "file_name")
  3. Migrate Key-Value Pairs: Identify the keys you want to migrate from SharedPreferences. Specify these keys in the keysToMigrate parameter when creating a SharedPreferencesMigration. For example:

    1private val dataStore: DataStore<Preferences> by preferencesDataStore(
    2 name = "file_name",
    3 produceMigrations = { context ->
    4 listOf(
    5 SharedPreferencesMigration(
    6 context = context,
    7 sharedPreferencesName = "shared_prefs_name",
    8 keysToMigrate = setOf("example_key")
    9 )
    10 )
    11 }
    12)
  4. Reading Data from DataStore: Unlike SharedPreferences, reading data from DataStore is asynchronous using Kotlin Flow. Handle exceptions (such as IOException) appropriately:

    1val userPreferencesFlow: Flow<String> = dataStore.data
    2 .catch { exception ->
    3 if (exception is IOException) {
    4 emit(emptyPreferences())
    5 } else {
    6 throw exception
    7 }
    8 }
    9 .map { preferences ->
    10 preferences[PreferencesKeys.EXAMPLE_KEY] ?: "defaultValue"
    11 }
  5. Avoid Blocking the UI Thread: Be cautious when using runBlocking for synchronous operations. It can lead to ANRs (Application Not Responding) on the UI thread. Instead, use suspend functions and handle data retrieval asynchronously.

    1suspend fun getExampleKey(): String = withContext(ioDispatcher) {
    2 dataStore.data.map { preferences ->
    3 preferences[EXAMPLE_KEY] ?: "defaultValue"
    4 }.first()
    5}

In Closing

By migrating to DataStore, you'll enjoy better performance, type safety, and improved error handling. Remember that DataStore is not suitable for large-scale data storage or partial updates. For those scenarios, consider using Room.