Welcome Preferences DataStore 🚀
Tags: android, datastore, kotlin, sharedpreferences
Published: 2020-10-04 08:58:44

Hello developers, in this article I am going to cover a new member of Jetpack Library 🚀 called **DataStore**.

We are working on shared preferences for a long time to store some of the data locally in our android apps and we know how shared preferences proved to be very useful to support our use cases.

To make our lives easier Android has introduced DataStore to implement Shared Preferences in our apps in a more efficient way.

DataStore provides two types of implementation,i.e; **Preferences DataStore** and **Proto DataStore**. Today we are looking at Preferences DataStore.

> So, why should we use Preferences DataStore? 😕
* It is an Async API that can be used via Flow
* It is safe to call on UI thread since work is moved to Dispatchers.IO under the hood
* It can signal errors
* It is safe from runtime exceptions
* It has a transactional API with strong consistency
* It handles data migration from Shared Preferences

This dude has some Advantages, right! 😃

Now, let’s directly jump into the code 💻

> For this example I have used Dagger-Hilt to provide dependency Injection.

Let’s update our build.gradle files like this
```
...
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'

android {
   
   ...
   
    // Compile options
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_1_8.toString()
    }

}

dependencies {
    ....

    // 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"

    // Kotlin coroutines components
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72"
    api "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
    api "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"

    //Dagger - Hilt
    implementation "com.google.dagger:hilt-android:2.28-alpha"
    kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"

}
```

```
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
   
   ....
   
    dependencies {
        classpath "com.android.tools.build:gradle:4.0.1"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "com.google.dagger:hilt-android-gradle-plugin:2.28-alpha"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}
```

Now, create an Application class DataStore as follows

```
@HiltAndroidApp
class DataStore: Application() {

    override fun onCreate() {
        super.onCreate()
    }

}
```
Next, we will create a **DataManager** class where all our logic will go. For this example I have stored user’s name, GitHub username and favorite number

```
class DataManager(context: Context) {

    private val dataStore = context.createDataStore("data_prefs")

    companion object{
        val USER_NAME = preferencesKey<String>("USER_NAME")
        val USER_GITHUB = preferencesKey<String>("USER_GITHUB")
        val USER_NO = preferencesKey<Int>("USER_NO")
    }

    suspend fun storeData(name:String, github:String, no:Int){
        dataStore.edit {
            it[USER_NAME] = name
            it[USER_GITHUB] = github
            it[USER_NO] = no
        }
    }

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

    val userGithubFlow:Flow<String> = dataStore.data.map {
        it[USER_GITHUB] ?: ""
    }

    val userNo: Flow<Int> = dataStore.data.map {
        it[USER_NO] ?: -1
    }

}
```

Here the **storeData** function is made suspend because to make it asynchronous and we love *coroutines*💛

Next, we will create our *AppModule* which will provide this DataManager

```
@Module
@InstallIn(ApplicationComponent::class)
object AppModule {

    @Singleton
    @Provides
    fun provideDataManager(@ApplicationContext context: Context) = DataManager(context)

}
```

This will provide a **Singleton** of **DataManager** to our application

It’s time to create a layout for our main activity as follows:

```
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:background="#0E0E0E">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="100dp"
        android:fontFamily="@font/poppins_medium"
        android:text="Data Store Example"
        android:textColor="#fff"
        android:textSize="20sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:id="@+id/tvTitle"/>

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@+id/tvTitle"
        android:backgroundTint="@color/colorPrimary"
        android:textColor="#fff"
        android:hint="Enter name"
        android:textColorHint="#8C8C8C"
        android:fontFamily="@font/poppins_medium"
        android:layout_marginTop="50dp"
        android:layout_marginStart="30dp"
        android:layout_marginEnd="30dp"
        android:id="@+id/etName"/>

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@+id/etName"
        android:backgroundTint="@color/colorPrimary"
        android:textColor="#fff"
        android:hint="Github username"
        android:textColorHint="#8C8C8C"
        android:fontFamily="@font/poppins_medium"
        android:layout_marginTop="20dp"
        android:layout_marginStart="30dp"
        android:layout_marginEnd="30dp"
        android:id="@+id/etgithub"/>

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@+id/etgithub"
        android:backgroundTint="@color/colorPrimary"
        android:textColor="#fff"
        android:hint="Enter favourite Number"
        android:textColorHint="#8C8C8C"
        android:fontFamily="@font/poppins_medium"
        android:layout_marginTop="20dp"
        android:layout_marginStart="30dp"
        android:layout_marginEnd="30dp"
        android:id="@+id/etNumber"/>

    <com.google.android.material.button.MaterialButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@+id/etNumber"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginTop="20dp"
        android:backgroundTint="@color/colorPrimary"
        app:cornerRadius="25dp"
        android:text="SAVE"
        android:paddingStart="20dp"
        android:paddingEnd="20dp"
        android:textColor="#fff"
        android:id="@+id/btnSave"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@+id/btnSave"
        app:layout_constraintStart_toStartOf="parent"
        android:text="No data saved to display!"
        android:textColor="#fff"
        android:textSize="16sp"
        android:inputType="number"
        android:id="@+id/tvName"
        android:visibility="gone"
        android:fontFamily="@font/poppins_medium"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginTop="30dp"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@+id/tvName"
        app:layout_constraintStart_toStartOf="parent"
        android:text="No data saved to display!"
        android:textColor="#fff"
        android:textSize="16sp"
        android:visibility="gone"
        android:id="@+id/tvGithub"
        android:fontFamily="@font/poppins_medium"
        app:layout_constraintEnd_toEndOf="parent"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@+id/tvGithub"
        app:layout_constraintStart_toStartOf="parent"
        android:text="No data saved to display!"
        android:textColor="#fff"
        android:textSize="16sp"
        android:id="@+id/tvNo"
        android:visibility="gone"
        android:fontFamily="@font/poppins_medium"
        app:layout_constraintEnd_toEndOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>
```

Finally here comes our MainActivity 😌

```
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var dataManager: DataManager
    private var name:String = ""
    private var github:String = ""
    private var no:Int = 0

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

        subscribeToObservers()

        btnSave.setOnClickListener {
            val name:String = etName.text.toString()
            val github:String = etgithub.text.toString()
            val no:Int = etNumber.text.toString().toInt()

            if (name.isNotEmpty() && github.isNotEmpty() && no!=-1){
                GlobalScope.launch {
                    dataManager.storeData(name,github,no)
                }
            }else{
                Toast.makeText(this, "Enter details carefully!", Toast.LENGTH_SHORT).show()
            }

        }

    }

    private fun subscribeToObservers() {

        dataManager.userNameFlow.asLiveData().observe(this) {
            name = it
            if (name.isNotEmpty()){
                tvName.visibility = View.VISIBLE
                tvName.text = "Name: $name"
            }
        }

        dataManager.userGithubFlow.asLiveData().observe(this) {
            github = it
            if (github.isNotEmpty()){
                tvGithub.visibility = View.VISIBLE
                tvGithub.text = "Github: $github"
            }
        }

        dataManager.userNo.asLiveData().observe(this) {
            no = it
            if (no != -1){
                tvNo.visibility = View.VISIBLE
                tvNo.text = "Number: $no"
            }
        }

    }
}
```

Here we have observed on our Preferences Keys as LiveData and made them visible.

Everything is pretty smooth and straight forward in this implementation 😌

For complete code reference checkout this [repository](https://github.com/raystatic/Preference_store_demo/)

See you in the next story..