Use Jetpack DataStore for data storage

Posted by qads on Wed, 12 Jan 2022 22:09:15 +0100

Welcome to jetpack DataStore, an improved new data storage solution designed to replace the original SharedPreferences. Jetpack DataStore is developed based on Kotlin collaboration and Flow, and provides two different implementations: proto DataStore # and # preferences DataStore. Proto DataStore can store objects with types (implemented with proto buffers); Preferences DataStore, which can store key value pairs. In DataStore, data is stored in an asynchronous, consistent and transactional manner, which overcomes most of the shortcomings of shared preferences.

SharedPreferences vs. DataStore

  • SharedPreferences has a synchronization API that looks like it can be called thread safe in the UI, but it actually performs disk I/O operations. In addition, the apply () method blocks the UI thread at fsync (). Anywhere in your application, whenever a Service or Activity starts or stops, a call waiting for fsync () will be triggered. The calling process of {fsync() scheduled by} apply() blocks the UI thread, which is often the source of} ANR}** SharedPreferences throws runtime exceptions when parsing errors.
  • ANRhttps://developer.android.goo...

In both implementations, unless otherwise specified, the DataStore will store preferences in a file, and all data operations will be performed in dispatchers Execute on Io #.

Although both Preferences DataStore and Proto DataStore can store data, their implementation methods are different:

  • Preference DataStore, like SharedPreferences, cannot define a schema or guarantee access to key values of the correct type.
  • Proto DataStore lets you define a schema using Protocol buffers. Using Protobufs, you can preserve strongly typed data. They are faster, smaller, and less ambiguous than XML or other similar data formats. Although Proto DataStore requires you to learn a new serialization mechanism, considering the advantages of strongly typed schema brought by Proto DataStore, we think the cost is worth it.
  • Protocol buffershttps://developers.google.cn/...

Comparison between Room and DataStore

If you have the need to locally update data, reference integrity, or support large and complex data sets, you should consider using Room Not DataStore. DataStore is an ideal choice for small, simple datasets. It does not support local updates and referential integrity.

Using DataStore

First add the DataStore dependency. If you are using Proto DataStore, make sure you have also added the proto dependency:

def dataStoreVersion = "1.0.0-alpha05" 
// Confirm the latest version number on the Android Developer website 
// https://developer.android.google.cn/jetpack/androidx/releases/datastore

// Preferences DataStore
implementation "androidx.datastore:datastore-preferences:$dataStoreVersion"

// Proto DataStore
implementation  "androidx.datastore:datastore-core:$dataStoreVersion"

When you use Proto DataStore, you need to use the proto file to define your own schema in the app/src/main/proto / directory. For more information on defining proto schema s, see the} protobuf language guide.

syntax = "proto3";

option java_package = "<your package name here>";
option java_multiple_files = true;

message Settings {
  int my_counter = 1;
}

Create DataStore

You can use context Createdatastore() extension method creates DataStore:

// Create Preferences DataStore 
val dataStore: DataStore<Preferences> = context.createDataStore(
    name = "settings"
)

If you are using Proto DataStore, you also need to implement the {Serializer} interface to tell DataStore how to read and write your data types.

object SettingsSerializer : Serializer<Settings> {
    override fun readFrom(input: InputStream): Settings {
        try {
            return Settings.parseFrom(input)
        } catch (exception: InvalidProtocolBufferException) {
            throw CorruptionException("Cannot read proto.", exception)
        }
    }

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

// Create Proto DataStore
val settingsDataStore: DataStore<Settings> = context.createDataStore(
    fileName = "settings.pb",
    serializer = SettingsSerializer
)

Read data from DataStore

The DataStore will expose the stored data in the form of Flow, whether it is the "Preferences" object or the object you define in the proto schema. DataStore can ensure that it is in the {dispatchers Retrieve data on Io # so your UI threads are not blocked.

Using Preferences DataStore:

val MY_COUNTER = preferencesKey<Int>("my_counter")
val myCounterFlow: Flow<Int> = dataStore.data
     .map { currentPreferences ->
        // Unlike Proto DataStore, type safety is not guaranteed here.
        currentPreferences[MY_COUNTER] ?: 0   
   }

Using Proto DataStore:

val myCounterFlow: Flow<Int> = settingsDataStore.data
    .map { settings ->
        // The myCounter attribute is generated by your proto schema!
        settings.myCounter 
    }

Write data to DataStore

In order to write data, DataStore provides a {DataStore updateData() suspends the function, which will provide you with the status of the currently stored data as a parameter, both for the {Preferences} object or the object instance you define in the proto schema. The updateData() function uses atomic read, write, modify operations and updates the data in a transactional manner. This process is completed when the data is stored on disk.

Preferences DataStore also provides a} datastore Edit () function to facilitate data update. In this function, you will receive a MutablePreferences object for editing instead of a MutablePreferences object. Like updateData(), this function applies the changes to the disk after the conversion code block is completed, and this collaboration will be completed when the data is stored on the disk.

Using Preferences DataStore:

suspend fun incrementCounter() {
    dataStore.edit { settings ->
        // It can safely increase our counters without losing data due to resource competition.
        val currentCounterValue = settings[MY_COUNTER] ?: 0
        settings[MY_COUNTER] = currentCounterValue + 1
    }
}

Using Proto DataStore:

suspend fun incrementCounter() {
    settingsDataStore.updateData { currentSettings ->
        // It can safely increase our counters without losing data due to resource competition.
        currentSettings.toBuilder()
            .setMyCounter(currentSettings.myCounter + 1)
            .build()
    }
}

Migrating from SharedPreferences to DataStore

To migrate from SharedPreferences to DataStore, you need to pass the SharedPreferences migration object to the DataStore constructor. DataStore can automatically complete the migration from SharedPreferences to DataStore. The migration runs before any data access occurs in the DataStore, which means that in the DataStore Data , returns any value and , DataStore Before updatedata() can update data, your migration must have been successful.

If you want to migrate to the Preferences DataStore, you can use the default implementation of SharedPreferences migration. Just pass in the name used in the SharedPreferences construct.

Using Preferences DataStore:

val dataStore: DataStore<Preferences> = context.createDataStore(
    name = "settings",
    migrations = listOf(SharedPreferencesMigration(context, "settings_preferences"))
)

When migrating to Proto DataStore, you must implement a mapping function to define how to migrate the key value pairs used by SharedPreferences to the DataStore schema you defined.

Using Proto DataStore:

val settingsDataStore: DataStore<Settings> = context.createDataStore(
    produceFile = { File(context.filesDir, "settings.preferences_pb") },
    serializer = SettingsSerializer,
    migrations = listOf(
        SharedPreferencesMigration(
            context,
            "settings_preferences"            
        ) { sharedPrefs: SharedPreferencesView, currentData: UserPreferences ->
            // Here, map sharedPrefs to your type.
          }
    )
)

summary

SharedPreferences has many defects: it seems that the synchronization API that can be safely called in the UI thread is not safe, there is no error prompt mechanism, there is a lack of transaction API, and so on. DataStore is an alternative to Shared Preferences, which solves most of the problems of Shared Preferences. DataStore contains a fully asynchronous API implemented using Kotlin collaboration and Flow, which can handle data migration, ensure data consistency, and handle data corruption.

Advanced notes of Android advanced development system, latest interview review notes PDF, My GitHub

end of document

Your favorite collection is my greatest encouragement!
Welcome to follow me, share Android dry goods and exchange Android technology.
If you have any opinions on the article or any technical problems, please leave a message in the comment area for discussion!

Topics: Android