DataStore Android -How to use it like a pro using Kotlin

Vaibhav Goyal
5 min readAug 10, 2021

--

In this article, we are going to look at Preferences DataStore and How to use it like a pro.

What is DataStore( Preferences ) :

DataStore is a data storage solution that allows you to store key-value pairs.It uses Kotlin coroutines and Flow to store data asynchronously, consistently, and transactionally.

Why:

Why we actually need DataStore when we can just use Shared Preferences. So, the reason behind this is Shared Preferences stores data synchronously which means on UI Thread which can lead to ANR dialogs in Application but DataStore uses Coroutines and stores the data on IO Dispatchers which makes them better than Shared Preferences. But this is not the only reason why we should not use Shared Preferences anymore, there are alot of reasons behind that which you can read here.

What you will learn in this article -

  1. How to read and write to DataStore using key/ value pairs.
  2. Dependency Injection using Dagger- Hilt.
  3. How to use MVVM Pattern.
  4. How to make your code as loosely coupled as possible.
  5. Make your code more testable.

Let’s start:

First of all we have to add some dependencies, so navigate to project level build.gradle file and add the following line in dependencies section.

classpath "com.google.dagger:hilt-android-gradle-plugin:2.37"

Now navigate to the module level build.gradle file and add these lines in plugins section.

id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'

And lastly add these lines to the dependencies section.

//DataStore
implementation "androidx.datastore:datastore-preferences:1.0.0"

// Lifecycle
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"

// Activity KTX for viewModels()
implementation "androidx.activity:activity-ktx:1.3.1"

//Dagger - Hilt
implementation 'com.google.dagger:hilt-android:2.37'
kapt 'com.google.dagger:hilt-compiler:2.37'

implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03'

Click Sync Now and wait for the Build to finish.

After Build finished, let’s start coding,

Our Code structure will look like below,

Start with creating a new Kotlin class called BaseApplication or you can choose whatever name and annotate this class with @HiltAndroidApp and extend it from Application class like below.

Now specify the name of your application class, in my case it’s BaseApplication in the Manifest file, like

Now let’s start with data package, where the main code lies. First create our DataStoreRepository interface. Add the following code like,

We have created interface here because it will help in Unit Testing our app as during Unit Testing we don’t want to save the data in DataStore in actual and also by doing this we are creating loosely coupled classes. .Now we have to implement the interface so let’s create the DataStoreRepositoryImpl class where all the things related to saving and retrieving the data from DataStore will happen.

Now we have to create one Extension for datastore of type DataStore<Preferences>, and we will lazily instanstiate it, it only then will instanstiate when we need it for the first time and if we don’t need it, it will not be initialise which is good for Performance. Add this line at top of your DataStoreRepositoryImpl class and remember to make it private because we don’t want outsiders to know or to do anything with this variable.

private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = PREFERENCES_NAME)

Now we know that we need context to access this variable let’s specify it in constructor of our DataStoreRepositoryImpl class and annotate this constructor with @Inject annotation from Dagger- Hilt like

class DataStoreRepositoryImpl @Inject constructor(
private val context: Context
) : DataStoreRepository {

Basically what it will do is to tell the Dagger- Hilt that I need Context in my constructor, please inject it for us and Dagger- Hilt will under the hood inject the context of the Application class which we had annotated with @HiltAndroidApp at top. That way, we don’t have to pass the context every time we want to use this class, thanks to Dagger- Hilt. We will see how to use this class just in a moment.

Now, let’s complete our methods,

What we have done here is we first created the preferenceKey variable. In Shared Preferences we could save the value simply by key of type String but in DataStore we have different type of key of type Preferences.Key<T> which we can use to save and retrieve values. And what we have done here is simply created Preferences.Key<String>(key) key, we could’ve used Preferences.Key<String> in method parameters but it will make our code more resistable to change which we don’t want, suppose in future we can just use simple key of type String to save and retrieve data then we have to change our code where we have used these methods and have to replace all those Preferences.Key<String> with simple String. So to avoid this , we have created like this.

Then we used already available extension function of DataStore<Preferences>.edit which takes MutablePreferences as a method parameter to edit our datastore file and save the value.It’s same with any other types just we have to create preference key for that specific type.

Now let’s complete our functions to retrieve the data-

We have created preferenceKey same as we have done before. But to retrieve we used extension function data which returns the Coroutine Flow and then we used first() method to get the first value in the Flow as there can only be one value and this returned us Preferences object.

Now we can use our preferenceKey to retrieve the value from preferences with this key. As it can throw IO Exception if there is not value found with this key we placed it in try- catch block.

So that’s all about our DataStoreRepositoryImpl class.

Now let’s just create our ViewModel, update your DataViewModel Class like this,

Here we just call the repository and save and retrieved the value. Remember to annotate this class with @HiltViewModel.

Now let’s see how to Inject our Repository Interface in constructor as it cannot be directly Injected because Hilt don’t know anything about this class. For this we have to create one Singleton class called AppModule in di package.

Like this,

Next actually Implement our UI Logic and create activity_main.xml file like ,

Now update your MainActivity like,

Remember to annotate this class with @AndoidEntryPoint otherwise App will crash, now it’s time to run the app have fun.

Thank you for reading this far, the article was long but it have covered a lot of Topics which will help you become better developer and you will write clean and better code.Get the full code here.

If you like the Article, consider following for more this kind of stuff regularly.

--

--

Vaibhav Goyal

Passionate Freelance Full Stack Android developer with primary language Kotlin. Have 4 years of programming experience.