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

Droidcon APAC 2020

Hitesh Das
December 15, 2020

Droidcon APAC 2020

Upcoming Session on DataStore Preferences and migrating from SharedPreferences

Hitesh Das

December 15, 2020
Tweet

More Decks by Hitesh Das

Other Decks in Technology

Transcript

  1. DataStore Preferences and Migrating from SharedPreferences Droidcon, APAC 2020 14-15

    December, 2020 Hitesh Das Technical Architect, Chapter Lead, Android
  2. - Introduction to Jetpack DataStore - Under the hood -

    Why do we need DataStore? - Setting up your android project with - Preference DataStore - Proto/Typed DataStore - Migrating from SharedPreferences to Datastore Contents
  3. - Part of Android Jetpack, as the name suggests, it’s

    a data storage solution Introduction - Comes with two different implementations: - Preferences DataStore, that stores key-value pairs - Proto DataStore, that lets us store typed objects (via protocol buffers) - Stores data asynchronously, consistently, and transactionally - Ideal for small, simple datasets and does not support partial updates or referential integrity.
  4. - Both Preference DataStore and Proto DataStore saves the preferences

    in a file and performs all data operations on Dispatchers.IO unless specified otherwise. Under the hood - As far as saving data is concerned, Preference DataStore, like SharedPreferences, has no way to define a schema or to ensure that keys are accessed with the correct type. - Proto DataStore lets you define a schema using Protocol buffers allowing persisting strongly typed data. - DataStore provides efficient, cached (when possible) access to the latest durably persisted state. The flow will always either emit a value or throw an exception encountered when attempting to read from disk.
  5. - Sharedpreferences provides a synchronous API that is safe in

    disguise to call on the UI thread. Moreover, there is no mechanism for signaling errors, lack of transactional API - Although Sharedpreferences provides asynchronous APIs for reading changed values, it’s not MAIN-thread-safe. Sometimes becomes a source of ANRs Why do we need DataStore? - DataStore, on the other hand, supports Async API via Kotlin Coroutines and Flow and is safe to use in UI thread - DataStore can signal errors and is safe from runtime exceptions(parsing errors) - Provides type safety - Proto DataStore are comparatively faster, smaller, simpler, and less ambiguous than XML and other similar data formats - Also, it provides a way to migrate from SharedPreferences :)
  6. dependencies { // For Preference DataStore implementation "androidx.datastore:datastore-preferences:1.0.0-alpha02" // For

    lifecycle and livedata support implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0' } Set up - Preference Datastore compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = JavaVersion.VERSION_1_8 }
  7. 1. CREATING THE PREFERENCE DATASTORE private val dataStore: DataStore<Preferences> =

    context.createDataStore( name = "app_preference" ) 3. WRITING TO THE PREFERENCE DATASTORE suspend fun incrementAppLaunchCounter() { dataStore.edit { preferences -> val currentCounterValue = preferences[keyAppLaunchCounter] ?: 0 preferences[keyAppLaunchCounter] = currentCounterValue + 1 } } 2. CREATING THE PREFERENCE KEY private val keyAppLaunchCounter = preferencesKey<Int>(name = "app_launch_counter")
  8. 4. READING FROM THE PREFERENCE DATASTORE fun getCurrentAppLaunchCounter() : Flow<Int>

    { return dataStore.data.map { preferences-> preferences[keyAppLaunchCounter]?: 0 } } 5. REMOVING KEY FROM THE PREFERENCE DATASTORE suspend fun clearAppLaunchCounter() { dataStore.edit { preferences -> preferences.remove(keyAppLaunchCounter) } } private fun observeAppLaunchCounter() { dataStoreUtils.getCurrentAppLaunchCount er() .asLiveData().observe(this, Observer { // …. Use the preference here --- }) }
  9. - Protocol buffers are Google’s language-neutral, platform-neutral, extensible mechanism for

    serializing structured data. - Protocol buffers currently support generated code in Java, Python, Objective-C, and C++. - Official Document https://developers.google.com/protocol-buffers/docs/proto What are Protocol Buffers?
  10. apply plugin: 'com.google.protobuf' dependencies { // For Proto DataStrore implementation

    "androidx.datastore:datastore-core:1.0.0-alpha02" // For ProtoBuf implementation "com.google.protobuf:protobuf-javalite:3.10.0" } protobuf { protoc { artifact = "com.google.protobuf:protoc:3.10.0" } generateProtoTasks { all().each { task -> task.builtins { java { option 'lite' } } } } } Set up - Proto DataStore To work with Proto DataStore and get Protobuf to generate code for our schema, we'll have to make several changes to the build.gradle file: • Add the Protobuf plugin • Add the Protobuf and Proto DataStore dependencies • Configure Protobuf to generate classes for the serializer
  11. 1. DEFINING THE PROTO FILE When working with Proto DataStore,

    you define your schema in a proto file (e.g filter.proto) in the app/src/main/proto/ directory syntax = "proto3"; option java_package = "com.demo.jetpackdatastore"; message AdFilterPreference { AdType adType = 1; AdCategory adCategory = 2; enum AdType { TYPE_ALL = 0; PAID = 1; FREE = 2; } enum AdCategory { CATEGORY_ALL = 0; AUTOS = 2; ELECTRONICS = 1; } } The AdFilterPreference.java class is generated at compile time from the message defined in the proto file. Build -> Rebuild Project In protobufs, each structure to be saved in Proto DataStore is defined using a message keyword
  12. 2. CREATING THE PROTO SERIALIZER If you’re using Proto DataStore,

    you’ll also have to implement the DataStore Serializer interface with AdFilterPreference class as type to tell DataStore how to read and write your data type. class AdFilterPreferenceSerializer : Serializer<Filter.AdFilterPreference>{ override fun readFrom(input: InputStream): Filter.AdFilterPreference { try { return Filter.AdFilterPreference.parseFrom(input) } catch (exception: InvalidProtocolBufferException) { throw CorruptionException("Cannot read proto.", exception) } } override fun writeTo(t: Filter.AdFilterPreference, output: OutputStream) { t.writeTo(output) } }
  13. 3. CREATING THE PROTO DATASTORE private val dataStore: DataStore<Filter.AdFilterPreference> =

    context.createDataStore( fileName = "ad_list_prefs.pb", serializer = AdFilterPreferenceSerializer() ) 4. WRITING TO THE PROTO DATASTORE suspend fun updateAdType(type: AdType?) { val adType = when (type) { AdType.FREE -> Filter.AdFilterPreference.AdType.FREE AdType.PAID -> Filter.AdFilterPreference.AdType.PAID else -> Filter.AdFilterPreference.AdType.TYPE_ALL } dataStore.updateData { preferences -> preferences.toBuilder() .setAdType(adType) .build() } }
  14. 4. READING FROM THE PROTO DATASTORE fun getAdFilter() : Flow<AdFilter>

    { return dataStore.data .catch { AdFilter(AdCategory.ALL, AdType.ALL) } .map { val type = when(it.adType) { Filter.AdFilterPreference.AdType.FREE -> AdType.FREE Filter.AdFilterPreference.AdType.PAID -> AdType.PAID else -> AdType.ALL } val category = when(it.adCategory) { Filter.AdFilterPreference.AdCategory.AUTOS -> AdCategory.AUTOS Filter.AdFilterPreference.AdCategory.ELECTRONICS -> AdCategory.ELECTRONICS else -> AdCategory.ALL } AdFilter(category, type) } } private fun observeFilters() { dataStoreUtils.getAdFilter(). asLiveData().observe(this, Observer { setFilters(it) setListDataWithFilters(it) }) }
  15. - We need to pass in a SharedPreferencesMigration object to

    the DataStore builder. - DataStore can automatically migrate from SharedPreferences to DataStore for you. - Migrations are run before any data access can occur in DataStore. - Migration must have succeeded before DataStore data returns any values and before DataStore.updateData() can update the data. - You can use the default SharedPreferencesMigration implementation and just pass in the name used to construct your SharedPreferences. - When migrating to Proto DataStore, you should have to implement a mapping function that defines how to migrate from the key-value pairs used by SharedPreferences to the DataStore schema you defined. Migration
  16. Note: keys are only migrated from SharedPreferences once, so you

    should discontinue using the old SharedPreferences once the code is migrated to DataStore. private val dataStore: DataStore<Preferences> = context.createDataStore( name = "<PREFERENCES_FILE_NAME_TO_MIGRATE>", migrations = listOf(SharedPreferencesMigration(context, "<PREFERENCES_FILE_NAME_TO_MIGRATE>")) ) MIGRATION FROM SHAREDPREFERENCES TO PREFERENCE DATASTORE
  17. private val sharedPrefsMigration = SharedPreferencesMigration( context, "<old preference file to

    migrate>" ) { sharedPrefs: SharedPreferencesView, currentData: Filter.AdFilterPreference -> currentData.toBuilder() .setAdType( Filter.AdFilterPreference.AdType.valueOf( sharedPrefs.getString( "<old ad type filter key name>", "TYPE_ALL" )!! ) ) .build() } MIGRATION FROM SHAREDPREFERENCES TO PROTO DATASTORE private val dataStore: DataStore<Filter.AdFilterPreference> = context.createDataStore( fileName = "ad_list_prefs.pb", serializer = AdFilterPreferenceSerializer(), migrations = listOf(sharedPrefsMigration) ) 1. CREATE MAPPER 2. PASS THE MAPPER AND SERIALIZER TO CREATE DATASTORE OBJECT
  18. - Use Kotlin runBlocking() coroutine builder val exampleData = runBlocking

    { dataStore.data.first() } Using DataStore in synchronous code - Asynchronously Preloading : Performing synchronous I/O operations on the UI thread is always not recommended. You can overcome these issues by asynchronously preloading the data from DataStore and later synchronous reads using runBlocking() may be faster or may avoid a disk I/O operation altogether if the initial read has completed. override fun onCreate(savedInstanceState: Bundle?) { lifecycleScope.launch { dataStore.data.first() } }
  19. - Android Official Developer site https://developer.android.com/topic/libraries/architecture/datastore - Gitlab demo/example Repo

    : https://gitlab.com/hiteshdas1912/jetpack-datastore-demo - Protocol Buffers Document : https://developers.google.com/protocol-buffers/docs/proto References
  20. Hitesh Das (HD) TECHNICAL ARCHITECT, Android Chapter Lead, OLX GROUP,

    INDIA Medium [email protected] +91-9015225032 @hiteshdas1912 @hiteshdas - - -