The new DataStore is designed to replace the use of SharePreferences. The DataStore can be implemented in two ways: The Preferences DataStore and the Proto DataStore. The following article focuses on the use of the second type of Proto DataStore. The first Jetpack Preferences DataStore uses the Github project address

One: Add dependencies

Add the following dependencies to your app’s build.grale

implementation('androidx. Datastore: datastore: 1.0.0 - alpha04') {
  exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-coroutines-core'
  exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-coroutines-core-jvm'
}
implementation('androidx. Datastore: datastore - core: 1.0.0 - alpha04') {
     exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-coroutines-core-jvm'
}
Copy the code

Because the transactions because will be submitted to Duplicate class kotlinx. Coroutines. AbstractCoroutine found in modules Jetlinx-coroutines-core-jvm-1.3.9.jar so exclude group: ‘org.jetBrains. Kotlinx ‘, module: Will exclude group:’ org.jetBrains. ‘kotlinx-coroutines-core’ exclude group: ‘org.jetbrains.kotlinx’, module: ‘kotlinx-coroutines-core-jvm’

Two: download the plug-in and add the configuration required by Proto

To support.proto files, you need to download the plugin first, and gradle must be above 5.6, it must be java8. Configure plug-in reference address 1 Configure plug-in reference address 2

1: in the project build. Add the classpath gradle ‘com. Google. Protobuf: protobuf – gradle – plugin: 0.8.14’, and add the warehouse mavenCentral ()

buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath 'com. Google. Protobuf: protobuf - gradle - plugin: 0.8.14'}}Copy the code

2: Add apply plugin: ‘com.google.protobuf’ to app build.gradle

apply plugin: 'com.google.protobuf'
Copy the code

3: Add proto file location in app build.gradle

// Set the proto file location
android{
    sourceSets {
        main {
            proto {
                // The proto file's default path is SRC /main/proto
                // You can change the location of the proto file by using srcDir
                srcDir 'src/main/proto'}}}}Copy the code

4: Add the following code to build.gradle of app using the configuration method after 3.8.0

/** * Note the configuration of protoc commands, divided into different versions, different configuration mode, * most of the network is 3.0.x to 3.7.x configuration mode, about this configuration method, Can view [protobuf - gradle - plugins] (https://github.com/google/protobuf-gradle-plugin) document, not demonstrated here, It is also not recommended to use the configuration that is used after 3.8 in this project, */
protobuf {
    // Set the protoc version
    protoc {
        / / / / downloaded from warehouse protoc. Here the version number of the need to rely on com Google. Protobuf: protobuf - javalite: XXX version are the same
        artifact = 'com. Google. Protobuf: protoc: 3.10.0'
    }
    generateProtoTasks {
        all().each { task ->
            task.builtins {
                java {
                    option "lite"}}}}/ / the default generated $buildDir/generated/source/proto change generated by generatedFilesBaseDir position
   // generatedFilesBaseDir = "$projectDir/src/main"
}

dependencies {
  // You need to depend on the lite runtime library, not protobuf-java
  implementation 'com. Google. Protobuf: protobuf - javalite: 3.10.0'
}
Copy the code

3. Define a protocol and create a. Proto protocol file in app/ SRC /main/proto/

Create a new proto folder under app/ SRC /main/. Create a persons.proto file as follows:

syntax = "proto3";

option java_package = "com.example.jepcaktestapp.datastore.proto";
option java_outer_classname = "PersonProtos";

message Person {
    // Format: field type + field name + field number
    int32 count = 1;
}
Copy the code

The following is a brief explanation of the contents of the file, followed by a brief introduction to the Proto3 syntax:

  • Syntax declares the version of proto, as here syntax = “proto3”;
  • Option JavA_Package defines the package name of the generated class
  • Option JAVA_outer_className Defines the classname of the generated class
  • Message declares an inner class. The format of message is field type + field name + field number
  • String -> string, int32->int, int64->long, bool->Boolean, float->float,double->double

Four: compile

During the Rebuild Project, every time the proto file changes, Click Build -> Rebuild Project again to generate the corresponding Java file. This Java file (the proto file we defined above), The file is generated by default directory app/build/generated/source/proto/package name/class name Can change this file generated by generatedFilesBaseDir position

Five:

1: create

// Specify a name
private val PROTO_NAME = "proto_datastore.pb"
var dataStore: DataStore<PersonProtos.Person> = context.createDataStore(
    fileName = PROTO_NAME,
    serializer = PersonSerializer
)

object PersonSerializer :Serializer<PersonProtos.Person>{

    override val defaultValue: PersonProtos.Person
        get() = PersonProtos.Person.getDefaultInstance()

    override fun readFrom(input: InputStream): PersonProtos.Person {
        try {
            return PersonProtos.Person.parseFrom(input)
        }catch (e:IOException){
            throw CorruptionException("Cannot read proto.", e)
        }
    }

    override fun writeTo(t: PersonProtos.Person, output: OutputStream) =  t.writeTo(output)
}
Copy the code

2: the store

// Take the count attribute
suspend fun putCount(count:Int) {
 dataStore.updateData { it.toBuilder().setCount(count).build() }
}
Copy the code

3: get

// Take the count attribute
suspend fun getCount(a):Int =  dataStore.data.map { it.count }.first()
Copy the code

4: Critical code

class ProtoDataStoreImpl(val context: Application){

    // Specify a name
    private val PROTO_NAME = "proto_datastore.pb"

    var dataStore: DataStore<PersonProtos.Person> = context.createDataStore(
        fileName = PROTO_NAME,
        serializer = PersonSerializer
    )

