Basic usage of DataStore

0. Introduction

Since the launch of components, Google JetPackhas always recommended that we use DataStorecomponents to replace the components we knew on the first day of learning android SharedPreferences. The reason is very simple, because SharedPreferencesmost of the problems that existed back then DataStorecame to solve these problems.

1. Disadvantages of SP

As SPfor what problems exist, we can directly check DataStorethe comments on the source code:

  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. Not durable – it can returns 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

To translate word for word in our broken English:

  1. Synchronous APIs encourage StrictMode violations
  2. The apply() and commit() methods have no error signaling mechanism
  3. The apply() method will block the UI thread when redrawing the interface
  4. Non-durable - it can return state, but not persist state
  5. no consistency or transaction semantics
  6. When there is an error in parsing, a runtime exception is thrown directly
  7. In its internal state, expose its mutable reference

Literal translations of questions written by foreigners are generally difficult to understand, unless the questions are very simple and clear. So let me say a few words, and roughly talk about the problems I think SPexist :

  1. Cross-process is not supported, and using MODE_MULTI_PROCESSthe pattern is useless. And in cross-process, frequent reading and writing may lead to data corruption or loss;
  2. Reading files in lazy loading mode SPmay cause getXXX() to block. So it is recommended to initialize asynchronously in advance SP;
  3. spAll the data in the file is stored in memory, so it is not suitable SPfor children with large amounts of data
  4. edit()method creates a new EditorImplobject every time. Suggest once edit(), many times putXXX;
  5. Whether it is commit()or apply(), it is written in full for any modification. In this case, the frequently modified configuration items are stored in separate SPfiles;
  6. commit()Synchronous save, with a return value; apply()asynchronous save, no return value.
  7. onPause() onReceive()The asynchronous write operation is used in the method to complete the execution, which may cause freeze or ANR.

Of course, this is not to SPsay that it is useless. As the saying goes, existence is reasonable. When we do not involve cross-processes and the amount of stored data is relatively small, it SPis still a pretty good choice.

2. Basic usage of DataStore

The first thing that needs to be declared is that DataStorethere are two versions, one is similar to SPreading and writing based on ordinary files; the other is based on Google protobufpatterns, here protobufis Googlea self-developed data structure, which is usually used more At least, I have written similar ones in my blog before, so here I will only introduce the first file-based logic:

The following introduces the basic usage DataStoreof :

First you need to import:

 implementation("androidx.datastore:datastore:1.0.0")

First of all, we need to be clear, since ours DataStoreis compatible with the current use SP, then it should support SPthe storage type, and we also know that SPthe supported data types are Int, Long, Float, Boolean, Stringand StringSet; at this time DataStore, not only the above six data structures are supported, but also One additional Doubletype is supported.

Create an DataStoreobject:

val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "dataStore_data")

First we need to read an Intobject of type:

val keyName = intPreferencesKey("example_counter")
val keyNameFlow: Flow<Int> = context.dataStore.data
  .map {
    
     preferences -> preferences[keyName] ?: 0
}

Then we write an object of Inttype :

val keyName = intPreferencesKey("example_counter")

suspend fun incrementCounter() {
    
    
  context.dataStore.edit {
    
     settings ->
    val currentCounterValue = settings[keyName] ?: 0
    settings[keyName] = currentCounterValue + 1
  }
}

At first glance, it looks very confusing, what is written. That's right, I also thought the same way on the first day I DataStorelearned SP. It's more than ten thousand times more fragrant than this thing. I don't know so many new things, and it feels awkward to write. Writing a simple way to store so many code.

First of all, when a new knowledge point comes out, everyone must resist it in their hearts, because it takes time and energy to understand and practice. But everyone is the same, so we still need to embrace changes.

First of all, let's follow the simple method. We all know that SPit is a XMLfile- based Key-Valuestructure, so DataStoreas its compatible class, it must also be compatible with this Key-Valuestructure. So is DataStoreit type? But it is not, it is a type, the specific type is: , can be divided into the following types:KeyStringPreferences.Keyandroidx.datastore.preferences.core.Preferences.Key

  1. intPreferencesKey -> Preferences.Key<Int> Save Inttype data
  2. doublePreferencesKey -> Preferences.Key<Double> Save Doubletype data
  3. stringPreferencesKey -> Preferences.Key<String> Save Stringtype data
  4. booleanPreferencesKey ->Preferences.Key<Boolean> Save Booleantype data
  5. floatPreferencesKey -> Preferences.Key<Float> save Floattype data
  6. longPreferencesKey -> Preferences.Key<Long> save Longtype data
  7. stringSetPreferencesKey -> Preferences.Key<Set<String>> Save Set<String>type data

With Keythat, we need to see DataStorehow to store and read data.

a. How to write DataStore

SPYes Editor, DataStorethere are editmethods for the same reason:

public suspend fun DataStore<Preferences>.edit(
    transform: suspend (MutablePreferences) -> Unit
): Preferences {
    
    
    return this.updateData {
    
    
        // It's safe to return MutablePreferences since we freeze it in
        // PreferencesDataStore.updateData()
        it.toMutablePreferences().apply {
    
     transform(this) }
    }
}

