1. Introduction to the DataStore

Jetpack DataStore is a data storage solution that allows you to store key-value pairs or typed objects using protocol buffers. DataStore uses Kotlin coroutines and processes to store data in an asynchronous, consistent transactional manner.

2. Why use DataStore, SharedPreferences shortcomings

SharedPreferences, stored locally as key-value pairs, is familiar to every Android developer and is very simple to use. But there are also some problems that we often encounter in projects:

GetXXX () will block the main thread. When you call get on the main thread, you must wait for SP to finish loading, which will block the main thread. 2. The putXXX method can override the same key with different types of data when using the same key. 3. Sp-loaded data remains in memory, and data loaded with the getSharedPreferences() method is eventually stored in static member variables. 4. The apply() method is asynchronous and ANR may occur. The apply() method is asynchronous and will not have any problems. However, when the life cycle is in handleStopService(), handlePauseActivity(), handleStopActivity(), it will wait for the apply() method to save the data successfully, otherwise it will wait. Thus blocking the main thread makes ANR 5.SP unavailable for cross-process communicationCopy the code

Compared to the SharedPreferences, DataStore advantages are the following

1. DataStore is implemented based on Flow, ensuring security in the main thread. Update data in transaction mode, transaction has four characteristics (atomicity, consistency, isolation, persistence) 3. There are no data persistence methods like apply() and commit(). Automatically migrate the SharedPreferences to the DataStore to ensure data consistency and prevent data damage. 5. You can listen to the success or failure of an operationCopy the code

Take another look at the difference between Google Analytics SharedPreferences and DataStore:

3. The use of the DataStore

Jetpack DataStore is implemented in two ways:

(2)Preferences DataStore: The parsed object is a parsed object. The parsed object is added to the parsed object. Stored locally as key-value pairs, similar to SharedPreferences

The Preferences DataStore supports only Int, Long, Boolean, Float, String key-value pairs. It is suitable for storing simple, small data and does not support local updates. The entire contents of the file will be reserialized,

Use the Preferences DataStore in your project
1. Need to add the Preferences DataStore dependency // Preferences DataStore implementation "Androidx. Datastore: datastore - preferences: 1.0.0 - alpha01" 2. Private val PREFERENCE_NAME = "DataStore" var DataStore: DataStore<Preferences> = createDataStore( name = PREFERENCE_NAME ) 3. Suspend fun saveDataLong(key: Preferences.Key<Long>, value: Long) { dataStore.edit { mutablePreferences -> mutablePreferences[key] = value } } fun getDataLong(key: Preferences.Key<Long>): Flow<Long? > = dataStore.data.catch { if (it is IOException) { it.printStackTrace() emit(emptyPreferences()) } else { throw it } }.map { it[key] ? : -1L } fun getDataLongSyn(key: Preferences.Key<Long>): Long { var value = -1L runBlocking { dataStore.data.first { value = it[key] ? : -1 true } } return value } 4. CreateDataStore (Name: String, corruptionHandler: ReplaceFileCorruptionHandler<Preferences>? = null, migrations: List<DataMigration<Preferences>> = listOf(), scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) ): DataStore<Preferences> = PreferenceDataStoreFactory.create( produceFile = { File(this.filesDir, "datastore/$name.preferences_pb") }, corruptionHandler = corruptionHandler, migrations = migrations, Scope = scope) {scope = scope; If the data store encounters a CorruptionException when attempting to read data, the corruptionHandler is called. When data cannot be deserialized, the serializer raises a CorruptionException. This parameter coroutines SharedPreferences the scope of the migration to the DataStore (1) into a SharedPreferencesMigration object (2) when the DataStore object after the build, you need to perform a read or write operation, The SharedPreferences file will be automatically deleted after the migration succeeds. Note that: XML file corresponding to SP will be deleted after the migration is successful. Sp.datastore = context.createdatastore (name = preferenceName, Migrations = listOf (SharedPreferencesMigration (the context, you store Name "SP")))Copy the code

4. About asynchronous storage values

So let’s do a demo

fun saveData() { GlobalScope.launch { saveDataInt(ketInt, 4) saveDataLong(keyLong, 5) saveDataString(keyString, "charles") saveDataBoolean(keyBoolean, Globalscope.launch {getDataInt(ketInt).collect {log.e ("Charles", "ketInt==$it") } getDataBoolean(keyBoolean).collect { Log.e("Charles", "keyBoolean==$it") } getDataLong(keyLong).collect { Log.e("Charles", "keyLong==$it") } getDataString(keyString).collect { Log.e("Charles", "ketString==$it") } }Copy the code

The normal logic would be to retrieve the values I saved, but now it prints:

The 2021-03-22 17:03:26. 461, 22076-22439 / com. Example. Myapplication E/Charles: ketInt = = 4Copy the code

This is because one of the main advantages of DataStore is its asynchronous API, but it may not always be possible to change the surrounding code to asynchronous code. This can happen if you use an existing code base that uses synchronous disk I/O, or if your dependencies do not provide an asynchronous API. Kotlin coroutines provide a runBlocking() coroutine builder to help eliminate the difference between synchronous and asynchronous code. You can use runBlocking() to synchronously read data from the DataStore.

Whenever runBlocking is added, the code in the block blocks the calling thread until execution ends. Obviously, the main thread will stall due to blocking for time-consuming operations, so use asynchronous storage or reads for time-consuming operations.

fun getDataLongSyn(key: Preferences.Key<Long>): Long { var value = -1L runBlocking { dataStore.data.first { value = it[key] ? : -1 true } } return value }Copy the code

DataStore is based on the Flow implementation, because the above storage is asynchronous, data Flow is also asynchronous! You can use first() if you want to get it all the time

The print is:

The 2021-03-22 17:29:49. 062, 25850-25850 / com. Example. Myapplication E/Charles: KetInt = = true 17:29:49. 2021-03-22, 062, 25850-25850 / com. Example. Myapplication E/Charles: KetInt = = 5 2021-03-22 17:29:49. 062, 25850-25850 / com. Example. Myapplication E/Charles: KetInt = = Charles 17:29:49. 2021-03-22, 064, 25850-26008 / com. Example. Myapplication E/Charles: ketInt = = 4Copy the code

Performing synchronous I/O operations on interface threads can cause ANR or interface lag. These problems can be reduced by asynchronously preloading data from the DataStore:

override fun onCreate(savedInstanceState: Bundle?) {
    lifecycleScope.launch {
        context.dataStore.data.first()
        // You should also handle IOExceptions here.
    }
}
Copy the code

For the use of the Flow can refer to website: (1) kotlin. Making. IO/kotlinx cor…

(2) cloud.tencent.com/developer/a…

Summary:

The Preferences DataStore is easy to use, but kotlin's coroutine and Flow are easy to use. And a Flow is very much like an Observable in rxJava.Copy the code