Android – An Overview Of Jetpack DataStore

Hello Readers, CoolMonkTechie heartily welcomes you in this article (An Overview Of Jetpack DataStore).

In this article, we will learn about Google’s new library Jetpack DataStore in Android. Jetpack DataStore is Google’s new library to persist data as key-value pairs or typed objects using protocol buffers. Using Kotlin coroutines and Flow as its foundation, it aims to replace SharedPreferences. This is part of the Jetpack suite of libraries. This article explains about Jetpack DataStore types implementations and address the limitations of the SharedPreferences API in Android.

To understand the Jetpack DataStore, we cover the below topics as below :

  • Overview
  • Limitation of SharedPreferences
  • Types of Jetpack DataStore Implementations
  • Jetpack DataStore Setup
  • Store key-value pairs with Preferences DataStore
  • Store typed objects with Proto DataStore
  • Use Jetpack DataStore in synchronous code

A famous quote about learning is :

“One learns from books and example only that certain things can be done. Actual learning requires that you do those things.”

So Let’s begin.

Overview

Jetpack DataStore is a data storage solution that allows us to store key-value pairs or typed objects with protocol buffers. DataStore uses Kotlin coroutines and Flow to store data asynchronously, consistently, and transactionally. Google introduced DataStore to address the limitations in the SharedPreferences API.

Limitation of SharedPreferences

To understand the DataStore’s advantages, we need to know about the limitations of SharedPreferences API. Even though SharedPreferences has been around since API level 1, it has drawbacks that have persisted over time:

  • SharedPreferences is not always safe call on the UI thread. It can cause junk by blocking the UI thread.
  • There is no way for SharedPreferences to signal errors except for parsing errors as runtime exceptions.
  • SharedPreferences has no support for data migration. If we want to change the type of a value, we have to write the entire logic manually.
  • SharedPreferences doesn’t provide type safety. Application will compile fine If we try to store both Booleans and Integers using the same key.

Google introduced DataStore to address the above limitations.

Types of Jetpack DataStore Implementations

DataStore provides two different implementations: Preferences DataStore and Proto DataStore.

  • Preferences DataStore: Stores and accesses data using keys. This implementation does not require a predefined schema, and it does not provide type safety. This is similar to SharedPreferences. We use this to store and retrieve primitive data types.
  • Proto DataStore: Uses protocol buffers to store custom data types. When using Proto DataStore, we need to define a schema for the custom data type.

SharedPreferences uses XML to store data. As the amount of data increases, the file size increases dramatically and it’s more expensive for the CPU to read the file.

Protocol buffers are a new way to represent structured data that’s faster and than XML and has a smaller size. They’re helpful when the read-time of stored data affects the performance of our application.

Jetpack DataStore Setup

To use Jetpack DataStore in application, we add the following dependencies to Gradle file depending on which implementation we want to use:

Datastore Typed

    // Typed DataStore (Typed API surface, such as Proto)
    dependencies {
        implementation("androidx.datastore:datastore:1.0.0")

        // optional - RxJava2 support
        implementation("androidx.datastore:datastore-rxjava2:1.0.0")

        // optional - RxJava3 support
        implementation("androidx.datastore:datastore-rxjava3:1.0.0")
    }

    // Alternatively - use the following artifact without an Android dependency.
    dependencies {
        implementation("androidx.datastore:datastore-core:1.0.0")
    }
    

Datastore Preferences

    // Preferences DataStore (SharedPreferences like APIs)
    dependencies {
        implementation("androidx.datastore:datastore-preferences:1.0.0")

        // optional - RxJava2 support
        implementation("androidx.datastore:datastore-preferences-rxjava2:1.0.0")

        // optional - RxJava3 support
        implementation("androidx.datastore:datastore-preferences-rxjava3:1.0.0")
    }

    // Alternatively - use the following artifact without an Android dependency.
    dependencies {
        implementation("androidx.datastore:datastore-preferences-core:1.0.0")
    }
    

If we use the datastore-preferences-core artifact with Proguard, we must manually add Proguard rules to our proguard-rules.pro file to keep our fields from being deleted.

Store key-value pairs with Preferences DataStore

The Preferences DataStore implementation uses the DataStore and Preferences classes to persist simple key-value pairs to disk.

Create a Preferences DataStore

// At the top level of our kotlin file:
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")

Use the property delegate created by preferencesDataStore to create an instance of Datastore<Preferences>. Call it once at the top level of kotlin file, and access it through this property throughout the rest of application. This makes it easier to keep DataStore as singleton.

Read from a Preferences DataStore

val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
val exampleCounterFlow: Flow<Int> = context.dataStore.data
  .map { preferences ->
    // No type safety.
    preferences[EXAMPLE_COUNTER] ?: 0
}

Because Preferences DataStore does not use a predefined schema, we must use the corresponding key type function to define a key for each value that we need to store in the DataStore<Preferences> instance.

Write to a Preferences DataStore

