Introducing DataStore
Tags: android, jetpack, datastore
Published: 2020-12-29 13:23:49

A new way to store data on Android.

##Overview
- A new and improved data storage solution aimed at replacing SharedPreferences.
- Kotlin coroutines and Flow.
- Data is stored asynchronously, consistently, and transactionally.

##SharePreferences vs DataStore
![Comparing SharedPreferences vs DataStore](https://dev-to-uploads.s3.amazonaws.com/i/whek7kf5ej8mqrib64bv.png)

##Features
It provides two different implementations:

- **Preferences DataStore:** stores primitive data key-value pairs.
- **Proto DataStore:** stores typed objects.

In both implementations, DataStore saves the preferences in a file and performs all data operations on `Dispatchers.IO` unless specified otherwise.

##Pre requirement
- Familiarity with coroutines and Kotlin Flow.

##Steps
####Setup
Gradle:
```gradle
// Preferences DataStore 
implementation “androidx.datastore:datastore-preferences:1.0.0-alpha05”
// Proto DataStore 
implementation “androidx.datastore:datastore-core:1.0.0-alpha05”
```

For Proto DataStore:
- Setup protobuf gradle plugin: https://github.com/google/protobuf-gradle-plugin
- Define your schema in a proto file in the `app/src/main/proto/` directory.

####Create the DataStore
Preference DataStore:
```kotlin
val dataStore: DataStore<Preferences> = context.createDataStore(name = "settings")
```

Proto DataStore:
```kotlin
object SettingsSerializer : Serializer<Settings> {
  override fun readFrom(input: InputStream): Settings {
    try {
      return Settings.parseFrom(input)
    } catch (exception: InvalidProtocolBufferException) {
      throw CorruptionException("Cannot read proto.", exception)
    }
  }

  override fun writeTo(
    t: Settings,
    output: OutputStream) = t.writeTo(output)
}

val settingsDataStore: DataStore<Settings> = context.createDataStore(
  fileName = "settings.pb",
  serializer = SettingsSerializer
)
```

####Read data
Preference DataStore:
```kotlin
val MY_COUNTER = preferencesKey<Int>("my_counter")
val myCounterFlow: Flow<Int> = dataStore.data
     .map { currentPreferences ->
        // Unlike Proto DataStore, there's no type safety here.
        currentPreferences[MY_COUNTER] ?: 0   
   }
```

Proto DataStore:
```kotlin
val myCounterFlow: Flow<Int> = settingsDataStore.data
    .map { settings ->
        // The myCounter property is generated for you from your proto schema!
        settings.myCounter 
    }
```

####Write data
Preference DataStore:
```kotlin
suspend fun incrementCounter() {
    dataStore.edit { settings ->
        // We can safely increment our counter without losing data due to races!
        val currentCounterValue = settings[MY_COUNTER] ?: 0
        settings[MY_COUNTER] = currentCounterValue + 1
    }
}
```

Proto DataStore:
```kotlin
suspend fun incrementCounter() {
    settingsDataStore.updateData { currentSettings ->
        // We can safely increment our counter without losing data due to races!
        currentSettings.toBuilder()
            .setMyCounter(currentSettings.myCounter + 1)
            .build()
    }
}
```

####Migrate from SharedPreferences
Preference DataStore:
```kotlin
val dataStore: DataStore<Preferences> = context.createDataStore(
    name = "settings",
    migrations = listOf(SharedPreferencesMigration(context, "settings_preferences"))
)
```

Proto DataStore:
```kotlin
val settingsDataStore: DataStore<Settings> = context.createDataStore(
    produceFile = { File(context.filesDir, "settings.preferences_pb") },
    serializer = SettingsSerializer,
    migrations = listOf(
        SharedPreferencesMigration(
            context,
            "settings_preferences"            
        ) { sharedPrefs: SharedPreferencesView, currentData: Settings ->
            // Map your sharedPrefs to your type here
          }
    )
)
```

##Conclusion
Jetpack DataStore is a replacement for SharedPreferences that address some problems such as a synchronous API that can appear to be safe to call on UI thread, no mechanism for signaling errors, lack of transactional and more. DataStore is fully asynchronous API using Kotlin Coroutines and Flow, consistent, handle data migration and data corruption.