Introduction to DataStore Storage Solution
DataStore is the recommended data storage solution for key-value pairs in an Android app. The following sections will walk you through creating and using a DataStore instance.
DataStore is a modern persistence solution from Google. Persistence allows you to store data, key-value pairs, or typed objects with protocol buffers. DataStore uses Kotlin coroutines and Flows to store data asynchronously, consistently, and transactionally. This makes DataStore read and write operations more performant and safe. There are two implementations of DataStore that you can use:
- Preferences DataStore: Stores data as key-value pairs, similar to SharedPreferences. You can only store and retrieve primitive data types such as String, Int, Long, Float, and Boolean.
- Proto DataStore: Uses protocol buffers to store custom data types. When using Proto DataStore, you define a schema for your custom data type.
Here’s a comparison of Proto DataStore and Preferences DataStore:
- Preferences DataStore stores data as key-value pairs, whereas Proto DataStore uses protocol buffers to store custom data types.
- With Preferences DataStore, you can easily migrate from SharedPreferences to DataStore. Proto DataStore is more complex and requires you to define a schema for your custom data types.
- Preferences DataStore doesn’t provide type safety. Proto DataStore provides type safety by using protocol buffers and complex data types.
If you’re storing simple data such as user preferences, settings, configurations, or access tokens for authenticating your network calls, you can use Preferences DataStore. If you’re storing complex data types, you can use Proto DataStore. In this lesson, you’ll primarily focus on Preferences DataStore as you’ll be storing user-selected filters in your app.
DataStore was introduced as a replacement for SharedPreferences. SharedPreferences was the go-to solution for storing simple data in Android. However, SharedPreferences has some limitations. This is how DataStore addresses them:
- Type safety: SharedPreferences doesn’t provide type safety. You can store both a
String
and anInt
with the same key, and your app will compile without any errors. DataStore addresses this by providing type safety and reducing the risk of runtime crashes and type-casting errors. - Error handling: SharedPreferences doesn’t provide a great way to propagate errors. It will throw parsing errors as runtime exceptions. Using DataStore, you can handle errors more gracefully by using Kotlin coroutines and Flows.
- Asynchronous operations: SharedPreferences doesn’t provide a way to perform asynchronous operations. It’s always unsafe to call SharedPreferences on the UI thread as it can cause jank by blocking the UI thread.
- Observing data changes: SharedPreferences doesn’t provide a way to observe data changes. DataStore uses Kotlin Flow to observe data changes.
- Migration Support: SharedPreferences doesn’t provide a way to migrate data, you have to handle it manually in an error-prone way. DataStore provides a way to migrate data. Additionally, you can migrate safely from SharedPreferences to DataStore.
- Data Consistency: Modifying data in SharedPreferences isn’t atomic. This means it’s not always guaranteed that your data modifications will be reflected everywhere. However, DataStore updates data in a transactional way, ensuring that your data modifications are atomic.
You now have an understanding of DataStore and how it compares to SharedPreferences. Next, you’ll create and set up your own DataStore.
To use DataStore, you need to add the DataStore dependencies to your project:
implementation "androidx.datastore:datastore-preferences:1.1.1"
After adding the dependency, you need to create a DataStore instance as follows:
private val Context.dataStore:DataStore<Preferences> by preferencesDataStore(name = "devscribe")
You interact with the data you have saved in DataStore using an instance of DataStore<Preferences>
. DataStore is an interface that provides the necessary APIs to read and write data to DataStore. Preferences is an abstract class used especially for Preferences DataStore. Its purpose is to manage key-value pairs. You use the delegate preferencesDataStore()
to create a DataStore instance. The name parameter is the name of the DataStore. This delegate is a Kotlin extension property with a receiver type of Context. Context is needed to construct the File object where DataStore stores the data. Each file shouldn’t have more than one DataStore instance. Doing so can break all DataStore functionality.