DEV Community

Cover image for Welcome Preferences DataStore 🚀
raystatic
raystatic

Posted on

Welcome Preferences DataStore 🚀

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"

}
Enter fullscreen mode Exit fullscreen mode
// 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
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, create an Application class DataStore as follows

@HiltAndroidApp
class DataStore: Application() {

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

}
Enter fullscreen mode Exit fullscreen mode

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
    }

}
Enter fullscreen mode Exit fullscreen mode

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)

}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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

    }
}
Enter fullscreen mode Exit fullscreen mode

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

See you in the next story..

Top comments (0)