Android Jetpack Preferences DataStore Example

This blog is about Android Jetpack DataStore Preferences and how to implement DataStore Preferences in our Android application.

DataStore

  • Jetpack DataStore is a data storage solution.
  • It allows us to store key-value pairs (like SharedPreferences) or typed objects with protocol buffers (discuss in the next article).
  • DataStore uses Kotlin, Coroutines and Flow to store data asynchronously with consistency and transaction support.
  • DataStore is the new data storage solution which is the replacement of SharedPreferences.

Types of DataStore

DataStore provides two different types of implementations to store data.

  • Preferences DataStore — This uses key-value pairs to store data. But it doesn’t provide type-safety.
  • Proto DataStore — It stores data as a custom type with specified schema using Protocol Buffers (discuss in the next article).

Creating new project

  1. Create a new project by going to File  New Android Project, select Empty Activity , provide app name, select language to kotlin and then finally click on finish.
  2. Open project’s app level build.gradle file:
  • Go to the android block and add the below lines.
compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}

kotlinOptions {
    jvmTarget = '1.8'
}
  • Now add the below dependencies inside dependencies block. Once you have added all these, sync your project.
dependencies { 
    // Preferences DataStore
    implementation "androidx.datastore:datastore-preferences:1.0.0-alpha07"

    // Lifecycle component
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"

    // Kotlin coroutines components
    api "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
    api "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"

}

3 . Open your activity_main.xml. To create the following UI, add the below code.

activity_main.xml

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

    <Button
        android:id="@+id/btn_save"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/margin_60dp"
        android:background="@drawable/background"
        android:padding="@dimen/padding_18dp"
        android:text="Save user"
        android:textColor="@android:color/white"
        android:textSize="@dimen/textsize_15sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="@+id/switch_gender"
        app:layout_constraintStart_toStartOf="@+id/switch_gender"
        app:layout_constraintTop_toBottomOf="@+id/switch_gender" />

    <EditText
        android:id="@+id/et_lname"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/margin_16dp"
        android:ems="10"
        android:hint="@string/hint_enter_last_name"
        app:layout_constraintEnd_toEndOf="@+id/et_fname"
        app:layout_constraintStart_toStartOf="@+id/et_fname"
        app:layout_constraintTop_toBottomOf="@+id/et_fname" />

    <EditText
        android:id="@+id/et_age"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/margin_16dp"
        android:ems="10"
        android:hint="@string/hint_enter_age"
        android:inputType="number"
        app:layout_constraintEnd_toEndOf="@+id/et_lname"
        app:layout_constraintStart_toStartOf="@+id/et_lname"
        app:layout_constraintTop_toBottomOf="@+id/et_lname"
        tools:layout_editor_absoluteY="317dp" />

    <EditText
        android:id="@+id/et_fname"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/margin_30dp"
        android:ems="10"
        android:hint="@string/hint_enter_first_name"
        app:layout_constraintEnd_toEndOf="@+id/tv_gender"
        app:layout_constraintStart_toStartOf="@+id/tv_gender"
        app:layout_constraintTop_toBottomOf="@+id/tv_gender"
        tools:layout_editor_absoluteY="178dp" />

    <TextView
        android:id="@+id/tv_fname"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/margin_30dp"
        android:textStyle="bold"
        android:textColor="@color/colorPrimaryDark"
        android:textSize="@dimen/textsize_20sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_lname"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/margin_16dp"
        android:textStyle="bold"
        android:textColor="@color/colorPrimaryDark"
        android:textSize="@dimen/textsize_20sp"
        app:layout_constraintEnd_toEndOf="@+id/tv_fname"
        app:layout_constraintStart_toStartOf="@+id/tv_fname"
        app:layout_constraintTop_toBottomOf="@+id/tv_fname" />

    <TextView
        android:id="@+id/tv_age"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/margin_16dp"
        android:textStyle="bold"
        android:textColor="@color/colorPrimaryDark"
        android:textSize="@dimen/textsize_20sp"
        app:layout_constraintEnd_toEndOf="@+id/tv_lname"
        app:layout_constraintStart_toStartOf="@+id/tv_lname"
        app:layout_constraintTop_toBottomOf="@+id/tv_lname" />

    <TextView
        android:id="@+id/tv_gender"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/margin_16dp"
        android:textStyle="bold"
        android:textColor="@color/colorPrimaryDark"
        android:textSize="@dimen/textsize_20sp"
        app:layout_constraintEnd_toEndOf="@+id/tv_age"
        app:layout_constraintStart_toStartOf="@+id/tv_age"
        app:layout_constraintTop_toBottomOf="@+id/tv_age" />

    <androidx.appcompat.widget.SwitchCompat
        android:id="@+id/switch_gender"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/margin_16dp"
        android:text="@string/female_male"
        android:textSize="@dimen/textsize_20sp"
        app:layout_constraintEnd_toEndOf="@+id/et_age"
        app:layout_constraintStart_toStartOf="@+id/et_age"
        app:layout_constraintTop_toBottomOf="@+id/et_age" />

