Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Jetpack DataStore

Jetpack DataStore

In Android apps, you can store user settings, like language, using SharedPreferences API. SharedPreferences runs its read/write operations on the UI thread. This can block the UI thread, becoming a source of ANRs(Application Not Responding).

Jetpack DataStore is the new and improved data storage solution. It allows you to store key-value pairs or typed objects using protocol buffers. It is built on Kotlin Coroutines and Flow. Hence, data is stored asynchronously, consistently, and transactionally, overcoming some of the limitations of SharedPreferences.

In this session, I'll go through Jetpack DataStore. I'll go over the two implementations: Preferences DataStore and Proto DataStore, as well as how to migrate SharedPreferences to DataStore. Goodbye ANRs 😀.

Beatrice Kinya

December 04, 2021
Tweet

More Decks by Beatrice Kinya

Other Decks in Technology

Transcript

  1. SharedPreferences ❖ Store key value pairs. ❖ Has a synchronous

    api. The api looks safe to call on the UI thread, but it runs disk I/O operations on the UI thread. This may block the UI thread. ❖ Throws parse time errors as runtime exceptions.
  2. Jetpack DataStore ❖ It is the new and improved data

    storage solution aimed at replacing SharedPreferences. ❖ It is built on Kotlin coroutines and Flow. ❖ It is safe to call on the UI thread. Under the hood, it uses Dispatchers.IO, unless specified otherwise. ❖ It provides two different implementations: ➢ Preference DataStore which stores of key-value pairs. ➢ Proto DataStore which stores typed objects using protocol buffers.
  3. Creating Preferences DataStore ❖ Add this dependency in build.gradle(app) file

    implementation "androidx.datastore:datastore-preferences:1.0.0"
  4. Creating Preferences DataStore ❖ Add this code at the top

    level of your kotlin file. const val DATASTORE_NAME = "settings" val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = DATASTORE_NAME)
  5. Writing to Preferences DataStore ❖ Preferences DataStore provides an edit()

    function that transactionally updates the data in a DataStore. context.dataStore.edit { settings -> settings[LANGUAGE_KEY] = language }
  6. Writing to Preferences DataStore ❖ Note: When writing to Preferences

    Datastore use the corresponding key type function to define a key for each value that you need to store in the DataStore<Preferences> instance. Example: val LANGUAGE_KEY = stringPreferencesKey("language")
  7. Reading From Preferences DataStore ❖ Use DataStore.data property to expose

    the stored value using a Flow. ❖ Preferences DataStore exposes the data stored in a Flow<Preferences> . fun getLanguage(context: Context): Flow<String?> { return context.dataStore.data.map { settings -> settings[LANGUAGE_KEY] } }
  8. Proto DataStore ❖ It stores objects with custom data types

    with protocol buffers. ❖ Protocol buffers(Protobuf) is a free and open source data format use to serialize structured data. Similar to XML but smaller, faster and simpler. ❖ Proto DataStore defines schemas using protocol buffers. This allows persisting strongly typed data. ❖ To work with Proto DataStore and get Protobuf to generate code for our the schema follow set up instructions in the following link: https://bit.ly/3NnXAb9
  9. Defining and Using Protobuf objects ❖ You define your schemas

    in proto files in src/main/proto directory ❖ In protobufs: ➢ Each structure is defined using a message keyword. ➢ Each member of the structure is defined inside the message, based on type and name and it gets assigned a 1-based order. ❖ Do not forget to build the project after defining your schema. 😀
  10. syntax = "proto3"; option java_package = "com.example.greetingsapp"; option java_multiple_files =

    true; message UserProfile{ string name = 1; string hobby = 2; string funFact = 3; }
  11. Create a Serializer ❖ Serializer class tells DataStore how to

    read and write your data type. ❖ Define a class that implements Serializer<T>, where T is the type defined in the proto file.
  12. object UserProfileSerializer : Serializer<UserProfile> { override val defaultValue: UserProfile get()

    = UserProfile.getDefaultInstance() override suspend fun readFrom(input: InputStream): UserProfile { TODO() } override suspend fun writeTo(t:UserProfile, output:OutputStream) { TODO() } }
  13. Creating a Proto DataStore ❖ Put this code at the

    top level of your Kotlin file. val Context.protoDataStore: DataStore<UserProfile> by dataStore(DATA_STORE_FILE_NAME,UserProfileSerializer)
  14. Reading From Proto DataStore ❖ Use DataStore.data to expose a

    Flow of the appropriate property from your stored objects, e.g. Flow<UserProfile> fun getUserProfile(context: Context): Flow<UserProfile>{ return context.protoDataStore.data }
  15. Writing to Proto DataStore ❖ Proto Datastore provides updateData() function

    to update stored data. context.protoDataStore.updateData {userProfile -> userProfile.toBuilder() .setName(user.name) .build() }
  16. Migrating to Preferences DataStore ❖ To migrate to Preference Datastore,

    pass in SharedPreferencesMigration instance to the list of migrations parameter. val Context.dataStore: DataStore<Preferences> by preferencesDataStore( name = DATASTORE_NAME, produceMigrations = { context -> listOf(SharedPreferencesMigration(context, SETTINGS_NAME)) })
  17. Migrating SharedPreferences to DataStore ❖ Note: Migrations are run before

    any data access can occur in DataStore. This means that your migration must have succeeded before DataStore.data emits any values and before DataStore.edit() can update data.
  18. Migrating to Proto DataStore To migrate to Proto DataStore: ❖

    Pass in SharedPreferencesMigration instance to the list of migrations parameter of datastore builder. ❖ Implement a mapping function that defines how to migrate from the key-value pairs used by SharedPreferences to the DataStore schema you defined.
  19. val Context.protoDataStore: DataStore<UserProfile> by dataStore( fileName = "settings.pb", serializer =

    UserProfileSerializer, produceMigrations = { context -> listOf( SharedPreferencesMigration( context, SETTINGS_NAME ) { sharedPrefs: SharedPreferencesView, currentData: profile -> currentData.toBuilder() .setName(sharedPrefs.getString(NAME_KEY)) .build() }) })
  20. Room vs DataStore ❖ DataStore is ideal for small or

    simple datasets and does not support partial updates or referential integrity, for instance only one user’s data. ❖ If you have a need for partial updates, referential integrity, or large/complex datasets, you should consider using Room instead of DataStore.
  21. Thank you! Android Engineer She/her @B__Kinya Migrating SharedPreferences to DataStore:

    https://bit.ly/3pQHqxB Jetpack DataStore Docs: https://bit.ly/3tjaQWd Sample Project: https://bit.ly/3Mj5hhb Resources Beatrice Kinya