First of all, it is a suspendfunction that can only be run in the body of the coroutine. Whenever suspenda function runs in a suspended mode, it will not block the main thread.

Since it is suspenda function, we can write data in a summation way 同步:异步

同步方式:

private suspend fun saveSyncIntData(key : String, value:Int) {
    
    
    globalDataStore.edit {
    
     mutablePreferences -> mutablePreferences[intPreferencesKey(key)] = value }
}

异步方式:
This is very simple, you can play it as you like, just add one to the synchronization method runBlocking:

private fun saveIntData(key: String, value:Int) = runBlocking {
    
     saveSyncIntData(key,value) }

b. How to read DataStore

According to the above convention, there must be two methods of 同步读取and . 异步读取First of all, it needs to be clear that DataStorewhat is datareturned is Flowa type, which Flowis a stream interface, similar to inRxJava , there are many operators that can transform data, and if time permits, you can write an article about it.ObservableFlow

First we get 同步的读:

private fun readSyncIntData(key: String, defaultValue: Int) : Flow<Int> = dataStore.data.catch {
    
    

        if(it is IOException) {
    
    
            it.printStackTrace()
            emit(emptyPreferences())
        } else {
    
    
            throw it
        }

    }.map {
    
     it[intPreferencesKey(key)] ?: defaultValue }

Interpret the code, dataStore.datathe return type is Flowthe type, Flowcheck catchwhether there is an exception, then mapconvert it, then get Flow<Int>, and finally return.

After writing 同步的读, then 异步的读:

private fun readIntData(key: String, defaultValue : Int) : Int {
    
    
    var resultValue = defaultValue

    runBlocking {
    
    
        dataStore.data.first {
    
    
            resultValue = it[intPreferencesKey(key)] ?: resultValue
            true
        }
    }

    return resultValue
}

The asynchronous read directly returns the specific type of data, and firstthe operator here means to take the first one.

Basically, we DataStorehave a simple understanding of the operation, and the important thing is to practice it by ourselves, which is neither difficult nor easy.

3. Migrate from SP to DataStore

There are roughly two steps:

  1. Need one SharedPreferencesMigration, this migration class is not difficult, just need the file name you pass in Contextand SP:
val Context.dataStore : DataStore<Preferences> by preferencesDataStore(name = "dataStore_setting",
    produceMigrations = {
    
     context ->
        listOf( SharedPreferencesMigration(context, "sp_name"))
    })

  1. When dataStorethe generation is complete, a read or write operation needs to be performed, SharedPreferencesthe data will be migrated to dataStore, and SharedPreferencesthe file will also be deleted.
  • SPfolder used
    insert image description here

  • Migrated to DataStorethe folder
    https://img-blog.csdnimg.cn/4326d3e277f24fe2820530cfa59bd0e0.png
    You can see SPthat the file has been deleted, and then dataStorethe file directory is/data/data/package_name/files/xxx.preferences_pb

4. Encapsulation class of DataStore

For the convenience of operation, the logic encapsulated here DataStorewill be more convenient to read and write. The code of the method part is:
insert image description here

It is also very simple when we use it, the direct code is:

DataStoreUtils.putData("int_value",100)
DataStoreUtils.putData("long_value",100L)
DataStoreUtils.putData("float_value",100.0f)
DataStoreUtils.putData("double_value",100.00)
DataStoreUtils.putData("boolean_value",true)
DataStoreUtils.putData("string_value","hello world")

val intValue = DataStoreUtils.getData("int_value", 0)
val longValue = DataStoreUtils.getData("long_value", 0L)
val floatValue = DataStoreUtils.getData("float_value", 0.0f)
val doubleValue = DataStoreUtils.getData("double_value", 0.00)
val booleanValue = DataStoreUtils.getData("boolean_value", false)
val stringValue = DataStoreUtils.getData("string_value", "hello")

Of course, this is just an asynchronous way of reading/storing, of course we also have a synchronous way of getting:

lifecycle.coroutineScope.launch {
    
    

  //  读取
  DataStoreUtils.getSyncData("int_value",0).collect(object : FlowCollector<Int> {
    
    
      override suspend fun emit(value: Int) {
    
    
                   Log.d("TAG","get sync data : $value")
               }
           })
           
  // 写入
  DataStoreUtils.putSyncData("int_value", 1)
  
  }

Of course, you can see the specific source code here .

5. Personal conclusions

Generally speaking, DataStoreif it is highly encapsulated, there is basically no difference in the way of use SP. It solves SPthe existing criticisms, but for now, its performance is still unknown. This may require subsequent online inspections. Of course, there should be no big problems with the things produced by Gu Daddy. Of course, there is still a very high threshold for learning . DataStoreAmong Kotlinthem 协程, there is still a rather steep learning gradient for related knowledge points such as .高阶函数Flow

Guess you like

Origin blog.csdn.net/u013762572/article/details/121984443