    private val shardPrefsMigration = SharedPreferencesMigration<PersonProtos.Person>(context, SpUtils.SHARE_PREFERENCES_NAME) {
            sharedPreferencesView, person ->
            // Get the SharedPreferences data
            val count = sharedPreferencesView.getInt(SpKeys.KEY_COUNT,0)
            // Store to dataStore
            person.toBuilder().setCount(count).setName(name).setFlag(flag).setPrice(price).setTime(time).build()
        }

    suspend fun putCount(count:Int) {
        dataStore.updateData { it.toBuilder().setCount(count).build() }
    }
    suspend fun getCount(a):Int =  dataStore.data.map { it.count }.first()

    fun spToDataStore(a){
        dataStore = context.createDataStore(
            fileName = PROTO_NAME,
            serializer = PersonSerializer,
            migrations = listOf(shardPrefsMigration)
        )
    }
}

object StoreFactory {
    fun provideProtoDataStore(context: Application):ProtoDataStoreImpl{
        return ProtoDataStoreImpl(context)
    }
}

object PersonSerializer :Serializer<PersonProtos.Person>{

    override val defaultValue: PersonProtos.Person
        get() = PersonProtos.Person.getDefaultInstance()

    override fun readFrom(input: InputStream): PersonProtos.Person {
        try {
            return PersonProtos.Person.parseFrom(input)
        }catch (e:IOException){
            throw CorruptionException("Cannot read proto.", e)
        }
    }

    override fun writeTo(t: PersonProtos.Person, output: OutputStream) =  t.writeTo(output)
}


class MainActivity : AppCompatActivity() ,View.OnClickListener ,CoroutineScope by MainScope(){

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        tv_proto_data_store.setOnClickListener(this)}override fun onClick(v: View?). {
        when(v){
            tv_preferences_data_store->{
              launch (Dispatchers.Main){
                  / / set
                  StoreFactory.providePreferencesDataStore(application).putInt(PreferencesKeys.KEY_COUNT,1)
                  / / to get
                  val count = StoreFactory.providePreferencesDataStore(application).getInt(PreferencesKeys.KEY_COUNT)
              }
            }
        }        
    }
    override fun onDestroy(a) {
        cancel()
        super.onDestroy()
    }
}
Copy the code

5: indicates the location where the proto datastore is generated

The proto datastore file is stored in the files datastore folder. The file name is proto_datastore.pb defined by us

6. Switch from SP to Proto DataStore

It is mainly to obtain the value of Sp. In the one-to-one corresponding setting to Proto, the key code is as follows:

private val shardPrefsMigration = SharedPreferencesMigration<PersonProtos.Person>(context, SpUtils.SHARE_PREFERENCES_NAME) {
            sharedPreferencesView, person ->
            // Get the SharedPreferences data
            val count = sharedPreferencesView.getInt(SpKeys.KEY_COUNT,0)
            person.toBuilder().setCount(count).build()
        }
        
var dataStore: DataStore<PersonProtos.Person> = context.createDataStore(
            fileName = PROTO_NAME,
            serializer = PersonSerializer,
            migrations = listOf(shardPrefsMigration)
) 
Copy the code

What is the difference between Pro DataStore and Preferences DataStore?

  • Preference DataStore is primarily designed to address performance issues associated with SharedPreferences
  • Proto DataStore is more flexible and supports more types than Preference DataStore
  • Preference DataStore supports Int, Long, Boolean, Float, String, Double, and Set
  • Protocol Buffers are of the types supported by Proto DataStore
  • Preference DataStore stores key-value data as XML, which is very readable
  • Proto DataStore uses binary encoding compression, which is smaller and faster than XML

Introduction to Proto3 syntax

For example, define a file as follows:

syntax = "proto3";

option java_package = "com.example.jepcaktestapp.datastore.proto";
option java_outer_classname = "PersonProtos";

message Teacher{
   string grade = 1;
}

message Person {
    // Format: field type + field name + field number
    string name = 1;
    int32 count = 2;
    bool flag = 3;
    float price = 4;
    int64 time = 5;
    double money = 6;
    Teacher teacher = 7;
    enum Weekday{
        SUN = 0;
        MON = 1;
        TUE = 2;
        WED = 3;
        THU = 4;
        FRI = 5;
        SAT = 6;
    }
    Weekday weekday = 8;
    repeated string course = 9;
}
Copy the code
  • Syntax: Specifies the version of a protobuf, if not specified proto2 is used by default
  • Option: Indicates an optional field
    • Java_package is the name of the package that specifies where the Java classes will be generated
    • Java_outer_classname: Specifies the name of the generated Java class
  • You can define multiple messages in A proto file and if you have messageA in messageB, that means that B is an inner class of A
  • Field type + field name + field number, e.g. String name = 1; String is the type, name is the name, and 1 on the right of the equals sign indicates the number

The field type

proto type java type The default value
double double 0
float float 0
int32 int 0
int64 long 0
bool boolean false
string String “”
repeated List Empty collection

Other message types can also be used as types, as shown in the following example:

message Person {
    // Format: field type + field name + field number
    Teacher teacher =1;
}

message Teacher{
    ......
}
Copy the code

A proto file can have multiple messages, and messages can be nested. Such as:

message Person {
    // Format: field type + field name + field number
    message Teacher{
    	string name=1;
	}
    Teacher teacher = 1;
}
Copy the code

These messages are compiled into static inner classes. Enumerations can also be used under message, and the first value of the enumeration must be 0. (for compatibility with Proto2 syntax)

message Person {
 enum Weekday{
        SUN = 0;
        MON = 1;
        TUE = 2;
        WED = 3;
        THU = 4;
        FRI = 5;
        SAT = 6;
  }
  Weekday weekday = 1;
}
Copy the code