</androidx.constraintlayout.widget.ConstraintLayout>

Creating a Preferences DataStore

4. Create a new kotlin file User.kt and add the below code. 

User.kt

package com.c1ctech.jetpackdatastore

import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore

// At the top level of your kotlin file
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "user_prefs")
  • dataStore: instance of Datastore<Preferences> created by preferencesDataStore.
  • name parameter(mandatory) : is the name of the Preferences DataStore.

5 .Create a new kotlin class UserManager.kt and add the below code.

UserManager.kt

package com.c1ctech.jetpackdatastore

import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

class UserManager(val dataStore: DataStore<Preferences>) {

    //Create some keys
    companion object {
        val USER_AGE_KEY = intPreferencesKey("USER_AGE")
        val USER_FIRST_NAME_KEY = stringPreferencesKey("USER_FIRST_NAME")
        val USER_LAST_NAME_KEY = stringPreferencesKey("USER_LAST_NAME")
        val USER_GENDER_KEY = booleanPreferencesKey("USER_GENDER")
    }

    //Store user data
    suspend fun storeUser(age: Int, fname: String, lname: String, isMale: Boolean) {
        dataStore.edit {
            it[USER_AGE_KEY] = age
            it[USER_FIRST_NAME_KEY] = fname
            it[USER_LAST_NAME_KEY] = lname
            it[USER_GENDER_KEY] = isMale

        }
    }

    //Create an age flow
    val userAgeFlow: Flow<Int?> = dataStore.data.map {
        it[USER_AGE_KEY]
    }

    //Create a fname flow
    val userFirstNameFlow: Flow<String?> = dataStore.data.map {
        it[USER_FIRST_NAME_KEY]
    }

    //Create a lname flow
    val userLastNameFlow: Flow<String?> = dataStore.data.map {
        it[USER_LAST_NAME_KEY]
    }

    //Create a gender flow
    val userGenderFlow: Flow<Boolean?> = dataStore.data.map {
        it[USER_GENDER_KEY]
    }

}

Defining Key for the Value that needs to be saved:

  • To define a key for each value that you need to store in the DataStore<Preferences> instance, you must use the corresponding key type function. For example:
val USER_AGE_KEY = intPreferencesKey("USER_AGE")
val USER_FIRST_NAME_KEY = stringPreferencesKey("USER_FIRST_NAME")
val USER_LAST_NAME_KEY = stringPreferencesKey("USER_LAST_NAME")
val USER_GENDER_KEY = booleanPreferencesKey("USER_GENDER")

Saving Value to Preference DataStore:

  • Preferences DataStore provides an edit () function using which we can save our values to keys.

Reading Value back from Preference DataStore:

  • To read the value from a Preferences DataStore we need to use the DataStore.data property which returns a Flow<Preferences> and with the .map{} operator we can can get the Flow<> (value) by passing the right key into the preferences.

6 . Open MainActivity.kt and add the below code.

MainActivity.kt

package com.c1ctech.jetpackdatastore

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.lifecycle.asLiveData
import androidx.lifecycle.observe
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch

class MainActivity : AppCompatActivity() {
    lateinit var userManager: UserManager
    var age = 0
    var fname = ""
    var lname = ""
    var gender = ""

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

        //Get reference to our userManager class
        userManager = UserManager(dataStore)

        buttonSave()

        observeData()
    }

    private fun observeData() {

        //Updates age
        userManager.userAgeFlow.asLiveData().observe(this, {
            if (it != null) {
                age = it
                tv_age.text = it.toString()
            }
        })

        //Updates firstname
        userManager.userFirstNameFlow.asLiveData().observe(this, {
            if (it != null) {
                fname = it
                tv_fname.text = it
            }
        })

        //Updates lastname
        userManager.userLastNameFlow.asLiveData().observe(this, {
            if (it != null) {
                lname = it
                tv_lname.text = it
            }
        })

        //Updates gender
        userManager.userGenderFlow.asLiveData().observe(this, {
            if (it != null) {
                gender = if (it) "Male" else "Female"
                tv_gender.text = gender
            }
        })
    }

    private fun buttonSave() {

        //Gets the user input and saves it
        btn_save.setOnClickListener {
            fname = et_fname.text.toString()
            lname = et_lname.text.toString()
            age = et_age.text.toString().toInt()
            val isMale = switch_gender.isChecked

            //Stores the values
            GlobalScope.launch {
                userManager.storeUser(age, fname, lname, isMale)
            }
        }
    }
}
  • In order to get the saved values in the activity from the DataStore, we need to change the Flow<> to LiveData with .asLiveData() and observe it.

Leave a Reply