suspend fun incrementCounter() {
  context.dataStore.edit { settings ->
    val currentCounterValue = settings[EXAMPLE_COUNTER] ?: 0
    settings[EXAMPLE_COUNTER] = currentCounterValue + 1
  }
}

Preferences DataStore provides an edit() function that transactionally updates the data in a DataStore. The function’s transform parameter accepts a block of code where we can update the values as needed. All of the code in the transform block is treated as a single transaction.

Store typed objects with Proto DataStore

The Proto DataStore implementation uses DataStore and protocol buffers to persist typed objects to disk.

Define a schema

Proto DataStore requires a predefined schema in a proto file in the app/src/main/proto/ directory. This schema defines the type for the objects that we persist in our Proto DataStore.

syntax = "proto3";

option java_package = "com.example.application";
option java_multiple_files = true;

message Settings {
  int32 example_counter = 1;
}

The class for our stored objects is generated at compile time from the message defined in the proto file. Make sure we rebuild our project.

Create a Proto DataStore

There are two steps involved in creating a Proto DataStore to store typed objects:

  • Step 1 : Define a class that implements Serializer<T>, where T is the type defined in the proto file. This serializer class tells DataStore how to read and write data type. Make sure we include a default value for the serializer to be used if there is no file created yet.
  • Step 2 : Use the property delegate created by dataStore to create an instance of DataStore<T>, where T is the type defined in the proto file. Call this once at the top level of kotlin file and access it through this property delegate throughout the rest of application. The filename parameter tells DataStore which file to use to store the data, and the serializer parameter tells DataStore the name of the serializer class defined in step 1.
object SettingsSerializer : Serializer<Settings> {
  override val defaultValue: Settings = Settings.getDefaultInstance()

  override suspend fun readFrom(input: InputStream): Settings {
    try {
      return Settings.parseFrom(input)
    } catch (exception: InvalidProtocolBufferException) {
      throw CorruptionException("Cannot read proto.", exception)
    }
  }

  override suspend fun writeTo(
    t: Settings,
    output: OutputStream) = t.writeTo(output)
}

val Context.settingsDataStore: DataStore<Settings> by dataStore(
  fileName = "settings.pb",
  serializer = SettingsSerializer
)

Read from a Proto DataStore

We use DataStore.data to expose a Flow of the appropriate property from our stored object.

val exampleCounterFlow: Flow<Int> = context.settingsDataStore.data
  .map { settings ->
    // The exampleCounter property is generated from the proto schema.
    settings.exampleCounter
  }

Write to a Proto DataStore

Proto DataStore provides an updateData() function that transactionally updates a stored object. updateData() gives us the current state of the data as an instance of our data type and updates the data transactionally in an atomic read-write-modify operation.

suspend fun incrementCounter() {
  context.settingsDataStore.updateData { currentSettings ->
    currentSettings.toBuilder()
      .setExampleCounter(currentSettings.exampleCounter + 1)
      .build()
    }
}

Use Jetpack DataStore in synchronous code

Asynchronous API is one of the primary benefits of DataStore. It may not always be feasible to change our surrounding code to be asynchronous. This might be the case if we’re working with an existing codebase that uses synchronous disk I/O or if we have a dependency that doesn’t provide an asynchronous API.

Kotlin coroutines provide the runBlocking() coroutine builder to help bridge the gap between synchronous and asynchronous code. We can use runBlocking() to read data from DataStore synchronously. RxJava offers blocking methods on Flowable. The following code blocks the calling thread until DataStore returns data:

val exampleData = runBlocking { context.dataStore.data.first() }

Performing synchronous I/O operations on the UI thread can cause ANRs or UI junk. We can mitigate these issues by asynchronously preloading the data from DataStore:

override fun onCreate(savedInstanceState: Bundle?) {
    lifecycleScope.launch {
        context.dataStore.data.first()
        // You should also handle IOExceptions here.
    }
}

This way, DataStore asynchronously reads the data and caches it in memory. Later synchronous reads using runBlocking() may be faster or may avoid a disk I/O operation altogether if the initial read has completed.

That’s all about in this article.

Conclusion

In this article, we understood about Google’s new library Jetpack DataStore in Android. This article explained about Jetpack DataStore types implementations and address the limitations of the SharedPreferences API in Android.

Thanks for reading! I hope you enjoyed and learned about Jetpack DataStore concepts in Android. Reading is one thing, but the only way to master it is to do it yourself.

Please follow and subscribe to the blog and support us in any way possible. Also like and share the article with others for spread valuable knowledge.

You can find other articles of CoolMonkTechie as below link :

You can also follow the official website and tutorials of Android as below links :

If you have any comments, questions, or think I missed something, leave them below in the comment box.

Thanks again Reading. HAPPY READING !!???

Android – An Overview of Android JetPack

Hello Readers, CoolMonkTechie heartily welcomes you in this article.

