DataStore and data migration
In this post from our Jetpack DataStore series, we will be covering how to do DataStore-to-DataStore migrations. Hopefully, this will provide you all the information you need to add DataStore to your app successfully. We will be referring to the Preferences codelab throughout this post, for code samples.
Data migration
We previously talked about migrating from SharedPreferences
to DataStore, using the SharedPreferencesMigration
. However, it is likely that at some point you will also need to do a DataStore-to-DataStore migration, for example when you make some significant changes to your dataset (like renaming your data model values or changing their type).
This process is very similar to the migration from SharedPreferences
. In fact, SharedPreferencesMigration
is just an implementation of the DataMigration
interface:
To understand how to use DataMigration
, let’s break this down:
DataMigration<T>
— our interface for migrations to DataStore. Methods on this migration (shouldMigrate
,migrate
andcleanUp
) may be called multiple times if DataStore encounters issues when writing the newly migrated data to disk or if any of the migrations throws anException
.shouldMigrate()
— specifies whether this migration needs to be performed. If this returns false, no migration or cleanup will occur. This will be called every time the DataStore is initialized.migrate()
— performs the migration. If it fails, DataStore will not commit any data to disk,cleanUp()
will not be called, and the exception will be propagated back to the DataStore call that triggered the migration. Future calls to DataStore will result in migration being attempted again. Note that this will always be called before a call tocleanUp()
.cleanUp()
—where you’d add your implementation for clearing any old data from the previous storage that was migrated into the new DataStore. This will not be called if the migration fails. IfcleanUp()
throws an exception, the exception will be propagated back to the DataStore call that triggered the migration and future calls to DataStore will result in migration being attempted again.
Preferences DataStore migration
In order to create an implementation that would suit our migration needs, let’s imagine a scenario where we’d want to migrate from oldPreferencesDataStore
to newPreferencesDataStore
. We want to:
- Remap and rename one specific old
Float
key-value pair to a newInt
one - Migrate all other key-value pairs from old to new as they are
- Clean up the old storage
For simplicity, we will do all this in our TasksActivity
of the Preferences codelab using the Preference DataStore delegates, but you can follow the instructions on Hilt injection to move this into an injection module.
We implement DataMigration
and override its functions in the following manner:
produceMigrations
will ensure that the migrate()
is run before any potential data access to the new DataStore. This means your migration must have succeeded before DataStore emits any further values and before it begins making any new changes to the data.
As the produceMigrations
parameter takes in a list of DataMigration
s, you could transfer from as many old storages as you’d like. That is all it takes to migrate your data safely! You can easily follow the same pattern to migrate Proto to Proto DataStore — the only difference would be in how you’d transform the data.
To be continued
We’ve covered performing data migrations from different DataStores — how the DataMigration
interface works, how to override its functions to transform your old data to new, migrate it and then clean up accordingly.
Join us for the next and final post in the series where we will be looking into how to test DataStore.
You can find all posts from our Jetpack DataStore series here:
Introduction to Jetpack DataStore
All about Preferences DataStore
All about Proto DataStore
DataStore and dependency injection
DataStore and Kotlin serialization
DataStore and synchronous work
DataStore and data migration
DataStore and testing