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
See you in the next story..
Top comments (0)