In this article, we will learn about Android Jetpack and Jetpack related components. We will discuss about the below Android Jetpack related topics :

  • What is Android Jetpack and why should we use it ?
  • What is Android Jetpack Components ?

A famous quote about learning is :

” That is what learning is. You suddenly understand something you’ve understood all your life, but in a new way.”

So Let’s begin.

In the above diagram, It represents a set of components, tools and guidance to make great Android apps. 

Cool !!

So first, we understand the Android Jetpack.

What is Android Jetpack ?

“Jetpack is a suite of libraries, tools, and guidance to help developers write high-quality apps more easily. These components help you follow best practices, free you from writing boilerplate code, and simplify complex tasks, so you can focus on the code you care about.”

Jetpack comprises the androidx.* package libraries, unbundled from the platform APIs. This means that it offers backward compatibility and is updated more frequently than the Android platform, making sure you always have access to the latest and greatest versions of the Jetpack components.

Android Jetpack is a collection of Android software components which helps us in building great Android apps.These software components help in:

  • Following the best practices and writing the boilerplate code.
  • Making complex things very simple.

Prior to Android Jetpack, we had many challenges during developing the android application:

  • Managing activity lifecycles in the application.
  • Surviving configuration changes.
  • Preventing memory leaks.

All these major problems have been solved by the Android Jetpack’s software components.

So, the solution for all the problems is Android Jetpack.

Another most important thing about the Jetpack is that it gets updated more frequently than the Android platform so that we always get the latest version.

Next question, We talk about a set of components, tools and guidance in Android Jetpack.

What is Android Jetpack Component ?

Android Jetpack components are a collection of libraries that are individually adoptable and built to work together while taking advantage of Kotlin language features that make us more productive.

These software components have been arranged in 4 categories which are as follows:

  • Foundation Components
  • Architecture Components
  • Behavior Components
  • UI Components

Let’s discuss one by one.

1. Foundation Components

Foundation components provide following cross-cutting functionality:

  • Backwards compatibility
  • Testing 
  • Kotlin language support.

All the foundation components are as follows:

  • App Compat: Degrade gracefully on older versions of Android with material design user interface implementation support.
  • Android KTX: Set of Kotlin extensions to write more concise, idiomatic Kotlin code.
  • Multidex: Provide support for multiple dex files for apps.
  • Test: A testing framework for unit and runtime UI tests in Android.

2. Architecture Components 

The architecture components help us in building:

  • Robust Apps
  • Testable Apps
  • Maintainable Apps

All the architecture components are as follows:

  • Data Binding: Declaratively bind UI elements to in our layout to data sources of our app.
  • Lifecycles: Manages activity and fragment lifecycles of our app.
  • LiveData: Notify views of any database changes.
  • Navigation: Handle everything needed for in-app navigation.
  • Paging: Gradually load information on demand from your data source.
  • Room: Fluent SQLite database access.
  • ViewModel: Manage UI-related data in a lifecycle-conscious way.
  • WorkManager: Manage every background jobs in Android with the circumstances we choose.

3. Behavior Components 

The behavior components help in the integration with standard Android services like:

  • Notifications
  • Permissions
  • Sharing
  • Assistant

All the behavior components are as follows:

  • Download Manager: Schedule and manage large downloads in background with auto retry support.
  • Media & playback: Backwards compatible APIs for media playback and routing (including Google Cast).
  • Notifications: Provides a backwards-compatible notification API with Wear and Auto support.
  • Permissions: Compatibility APIs for checking and requesting permissions in app.
  • Preferences: Create interactive settings screens for users to configure.
  • Sharing: Provides a share action suitable for an app’s action bar.
  • Slices: Create flexible UI elements that can display app data outside the app and can be extended all the way back to Android 4.4.

4. UI Components 

The UI components provide widgets and helpers to make your app not only easy, but delightful to use.

All the UI components are as follows:

  • Animation and transitions: Move widgets and transition between screens.
  • Auto: Components to develop Android Auto apps.
  • Emoji: Enable updated emoji font on older platforms.
  • Fragment: A basic unit of composable UI.
  • Layout: Lay out widgets with different algorithms.
  • Palette: Pull useful information from color palettes.
  • TV: Components to develop Android TV apps.
  • Wear: Components to develop Wear apps.

That’s all about Android Jetpack !!

Conclusion

In this article, We understood about the Android Jetpack Concepts. We learned the below Android Jetpack details:

  • What is Android Jetpack?
  • Why should we use ?
  • What Problem solves by Android Jetpack?
  • Types of Components in Android Jetpack
  • Components list and usage in Android Jetpack

Thanks for reading ! I hope you enjoyed and learned about the Android Jetpack Concepts. Reading is one thing, but the only way to master it is to do it yourself.

Please follow and subscribe us on this blog and and support us in any way possible. Also like and share the article with others for spread valuable knowledge.

If you have any comments, questions, or think I missed something, feel free to leave them below in the comment box.

Thanks again Reading. HAPPY READING!!???

Exit mobile version