[MUSIC PLAYING]
SIMONA STOJANOVIC: Hi there.
Welcome to the "MAD Skills" series
on Jetpack DataStore, a data storage replacement
for SharedPreferences.
Let's see how DataStore works, what
implementations it provides, and their individual use cases.
We'll also look at what benefits and improvements it brings
over SharedPreferences and why these make DataStore
worth your while.
DataStore is a Jetpack library that
provides a safe and consistent way
to store small amounts of data, such as preferences
or application state.
It's based on Kotlin Coroutines and Flow, which enable
asynchronous data storage.
It aims to replace SharedPreferences,
as it is thread-safe and non-blocking.
It provides two different implementations,
Proto DataStore store, which stores
typed objects backed by protocol buffers,
and Preferences DataStore, which stores key-value pairs.
Most likely you've used SharedPreferences in your apps.
It is also likely that you've experienced issues
with SharedPreferences that are hard to reproduce--
seeing crashes in your analytics due to uncaught exceptions,
blocking the UI thread when making calls, or inconsistent
persisted data throughout your app.
DataStore was built to address all of these issues.
Let's take a look at a direct comparison
between SharedPreferences and DataStore.
With most data storage APIs, you often
need to get notified asynchronously
when data has been modified.
SharedPreferences does offer some async support but only
for getting updates on changed values via its listener.
However, this callback is still invoked on the main thread.
Similarly, if you wanted to offload your file-saving work
to background, you could use SharedPreferences.apply().
But keep in mind that this will block the UI
thread on fsync, potentially causing jank and ANRs.
This could happen any time a service starts or stops
or an activity pauses or stops.
In comparison, DataStore provides a fully asynchronous
API for retrieving and saving data,
using the power of Kotlin Coroutines and Flow,
while reducing the risk of blocking your UI thread.
For those unfamiliar with Kotlin Flows,
this is just a stream of values that
can be computed asynchronously.
SharedPreferences API supports synchronous work out
of the box.
However, its synchronous commit function for modifying
persisted data may appear safe to call on the UI thread,
but it does in fact perform heavier I/O operations.
This is a risky scenario that could and often does lead
to ANRs and UI jank.
To prevent this, DataStore doesn't
offer ready-to-use synchronous support.
DataStore saves the preferences in a file and under the hood
performs all data operations in the background,
unless specified otherwise, keeping your UI thread
unblocked.
However, it is possible to combine DataStore
and synchronous work with a bit of help
from Coroutine Builders, as we'll see it later.
SharedPreferences can throw parsing errors
as runtime exceptions, leaving your app vulnerable to crashes.
For example, the ClassCastException
is a commonly occurring exception thrown by the API
when the wrong data type is requested.
DataStore provides a way of catching any exception coming
your way when reading or writing data
by relying on Flow's error-signaling mechanism.
Using map key-value pairs for saving and retrieving data
doesn't offer type safety protection.
However, with the Proto DataStore,
you can prefine a schema for your data model
and get the additional benefit of full type safety.
SharedPreferences doesn't guarantee data consistency
in a multithreaded environment.
This means you cannot rely on your data modifications being
reflected always and everywhere.
This can be dangerous, especially
since the whole point of this API is persisted data storage.
In comparison, DataStore's fully transactional API
provides strong consistency guarantees,
as the data is updated in an atomic read,
modify, write operation.
It also provides read after write consistency,
reflecting the fact that all updates that have completed
will be reflected in read values.
SharedPreferences doesn't have a built-in migration mechanism.
It is up to you to do some tedious, error-prone remapping
of your values from your old storage to the new one
and then some cleaning up, as well.
All this increases the chances of runtime exceptions,
as you could easily run into issues with data type mismatch.
DataStore, however, provides a way
of easily migrating data, along with
the provided implementation for SharedPreferences
to DataStore migration.
Now that we've seen what benefits DataStore offers
over SharedPreferences, let's talk a bit about how
to make a choice between its two implementations, Preferences
and Proto DataStore.
When it comes to data structuring,
Preferences DataStore reads and writes
data based on key-value pairs without defining the schema
upfront.
Proto DataStore, however, uses protocol buffers
for added readability.
Using key-value pairs allows a quick migration
with minimal changes from SharedPreferences
into Preferences DataStore.
Proto DataStore, on the other hand,
enables you to work with more complex data, such as enums
or lists.
Finally, if you don't require full type safety with your data
storage, then you could choose Preferences DataStore.
However, if you do need this kind of support,
then Proto DataStore is the better option for you.
You might ask, well, why not just use Room to store my data?
And that's a fair question.
So let's see where Room fits in all this.
We start by asking, do we need to work with small and simple
data sets?
If the answer is no, then it means
you're required to work with larger and more complex data
sets.
This type of data tends to require
partial updates and referential integrity
between different data tables.
If all this is true for your app,
you should consider using Room.
However, if you're working with small and simple data sets,
like preferences and app states, it
is likely you won't need partial updates
or referential integrity.
If this matches your requirements,
you should go with DataStore.
Right.
In this episode, we've gone into more detail on DataStore--
how it works, the changes and improvements it brings,
and how to decide between its two implementations.
In the next two episodes, we will further
discuss Proto and Preferences DataStore--
how to read and write data, handle any errors,
and how to migrate from SharedPreferences.
So hit Subscribe and stay tuned.
[MUSIC PLAYING]