Jetpack DataStore and How to implement it.
Tags: android, kotlin, jetpack, datastore
Published: 2020-09-13 08:53:54

**Jetpack DataStore** is a new improved library and built on Coroutines and Flow which aims to replace the SharedPreferences. So What are the advantages over SharedPreferences;  Data is stored asynchronously, consistently, and transactionally.

If we look a little deeper into our topic;

* DataStore provides asynchronous API(via Flow) for storing and reading data but SharedPreferences provides async API only for reading data.

* DataStore is safe to call on the UI thread because it uses Dispatchers.IO, but SharedPreferences blocks the UI thread.  apply() and commit() have no mechanism of signaling errors and apply() will block the UI thread on fsync() often becoming a source of ANRs.

* DataStore is safe from runtime exceptions, but SharedPreferences throws parsing errors in runtime exceptions. 

And also DataStore has two different implementations:
**Preferences DataStore** and **Proto DataStore**

**Preferences DataStore** – stores and accesses data via key-value pairs like SharedPreferenecs, but this implementation does not provide type safety.

**Proto DataStore** – stores data as custom objects and requires to define a schema using protocol buffers. Also, it provides type safety.

Note: Today we're just looking at the implementation of the Preferences DataStore.

![Screenshot_2020-09-12 Prefer Storing Data with Jetpack DataStore](https://dev-to-uploads.s3.amazonaws.com/i/ybtubzgsb522eocyu3le.png)
 
First of all, we need to add some dependencies;


```kotlin
    // Preferences DataStore
    implementation "androidx.datastore:datastore-preferences:1.0.0-alpha01" 

    // Lifecycle components
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
    implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
    implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" 
```

Ok, Let's suppose we store a user's informations which are name and surname. Her is our UI;

![nameandsurname](https://dev-to-uploads.s3.amazonaws.com/i/5f5myb7mozui1zpbkzah.png)
 
Next, we're gonna create a named UserInfo class which takes a context in the  constructor.

```kotlin
class UserInfo(context: Context) { 
```

below, we create a dataStore and we need to give it a name.

```kotlin
class UserInfo(context: Context) {

    val dataStore = context.createDataStore("user_info")
    
}
```

Then we should create some keys, so to do this we're gonna create a companion object.

we create two keys which are named **USER_NAME_KEY && USER_SURNAME_KEY**, they're gonna be equal to **preferencesKey** of type String and then we need to give them the actual name **USER_NAME && USER_SURNAME**.

```kotlin
 class UserInfo(context: Context) {

    val dataStore = context.createDataStore("user_info")

    companion object {
        val USER_NAME_KEY = preferencesKey<String>("USER_NAME")
        val USER_SURNAME_KEY = preferencesKey<String>("USER_SURNAME")
    }
} 
```

Next, we create a suspend function to save our data and it takes two arguments. We call **dataStore.edit** inside here we store values.

```kotlin
  suspend fun saveUserInfo(name: String, surname: String ) {
        dataStore.edit {
            it[USER_NAME_KEY] = name
            it[USER_SURNAME_KEY] = surname
        }
    }
```
So, that will take care of storing data to the key we have assigned it.

Now we create two flow which will help us to retrieve data. And we map them to our keys also we check if they're null we return an empty String ("")

```kotlin
val userNameFlow: Flow<String> = dataStore.data.map {
        it[USER_NAME_KEY] ?: ""
    }

val userSurnameFlow: Flow<String> = dataStore.data.map {
        it[USER_SURNAME_KEY] ?: ""
    } 
```
Okay until now we're done with UserInfo. Now we're going to MainActivity.

First, we create a **lateinit var** for UserInfo and 2 variables for name and surname.

```kotlin
 lateinit var userInfo: UserInfo

 var name = ""
 var surname = ""
```

And we're going to **onCreate** method to instantiate **userInfo** and write two methods that retrieve and save data.


```kotlin
 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        userInfo = UserInfo(this)

        saveData()

        observeData()
    }
```

After this one, we're going to **saveData()**
and inside here, as you know we created a suspend function in UserInfo class. So, this has to be done in CoroutineScope. We're just gonna call **GlobalScope.launch** and inside this block code we call saveUserInfo() method.

```kotlin
 private fun saveData() {
        buttonSave.setOnClickListener {
            name = etName.text.toString()
            surname = etSurname.text.toString()

            GlobalScope.launch {
                userInfo.saveUserInfo(name, surname)
            }
        }
    }
```

After saving data we're going to observe our LiveDatas and retrieve them to write to our TextViews.

```kotlin
private fun observeData() {
        userInfo.userNameFlow.asLiveData().observe(this, {
            name = it
            tvName.text = "Saved name: $it"
        })

        userInfo.userSurnameFlow.asLiveData().observe(this, {
            surname = it
            tvSurname.text = "Saved surname: $it"
        })
    }
```

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/i5a0rs3re90iygjzvkv3.jpg)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/q2kkaxccrgqwbg4cyv90.jpg)

* In additionally, as I mentioned before, **DataStore is safe from runtime exceptions** it reads data from a file, IOExceptions are thrown when an error occurs while reading data so, we can handle these exceptions before map()


```kotlin
val userNameFlow: Flow<String> = dataStore.data
        .catch { exception ->
            if (exception is IOException) {
                Log.d("DataStore", exception.message.toString())
                emit(emptyPreferences())
            } else {
                throw exception
            }
        }.map {
            it[USER_NAME_KEY] ?: ""
        }

```

Yeah, that's all.