Jetpack is continuously trying to make our lives (Android Dev’s Lives) easier. It is coming up with new improved libraries that are helping us to write better android applications.
What? Are you kidding me? 🤨
Ok let’s think in the other way that “Glass is half empty”. Jetpack is continuously making our lives HELL, by continuously releasing new things. And we as Android Devs needs to keep Learning and Learning and Learning.
You can be on either side, I don’t have an issue with it.
Recently Jetpack Team came up with a new library named Jetpack DataStore; that is aimed to replace our existing SharedPreferences. I guess you all know “What is SharedPreferences?“.
Jetpack DataStore is built on Kotlin Coroutines and Flow. It is a new and improved data storage solution for your Android Apps.
In this Jetpack DataStore Tutorial, I will explain you, how to use Jetpack DataStore in your Android Application Project.
Table of Contents
Types of DataStore
We have two implementations of DataStore.
- Preference DataStore, for storing simple Key-Value pairs (as we usually store in SharedPreferences).
- Proto DataStore, for storing custom data types. For this we need to define schema and we also get TypeSafety here.
Jetpack DataStore Tutorial
Enough of the talkings right? Now we will actually get our hands into Jetpack DataStore by implementing it in a sample android studio project. Excited? 😉
Creating a new Android Studio Project
As always, create a new android project to experiment these new things. I have created a simple project with EmptyActivity.
In your newly created project, first you need to do some changes.
- Go to the android block, inside app level build.gradle file. Here you need to define the following things.
1 2 3 4 5 6 7 8 9 10 |
compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = JavaVersion.VERSION_1_8.toString() } |
- Now add the required dependencies inside dependencies block.
1 2 3 4 5 6 7 8 9 10 |
//DataStore implementation "androidx.datastore:datastore-preferences:1.0.0-alpha01" implementation "androidx.datastore:datastore-core:1.0.0-alpha01" //Coroutines and LifeCycle Libraries implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9" implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.0-alpha07" implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0" |
- Once you have added all these, sync your project.
Preference DataStore
We will start with the first type that is Preference DataStore. In Preference DataStore we can save values by keys.
For this example I will save a String.
- Create a class named UserPreferences in your project.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
class UserPreferences( context: Context ) { private val applicationContext = context.applicationContext private val dataStore: DataStore<Preferences> init { dataStore = applicationContext.createDataStore( name = "app_preferences" ) } val bookmark: Flow<String?> get() = dataStore.data.map { preferences -> preferences[KEY_BOOKMARK] } suspend fun saveBookmark(bookmark: String) { dataStore.edit { preferences -> preferences[KEY_BOOKMARK] = bookmark } } companion object { val KEY_BOOKMARK = preferencesKey<String>("key_bookmark") } } |
We need to follow these steps to use Preference DataStore.
#1 Creating a DataStore instance
- Create a DataStore instance using the function createDataStore() . This function takes the preference name (that is a String) as a parameter.
#2 Defining Key for the Value that needs to be saved
- We can do it with preferencesKey<T>(name: String) function.
#3 Saving Value to Preference DataStore
- We can call the .edit( transform: suspend (MutablePreferences) -> Unit) function; in the trailing lambda we will get the MutablePreferences instance (as you can see in the code snippet above). We can use this MutablePreferences instance to directly assign any value to it using the key that we defined in step #2.
#4 Reading Value back from Preference DataStore
- To read the value back we need to call data function from our DataStore instance. It returns a flow that contains the value.
Using the UsersPreferences to Save Bookmark
- Open your activity_main.xml and create the following UI.
- The UI is pretty simple. Now we will use the ButtonClick event to save the entered Bookmark.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class MainActivity : AppCompatActivity() { private lateinit var userPreferences: UserPreferences override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) userPreferences = UserPreferences(this) buttonSaveBookmark.setOnClickListener { val bookmark = editTextBookmark.text.toString().trim() lifecycleScope.launch { userPreferences.saveBookmark(bookmark) } } userPreferences.bookmark.asLiveData().observe(this, Observer { textViewCurrentBookmark.text = it }) } } |
- After adding the above code to your activity, you can run your application.
As you can see our value is getting stored and we can even observe the changes of our value very easily.
Proto DataStore
If you want to store Typed objects, you can use Proto DataStore. Let’s see how we can use it.
Adding Protocol Buffers to our Project
First step is we need to add ProtoBuf to our project.
- Open project level build.gradle file and add the following classpath.
1 2 3 |
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.12' |
- Now inside app level build.gradle add the following.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
apply plugin: 'com.google.protobuf' protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.6.1' } plugins { javalite { artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0' } } generateProtoTasks { all().each { task -> task.builtins { remove java } task.plugins { javalite {} } } } } sourceSets { main.java.srcDirs += "${protobuf.generatedFilesBaseDir}/main/javalite" main.java.srcDirs += "$projectDir/src/main/proto" } processResources { exclude('**/*.proto') } |
- First we have applied the protobuf plugin.
- Then we have defined the protobuf artifact.
- Then we added protobuf-lite as the code generation plugin.
- And then we also need to add protobuf-lite in the dependencies as well.
1 2 3 4 |
//protobuf implementation 'com.google.protobuf:protobuf-lite:3.0.1' |
- Also make sure you have added it inside android block of the app level build.gradle file.
1 2 3 4 5 6 |
compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } |
Defining a Schema
To use Proto DataStore first we need to define a schema with protofile.
- We will create a .proto file at app/src/main/proto location. proto directory is not available by default, so you need to create it.
- Now let’s define the schema.
1 2 3 4 5 6 7 8 9 10 |
syntax = "proto3"; option java_package = "com.example.jetpackdatastore"; option java_multiple_files = true; message Bookmark { string bookmark = 1; } |
- To learn more about proto3 syntax, you can check this link. Now let’s move ahead in our project.
I will create a separate class (because we are working on the same project where we learned preferences data store).
- Create a class named BookmarkDataStore and write the following code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
class BookmarkDataStore( context: Context ) { private val applicationContext = context.applicationContext private val dataStore: DataStore<Bookmark> init { dataStore = applicationContext.createDataStore( fileName = "bookmark.pb", serializer = BookmarkSerializer ) } val bookmark = dataStore.data .map { bookmarkSchema -> bookmarkSchema.bookmark } suspend fun saveBookmark(bookmark: String) { dataStore.updateData { currentBookmark -> currentBookmark.toBuilder() .setBookmark(bookmark) .build() } } object BookmarkSerializer : Serializer<Bookmark> { override fun readFrom(input: InputStream): Bookmark { try { return Bookmark.parseFrom(input) } catch (exception: InvalidProtocolBufferException) { throw CorruptionException("Cannot read proto.", exception) } } override fun writeTo( t: Bookmark, output: OutputStream ) = t.writeTo(output) } } |
Steps to use Proto DataStore
#1 Creating a Schema
- We already created a proto schema.
#2 Create a class (or Object as I did in the above code), that inherits Serializer<T>
- Inside this class we need to override, readFrom() and writeTo() function as you can see in the above code.
#3 Saving to Proto DataStore
- For saving call the function updateData() and inside trailing lambda we will get the currently saved value and with the help of it we can save new value.
#4 Reading from Proto DataStore
- Reading is same, again we are calling the data function, that is returning a flow.
Using Proto Data Store
Here is the activity code that is using Proto Data Store.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class MainActivity : AppCompatActivity() { private lateinit var bookmarkDataStore: BookmarkDataStore override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) bookmarkDataStore = BookmarkDataStore(this) buttonSaveBookmark.setOnClickListener { val bookmark = editTextBookmark.text.toString().trim() lifecycleScope.launch { bookmarkDataStore.saveBookmark(bookmark) } } bookmarkDataStore.bookmark.asLiveData().observe(this, Observer { textViewCurrentBookmark.text = it }) } } |
After adding it to your project you can run the application. And you will get the exact same output.
Jetpack DataStore Tutorial Source Code
If you want my source code, you can get it from here.
Jetpack DataStore Tutorial Source Code
That’s it for this post friends, please SHARE it with your friends. In case you have any problem or question related to this Jetpack DataStore Tutorial, feel free to ask it in the comment section below. Thank You ♥️