“This is the 11th day of my participation in the First Challenge 2022. For details: First Challenge 2022”
1. DataStore Introduction
DataStore, a component of Android Jetpack, is a data storage solution that uses key-value storage like SharedPreferences. DataStore guarantees atomicity, consistency, isolation, and persistence. In particular, it addresses a design flaw in the SharedPreferences API. Jetpack DataStore is a new and improved data storage solution designed to replace SharedPreferences and enable applications to store data asynchronously and transactionally.
Note: DataStore is suitable for small data and simple operations, and data cannot be locally updated. If you need to support large or complex datasets, partial updates, or referential integrity, consider using Room instead of DataStore.
Preferences DataStore and Proto DataStore
- Preferences DataStore: Similar to SharedPreferences, data is stored through key-value pairs. This implementation does not require a predefined schema and does not provide type safety.
- Proto DataStore: Use protocol-buffers to define the storage data type and structure to ensure type security.
This article focuses on the Preferences DataStore.
Second, the Preferences DataStore
Like SharedPreferences, which stores data through key-value pairs, this implementation does not require a predefined schema and does not provide type safety.
2.1 Adding a Dependency
Add the following dependencies to build. Gradle for your project’s app_module:
dependencies {
//Typed DataStore (Typed API surface, such as Proto)
implementation "Androidx. Datastore: datastore: 1.0.0."
// // Optional - RxJava2 support
/ / implementation "androidx datastore: datastore - rxjava2:1.0.0"
// // Optional - RxJava3 support
implementation "Androidx. Datastore: datastore - rxjava3:1.0.0"
//Preferences DataStore (SharedPreferences like APIs)
implementation "Androidx. Datastore: datastore - preferences: 1.0.0"
// // Optional - RxJava2 support
/ / implementation "androidx datastore: datastore - preferences - rxjava2:1.0.0"
// // Optional - RxJava3 support
implementation "Androidx. Datastore: datastore - preferences - rxjava3:1.0.0"
}
Copy the code
2.2 Using the Preferences DataStore to store key-value pairs
Take a look at the source code for DataStore. DataStore is an interface.
package androidx.datastore.core
import kotlinx.coroutines.flow.Flow
import java.io.IOException
/** * DataStore DataStore provides a secure, persistent way to store small amounts of data, such as Preferences and application state. * Data storage provides ACID assurance. It is thread-safe and does not block. In particular, it addresses these design flaws of the SharedReferences API: * 1. Synchronous API encourages StrictMode violations * 2. apply() and commit() have no mechanism of signalling errors * 3. Apply () will block the UI thread on fsync() * 4. Durable - it can return state that is Not yet persisted * 5. No consistency or transactional semantics * 6. Throws runtime exception on parsing errors * 7. Exposes mutable references to its internal state */
public interface DataStore<T> {
public val data: Flow<T>
public suspend fun updateData(transform: suspend (t: T) -> T): T
}
Copy the code
As you can see, DataStore is implemented based on coroutines and flows.
- Data is a Flow object.
- UpdateData () is used to update objects.
And if you look at DataStore’s other source code, you’ll see that it’s all based on the Kotlin language. Google is pretty dedicated to pushing Kotlin.
2.2.1 create the DataStore
- Create it using preferencesDataStore(Kotlin)
Datastore<Preferences>
The instance. - If you use RxJava, RxPreferenceDataStoreBuilder to use.
The required name parameter is the name of the Preferences DataStore.
Here we use RxJava:
RxDataStore<Preferences> dataStore =
new RxPreferenceDataStoreBuilder(this./*name=*/ "datastore_sc").build();
// Create a key to use
Preferences.Key<String> nameKey = PreferencesKeys.stringKey("name");
Preferences.Key<Integer> ageKey = PreferencesKeys.intKey("age");
Copy the code
The default path: / data/data/com. SCC. Datastorage/files/datastore/datastore_sc preferences_pb
Here we create two keys of type String and Int.
2.2.1 DataStore Writing Data
// See the code above for nameKey and ageKey.
putString(nameKey, "name-Scc");
putInteger(ageKey, 25);
//2022/1/25 Function: Stores data of String type
private void putString(Preferences.Key<String> nameKey, String value) {
dataStore.updateDataAsync(new Function<Preferences, Single<Preferences>>() {
@Override
public Single<Preferences> apply(Preferences preferences) throws Throwable {
MutablePreferences mutablePreferences = preferences.toMutablePreferences();
mutablePreferences.set(nameKey, value);
returnSingle.just(mutablePreferences); }}); }//2022/1/25 Function: Stores data of type Integer
private void putInteger(Preferences.Key<Integer> ageKey, Integer value) {
dataStore.updateDataAsync(new Function<Preferences, Single<Preferences>>() {
@Override
public Single<Preferences> apply(Preferences preferences) throws Throwable {
MutablePreferences mutablePreferences = preferences.toMutablePreferences();
mutablePreferences.set(ageKey, value);
returnSingle.just(mutablePreferences); }}); }Copy the code
The two types are written here, so that it is easier to copy the understanding and you write your own utility classes, because the use of DataStore is less practice does not provide utility classes, so as not to mislead you.
2.2.3 DataStore Reading Data
getString(nameKey);
getInteger(ageKey);
//2022/1/25 Function: Obtain String data
private void getString(Preferences.Key<String> nameKey) {
Log.e("DataStore", nameKey.toString());
Flowable<String> example = dataStore.data().map(new Function<Preferences, String>() {
@Override
public String apply(Preferences preferences) {
Log.e("DataStore.apply", preferences.get(nameKey));
returnpreferences.get(nameKey); }}); Log.e("DataStore.Flowable", example.first("default").blockingGet());
}
//2022/1/25 Function: Obtain data of the Integer type
private void getInteger(Preferences.Key<Integer> ageKey) {
Log.e("DataStoreKey", ageKey.toString());
Flowable<Integer> example = dataStore.data().map(new Function<Preferences, Integer>() {
@Override
public Integer apply(Preferences preferences) {
Log.e("DataStore.apply", preferences.get(ageKey).intValue() + "");
returnpreferences.get(ageKey); }}); Log.e("DataStore.Flowable", example.first(12).blockingGet().toString());
}
Copy the code
Third, Proto DataStore
Use protocol-buffers to define the storage data types and structures to ensure type security.
The Proto DataStore implementation uses DataStore and protocol-buffers to persist typed objects to disk.
What is protocol-buffers?
Protocol-buffers is Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data — such as XML — but smaller, faster, and simpler. You define how your data is structured once, and then you can easily write and read structured data between various data streams and in various languages using specially generated source code.
3.1 Defining the Architecture
Proto DataStore requires the predefined schema in the Proto file in the app/ SRC /main/ Proto/directory. This schema defines the types of objects that you persist in the Proto DataStore.
syntax = "proto3";
option java_multiple_files = true;
option java_package = "com.scc.datastorage.proto";
option java_outer_classname = "User";
message User{
string name = 1;
int32 age = 2;
}
Copy the code
.proto files start with a package declaration, which helps prevent naming conflicts between different projects.
- Java_multiple_files: You can generate a separate.java file for each generated class.
- Java_package: Specifies what Java package name the generated class should use. If you don’t specify it explicitly, it will only match the package name given in the package declaration.
- Java_outer_classname: Defines the classname. If this option is not set, it will be generated by converting the file name to capital camel. For example, by default, “my_proto.proto” will use “MyProto” as the wrapper class name.
To define the message:
-
A number of standard simple data types can be used as field types, including bool, INT32, float, double, and String.
-
You can also add further structure to your message by using other Message types as field types.
The “= 1”, “= 2” tags on each element identify the unique “tag” used in binary encoding for that field.
Find out more about the Protobuf language guide on the official website.
Note: The class that stores the object is generated at compile time from message defined in the proto file. Make sure you rebuild your project.
3.2 Creating a Proto DataStore
- 1. Define an implementation
Serializer<T>
Class, where T is the type defined in the proto file. - 2. Use the attribute delegate created by the dataStore
DataStore<T>
Where T is the type defined in the proto file.
The class defined in the proto file cannot be generated. I have tried many things, but I don’t know what happened. Follow the steps provided in the Preferences DataStore website. I won’t take up space.
4. Related links
Official document DataStore;
Android data storage (2)-SP VS DataStore VS MMKV;
In my opinion, DataStore is still not as good as SP and MMKV. I recommend using MMKV after all, it has been online for several years, and it is introduced by a large factory, so it is relatively stable. Some individuals have realized the function of SP by themselves, but it will be embarrassing in case of bugs or sudden discontinuation of maintenance.