- Hello DataStore, Bye SharedPreferences👋 — Android📱 — Part 1: Preference DataStore
- In this article series, we’ll learn how to use the latest Android Jetpack 🚀 library i.e. DataStore in Android apps.
- What is DataStore 🤷♀️?
- Why DataStore 🤷♂️
- # That’s not only the reason —
- Let’s begin code 👨💻
- Start implementing DataStore 📁
- Setup Activity
- The new way of storing data in Android — Jetpack DataStore
- SharedPreferences
- Implementation
- Preferences DataStore
- Why should you use Preferences DataStore instead of SharedPreferences?
- Implementation
- Migration from SharedPreferences
- SharedPreferences vs DataStore
- Why use Room?
- Implementation
- When should you use DataStore instead of Room?
- Proto Datastore
- Key features:
- Implementation
- Language Guide (proto3) | Protocol Buffers | Google Developers
- This guide describes how to use the protocol buffer language to structure your protocol buffer data, including .proto…
- Using DataStore With Kotlin Serialization
- Up till now we’ve shared how to use DataStore with Protos or Preferences. Under the hood both DataStore versions use…
- Default value
- Room vs DataStore
- Wrapping up
Hello DataStore, Bye SharedPreferences👋 — Android📱 — Part 1: Preference DataStore
In this article series, we’ll learn how to use the latest Android Jetpack 🚀 library i.e. DataStore in Android apps.
Welcome Android developers 👋. This article is the first part of a series article based on the new Jetpack library🚀 i.e. DataStore🗄️. Currently, it’s alpha version is released. Let’s see what’s DataStore and why DataStore.
What is DataStore 🤷♀️?
- Jetpack DataStore is a data storage solution.
- It allows us to store key-value pairs (like SharedPreferences ) or typed objects with protocol buffers (We’ll see it in next article).
- DataStore uses Kotlin, Coroutines and Flow to store data asynchronously with consistency and transaction support 😍.
- In short, it’s the new data storage solution which is the replacement of SharedPreferences .
Why DataStore 🤷♂️
- First and my favourite reason 😃 — Built with❤️ Kotlin, Coroutines and Flow.
- If you have used SharedPreferences you might abuse or blamed it for something 😆 then DataStore is here to rescue!
- SharedPreference has some drawbacks like it provided synchronous APIs -but it’s not MAIN-thread-safe! whereas DataStore is safe to use in UI thread because it uses Dispatchers.IO under the hood👀.
- It’s safe from runtime exceptions!❌⚠️. What would be more satisfying that? 😅
- It also provides a way to migrate from SharedPreferences 😍.
- It provides Type safety! (Using Protocol buffers).
These are some reasons which encourage us to use DataStore and finally say goodbye to beloved SharedPreferences 👋.
# That’s not only the reason —
DataStore provides two different types of implementations to store data.
- Preference 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 (We’ll see about it in the next article).
I think that’s enough introduction to DataStore . It’s time to write some code👨💻😎.
Let’s begin code 👨💻
You can simply clone or refer this repository to get example code demonstrating DataStore 📁.
We’ll develop a sample Android application which stores a UI mode preference from user i.e. 🌞 Light Mode or 🌑 Dark Mode.
First of all, let’s add a Gradle dependency in build.gradle of your app module. Currently 1.0.0-alpha01 is the latest release. You can keep an eye here to get info about the latest version.
Start implementing DataStore 📁
- For UI mode preference i.e. Dark mode or Light mode, we’ll create an enum class as below
- We’ll create a class — SettingsManager where we’ll be managing setting preferences set by users in our app.
This will initialize the instance dataStore field by creating DataStore using the file name as “settings_pref”. createDataStore() is extension function created on Context .
- Now we’ll be storing UI mode preference using a key (as we managed in SharedPreference ). Key in DataStore is created as below 👇
Here 👆 we’ve created a 🔑 KEY IS_DARK_MODE which will store a boolean value ( false for Light mode / true for Dark mode). Because Preferences DataStore does not use a predefined schema, you must use Preferences.preferencesKey() to define a key for each value that you need to store in the DataStore
- Now we’ll create a method which will set UI mode from our UI/Activity i.e. setUiMode() 🔧
Note: Preferences DataStore provides a method edit() which transactional updates value in DataStore .
- Now it’s time to get preference 🔥. DataStore provides data property which exposes the preference values using Flow . Means it’s time to leverage Flow 🌊 😍. See the code below 👇
👆 You can see we’ve exposed a Flow uiModeFlow which will emit values whenever preferences are edited/updated. If you remember, we have been storing boolean in our DataStore . Using map<> , we’re mapping boolean values to the UiMode i.e UiMode.LIGHT or UiMode.DARK .
Note: DataStore throws IOException when it failed to read a value. So we have handled it by emitting emptyPreferences() .
So that’s all about setting up DataStore 😃. Now let’s design UI.
Setup Activity
In this activity, We have just an ImageButton which will have image resources i.e. 🌞 and 🌘 based on UI mode.
- In initViews() we’ll update preferences (UI Mode) on click of ImageButton .
- In observeUiPreferences() , we’ll observe DataStore UI Mode preference using a field which we exposed in SettingsManager which is a Flow 🌊 which will emit values whenever preferences are updated.
👆 Here we’ve used asLiveData() flow extension function which gives emitted values from Flow in LiveData . (Otherwise, we can also use lifecycleScope.launch<> here if you don’t like to use LiveData ).
- We’re just updating image resource and background color of root layout when UI mode is changed. (Actual mode can be changed using AppCompatDelegate. setDefaultNightMode() )
Yeah! That’s it 😃. It’s time to run this app 🚀. When you run this app, you’ll see it like 👇
Looking Awesome! 😍, isn’t it?
This is how we implemented Preferences DataStore instead of SharedPreferences .
So, DataStore is cool 🆒, isn’t it? Give it a try 😃. Since it’s currently in alpha, maybe many more is on the way to come 🛣️.
DataStore uses file management mechanism for storing data. But it’s different than managed in SharedPreferences . Now if you want to see how’s your data getting stored then using Android Studio’s ‘Device File Explorer’ you can go to /data/app/YOUR_APP_PACKAGE_NAME/files/datastore and you can see the file there like below 👇.
But it’s content is not readable as you can see in below image 👇
Источник
The new way of storing data in Android — Jetpack DataStore
In mobile applications some data has to be persisted to make the application startup faster, reduce network traffic or handle data completely offline. In this article, I would like to demonstrate the opportunities of how to store data in your Android application, especially with Jetpack DataStore. To introduce this I am going to give a walkthrough about the four mainly used approaches. The solutions that will be covered in the article are:
- SharedPreferences
- Preferences DataStore
- Room
- Proto DataStore
Besides the summary of current storage solutions, I will focus on the differences between SharedPrefrences, Room and DataStore. Regarding the DataStore, I added the implementation steps for both Preferences DataStore, and Proto DataStore.
SharedPreferences
If you have to store key-value pairs in your app, e.g.: user’s settings, IDs, the simplest way to implement this solution is the SharedPreferences API. A SharedPreferences object points to a file containing key-value pairs and provides simple methods to read and write them. Each SharedPreferences file is managed by the framework and can be private or shared across applications.
Implementation
Add required gradle dependency:
Saving user information to SharedPreferences:
Retrieving user information from SharedPreferences:
In the case of my application, which demonstrates a simple user data saving with the usage of Kotlin coroutines, this solution is not easy to use with Flow.
Unfortunately, SharedPreferences runs its read/write operations, except apply() function, on the main thread. Due to this disadvantage, and the lack of Kotlin Flow, and LiveData support, I would like to recommend a more sophisticated option, the Preferences DataStore.
Preferences DataStore
Preferences DataStore aims to replace SharedPreferences. The concept behind Preference DataStore is quite similar to SharedPreferences. It uses key-value pairs to store data and also does not provide type safeness. If you’re currently using SharedPreferences to store data, consider migrating to DataStore instead.
Why should you use Preferences DataStore instead of SharedPreferences?
- DataStore is safe to call on the UI thread because it uses Dispatchers.IO under the hood
- You do not have to use apply() or commit() functions to save the changes
- Handles data updates transactionally
- Exposes a Flow representing the current state of data
Implementation
1. Need to add this line into app-level build.gradle file’s dependencies
2. Create the DataStore instance
To create a Preferences DataStore, we can use the preferencesDataStore delegate. The delegate will ensure that we have a single instance of DataStore with that name in our application.
3. Create keys for the key part of the key-value pairs ( preferencesKeys )
4. To save data into DataStore
The edit() function is a suspend function, so it needs to be called from CoroutineContext .
Inside the lambda we have access to MutablePreferences , so we can change the value under the specified key.
Migration from SharedPreferences
With the usage of SharedPreferencesMigration we have the opportunity to migrate the “old fashioned” SharedPrefrences data to DataStore. Related/suggested article in this topic: Working with Preferences DataStore — Codelabs step by step guide — Step 7
SharedPreferences vs DataStore
- It is built on Kotlin Coroutines and Flow. It exposes the preference values using Flow.
- You don’t need to manually switch to a background thread
- DataStore is safe from runtime exceptions and has error handling support. SharedPreferences throws parsing errors as runtime exceptions.
- In both implementations, DataStore saves the preferences in a file and performs all data operations on Dispatchers.IO unless specified otherwise.
In my opinion, the following table is the best way to highlight the differences between the two key-value pair based storage approaches, and the Proto DataStore:
Room is designed to store and handle non-trivial amounts of structured data locally. Under the hood Room is a persistence library, which provides an abstraction layer over SQLite. The three major components of the library are:
The following diagram illustrates the connection between these major components:
Why use Room?
- Part of Jetpack
- Verifying SQL queries at compile time
- It can be integrated with LiveData , RxJava , and Coroutine Flow easily
- Reduces the amount of boilerplate code
Implementation
Add required Gradle dependencies:
Create the DAO with the Room SQL queries:
Room row insertion:
When should you use DataStore instead of Room?
If you use Room to save only one user’s data, you need to create a database with a table, and also need to implement the query and insert functions. It sounds really inconvenient, so in this case, the Proto DataStore can be the corresponding approach.
Proto Datastore
The aim of Proto DataStore is really similar to Preferences DataStore, but the previous one is able to store objects with custom data types. Unlike the Preferences DataStore it does not use key-value pairs, just returns the generated object in a Flow . The generated file’s type and structure depend on the schema of the .protoc file.
Key features:
- Provides type safety out of the box
- Able to define list inside protoc schema with repeated marked fields
- Requires to learn a new serialization mechanism, which depends on Protobuf, but it is worth the effort
- Protobuf is faster than XML and JSON serialization formats
Implementation
1. Add the following gradle dependencies to your app-level build.gradle file
If you need to use only Proto DataStore with typed objects you do not need to add the preferences version of the datastore dependency (androidx.datastore:datastore-preferences:1.0.0-beta01) .
2. Add protobuf to plugins in build.gradle
3. Add protobuf configuration to build.gradle
4. Add your proto file to project
In my case, I need to define the schema, which contains the user’s first name, last name, and birthday. So I need to add two string fields, and one 64-bit integer field ( int64 ). Birthday is stored in Long format, and that was the reason for using a 64-bit integer field.
You need to place your .proto file under app/src/main/proto folder. I gave user_preference.proto name to my file.
If you want to be familiar with the syntax of the protocol buffers, you should check this documentation:
Language Guide (proto3) | Protocol Buffers | Google Developers
This guide describes how to use the protocol buffer language to structure your protocol buffer data, including .proto…
5. Create the DataStore Serializer
This serializer class tells DataStore how to read and write your data type. Kotlin Serialization supports multiple formats including JSON and Protocol buffers.
As I mentioned before you have an option to use JSON instead of Protocol buffers. In my opinion, JSON is more readable and understandable, but the most common usage of DataStore depends on protocol buffers. Related, and recommended article, if you want to start a JSON based DataStore:
Using DataStore With Kotlin Serialization
Up till now we’ve shared how to use DataStore with Protos or Preferences. Under the hood both DataStore versions use…
6. Create the DataStore
7. To save data into DataStore
Default value
First of all, if you want to read an empty DataStore, which contains only standard field types (e.g.: string, int32, enum ), it will return the protobuf’s generated object with pre-initialized default values. In this case, your integers will be zero, and the strings will be empty strings.
If you import google/protobuf/wrappers.proto into your protobuf file, which depends on proto3 syntax you will be able to add fields with nullable types, e.g.: google.protobuf.StringValue first_name = 1;
Room vs DataStore
If you have a need for partial updates, referential integrity, or large/complex datasets, you should consider using Room instead of DataStore. DataStore is ideal for small or simple datasets and does not support partial updates or referential integrity.
Suppose that your data structure is not large/complex, and need to store only one/few rows in a table, e.g: one user’s data. In this scenario, the usage of the Room can be a little bit of overkill. So if you have to store only a small bunch of data in your database, you have only one or few tables, and you don’t want to use the benefits of Room, you should choose DataStore.
The most crucial disadvantage of DataStore against Room is that, it does not support partial updates: if any field is modified, the whole object will be serialized and persisted to disk. If you want partial updates, consider the Room API (SQLite).
Wrapping up
In summary, DataStore is ideal to use with Kotlin, and especially with coroutines. Don’t worry if you already have an application, which contains only the standard SharedPreferences solution. The migration is provided by the DataStore API.
Because of the fact that DataStore handles data migration, guarantees data consistency, and handles data corruption, it is worth changing from SharedPreferences to DataStore.
The examples of the article were implemented originally in my POC application, which aims to demonstrate the implementation of these four ways of storage. If you are interested in the deeper technical/coding part of the topic, feel free to check the application on Github:
Источник