Android

Bundles in libs.versions.toml

Say Goodbye to Repetition: How Bundles in libs.versions.toml Simplify Android Projects

If you’re tired of repeating the same dependencies across different modules in your Android project, you’re not alone. Managing dependencies manually is error-prone, messy, and not scalable. Fortunately, Bundles in libs.versions.toml offer a clean and modern solution that saves time and reduces duplication. Let’s break it down, step by step, in a simple way. What...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
Proto DataStore

Proto DataStore in Android: How to Store Complex Objects with Protocol Buffers

Managing data on Android has evolved significantly over the years. From SharedPreferences to Room, we’ve seen the full spectrum. But when it comes to storing structured, complex data in a lightweight and efficient way, Proto DataStore steps in as a game-changer.

In this blog, we’ll walk through Proto DataStore, how it works under the hood, and how to use it with Protocol Buffers to store complex objects. We’ll also look at how it stacks up against the older SharedPreferences and why it’s the better modern choice.

Let’s break it down step by step.

What is Proto DataStore?

Proto DataStore is a Jetpack library from Google that helps you store typed objects persistently using Protocol Buffers (protobuf), a fast and efficient serialization format.

It’s:

  • Type-safe
  • Asynchronous
  • Corruption-resistant
  • Better than SharedPreferences

Unlike Preferences DataStore, which stores data in key-value pairs (similar to SharedPreferences), Proto DataStore is ideal for storing structured data models.

Why Use Proto DataStore?

Here’s why developers love Proto DataStore:

  • Strong typing — Your data models are generated and compiled, reducing runtime errors.
  • Speed — Protocol Buffers are faster and more compact than JSON or XML.
  • Safe and robust — Built-in corruption handling and data migration support.
  • Asynchronous API — Uses Kotlin coroutines and Flow, keeping your UI smooth.

Store Complex Objects with Proto DataStore

Let’s go hands-on. Suppose you want to save a user profile with fields like name, email, age, and preferences.

Step 1: Add the Dependencies

Add these to your build.gradle (app-level):

Kotlin
dependencies {
    implementation "androidx.datastore:datastore:1.1.0"
    implementation "androidx.datastore:datastore-core:1.1.0"
    implementation "com.google.protobuf:protobuf-javalite:3.25.1"
}

In your build.gradle (project-level), enable Protobuf:

Kotlin
protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.25.1"
    }

    generateProtoTasks {
        all().each { task ->
            task.builtins {
                java { }
            }
        }
    }
}

Also apply plugins at the top:

Kotlin
plugins {
    id 'com.google.protobuf' version '0.9.4'
    id 'kotlin-kapt'
}

Step 2: Define Your .proto File

Create a file named user.proto inside src/main/proto/:

Kotlin
syntax = "proto3";

option java_package = "com.softaai.datastore";
option java_multiple_files = true;

message UserProfile {
  string name = 1;
  string email = 2;
  int32 age = 3;
  bool isDarkMode = 4;
}

This defines a structured data model for the user profile.

Step 3: Create the Serializer

Create a Kotlin class that implements Serializer<UserProfile>:

Kotlin
object UserProfileSerializer : Serializer<UserProfile> {
    override val defaultValue: UserProfile = UserProfile.getDefaultInstance()

    override suspend fun readFrom(input: InputStream): UserProfile {
        return UserProfile.parseFrom(input)
    }

    override suspend fun writeTo(t: UserProfile, output: OutputStream) {
        t.writeTo(output)
    }
}

This handles how the data is read and written to disk using protobuf.

Step 4: Initialize the Proto DataStore

Create a DataStore instance in your repository or a singleton:

Kotlin
val Context.userProfileDataStore: DataStore<UserProfile> by dataStore(
    fileName = "user_profile.pb",
    serializer = UserProfileSerializer
)

Now you can access this instance using context.userProfileDataStore.

Step 5: Read and Write Data

Here’s how you read the stored profile using Kotlin Flow:

Kotlin
val userProfileFlow: Flow<UserProfile> = context.userProfileDataStore.data

To update the profile:

Kotlin
suspend fun updateUserProfile(context: Context) {
    context.userProfileDataStore.updateData { currentProfile ->
        currentProfile.toBuilder()
            .setName("Amol Pawar")
            .setEmail("[email protected]")
            .setAge(28)
            .setIsDarkMode(true)
            .build()
    }
}

Easy, clean, and fully type-safe.

Bonus: Handling Corruption and Migration

Handle Corruption Gracefully

You can customize the corruption handler if needed:

Kotlin
val Context.safeUserProfileStore: DataStore<UserProfile> by dataStore(
    fileName = "user_profile.pb",
    serializer = UserProfileSerializer,
    corruptionHandler = ReplaceFileCorruptionHandler {
        UserProfile.getDefaultInstance()
    }
)

Migrate from SharedPreferences

If you’re switching from SharedPreferences:

Kotlin
val Context.migratedUserProfileStore: DataStore<UserProfile> by dataStore(
    fileName = "user_profile.pb",
    serializer = UserProfileSerializer,
    produceMigrations = { context ->
        listOf(SharedPreferencesMigration(context, "old_prefs_name"))
    }
)

When to Use Proto DataStore

Use Proto DataStore when:

  • You need to persist complex, structured data.
  • You care about performance and file size.
  • You want a modern, coroutine-based data solution.

Avoid it for relational data (instead use Room) or for simple flags (Preferences DataStore may suffice).

Conclusion

Proto DataStore is the future-forward way to store structured data in Android apps. With Protocol Buffers at its core, it combines speed, safety, and type-safety into one clean package.

Whether you’re building a user profile system, app settings, or configuration storage, Proto DataStore helps you stay efficient and future-ready.

TL;DR

Q: What is Proto DataStore in Android?
 A: Proto DataStore is a modern Jetpack library that uses Protocol Buffers to store structured, type-safe data asynchronously and persistently.

Q: How do I store complex objects using Proto DataStore?
 A: Define a .proto schema, set up a serializer, initialize the DataStore, and read/write using Flow and coroutines.

Q: Why is Proto DataStore better than SharedPreferences?
 A: It’s type-safe, faster, handles corruption, and integrates with Kotlin coroutines.

Jetpack DataStore in Android

Mastering Jetpack DataStore in Android: The Modern Replacement for SharedPreferences

If you’re still using SharedPreferences in your Android app, it’s time to move forward. Google introduced Jetpack DataStore as a modern, efficient, and fully asynchronous solution for storing key-value pairs and typed objects. In this blog, we’ll break down what Jetpack DataStore is, why it’s better than SharedPreferences, and how you can use it effectively...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
Unit Testing in Kotlin

How to Do Unit Testing in Kotlin Like a Google Engineer

Unit testing in Kotlin isn’t just about making sure your code works. It’s about writing tests that prove your code works, stays reliable over time, and catches bugs before they hit production. Google engineers treat testing as a core development skill, not an afterthought. And you can do the same. In this guide, we’ll break...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
How to Handle Room Database Migrations Like a Pro

How to Handle Room Database Migrations Like a Pro: Avoiding Data Loss

Room is one of the most popular persistence libraries for Android developers. It abstracts away a lot of boilerplate and gives us an easy way to work with SQLite. But when your app evolves and your database schema changes, you need to handle Room Database Migrations properly — or you risk losing your users’ data. This guide...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
Why Side-Effect APIs Matter in Jetpack Compose

Why Side-Effect APIs Matter in Jetpack Compose — And How to Use Them the Right Way

Jetpack Compose has completely changed how we build Android UIs. With its declarative approach, you just describe what your UI should look like, and Compose takes care of the rest. But here’s the thing: your app isn’t only about drawing screens. There are things like showing a toast, requesting a permission, or launching a background...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
Demystifying SideEffect, LaunchedEffect & DisposableEffect in Jetpack Compose

Demystifying SideEffect, LaunchedEffect & DisposableEffect in Jetpack Compose

Jetpack Compose, Android’s modern UI toolkit, introduces a declarative approach to building user interfaces. With this shift comes a new way of thinking about side effects — operations that interact with the outside world or perform actions outside the scope of a composable function. Understanding how to manage these side effects properly is crucial to building reliable, efficient, and reactive Compose applications. 

In this article, we’ll dive into three key APIs provided by Compose for handling side effects: SideEffect, LaunchedEffect, and DisposableEffect. Each serves a distinct purpose and understanding their differences can help you write cleaner, more predictable UI code.

What Are Side Effects in Jetpack Compose?

In Jetpack Compose, a side effect is any change that happens outside the scope of a composable function. This might include updating a database, logging analytics, showing a toast, or triggering a network call. Because composable functions can be re-executed (recomposed) frequently and unpredictably — whenever state or parameters change — running side-effect code directly inside them can lead to bugs or performance issues, such as duplicate network requests or inconsistent UI states.

Why Do We Need Side-Effect APIs in Jetpack Compose?

The declarative paradigm means you describe what the UI should look like, and Compose decides how and when to update it. However, this also means you can’t control exactly when your composable functions run. If you place side-effect code (like a network call) directly in a composable, it might run multiple times — once for every recomposition — which is usually not what you want.

Side-Effect APIs in Jetpack Compose are designed to solve this problem. They provide safe, predictable ways to perform actions that reach outside the Compose runtime, such as:

  • Triggering one-time operations
  • Cleaning up resources
  • Synchronizing Compose state with external systems

Key Side-Effect APIs in Jetpack Compose

Let’s explore the most commonly used Side-Effect APIs in Jetpack Compose, when to use each, and see them with simple code examples.

1. SideEffect

What it does:

Runs code after every successful recomposition of the parent composable.

When to use:

  • For actions that should happen every time the UI updates, like logging or updating analytics.
  • When you need to synchronize Compose state with an external system, but not for heavy or asynchronous operations.

Example: Logging Analytics on Recomposition

Kotlin
@Composable
fun ExampleSideEffect(name: String) {
    Text("Hello, $name")
    SideEffect {
        Log.d("ExampleSideEffect", "Composed with name: $name")
    }
}

Here, every time the name parameter changes and ExampleSideEffect recomposes, the log statement runs—perfect for analytics or debug logging.

2. LaunchedEffect

What it does:

Launches a coroutine tied to the lifecycle of the composable. Runs only when the specified key(s) change.

When to use:

  • For one-off or asynchronous operations, like fetching data from a network or starting animations.
  • When you want to avoid running code on every recomposition.

Example: Fetching Data Once

Kotlin
@Composable
fun FetchDataScreen(userId: String) {
    var data by remember { mutableStateOf<String?>(null) }

    LaunchedEffect(userId) {
        data = fetchDataFromNetwork(userId)
    }
    Text(text = data ?: "Loading...")
}

Here,
LaunchedEffect ensures the network call runs only when userId changes—not on every recomposition—preventing duplicate requests and wasted resources.

3. DisposableEffect

What it does:

Performs setup and cleanup logic tied to the lifecycle of the composable. Runs setup when the key(s) change, and cleanup when the composable leaves the composition.

When to use:

  • For managing resources like listeners, callbacks, or broadcast receivers that need explicit teardown.
  • When you want to perform cleanup when a composable is removed from the UI tree.

Example: Registering and Unregistering a Listener

Kotlin
@Composable
fun LifecycleAwareComponent() {
    val lifecycleOwner = LocalLifecycleOwner.current

    DisposableEffect(lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event ->
            if (event == Lifecycle.Event.ON_RESUME) {
                // Do something when resumed
            }
        }
        lifecycleOwner.lifecycle.addObserver(observer)
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }
}

This ensures the observer is added when the composable enters the composition and removed when it leaves, preventing memory leaks.

Common Mistakes and How to Avoid Them

  • Running Expensive Operations in SideEffect:
    Avoid using SideEffect for network calls or other heavy operations—it runs on every recomposition, which can lead to performance issues and duplicate work.
  • Ignoring Cleanup:
    If you add listeners or callbacks, always use DisposableEffect to remove them when the composable is disposed.
  • Not Using Keys Properly:
    For LaunchedEffect and DisposableEffect, always specify appropriate keys to control when effects should re-run.

Choosing the Right Side-Effect API

Conclusion

Side-Effect APIs in Jetpack Compose are essential for bridging the gap between declarative UI and imperative side effects. By understanding and using SideEffect, LaunchedEffect, and DisposableEffect correctly, you can:

  • Prevent bugs and performance issues caused by unwanted repeated side effects
  • Build responsive, robust, and maintainable Compose apps
  • Ensure your app interacts safely with the outside world

Remember:

  • Use SideEffect for lightweight, repeatable actions after recomposition
  • Use LaunchedEffect for one-time or asynchronous tasks
  • Use DisposableEffect for managing resources with setup and teardown

Mastering these tools will help you write cleaner, more reliable Compose code — and take your Android apps to the next level.

Android Automotive OS Architecture

Android Automotive OS Architecture: A High‑Level Overview

Android Automotive OS is Google’s in‑car operating system that runs directly on a vehicle’s hardware. Not to be confused with Android Auto (a phone projection platform), Android Automotive OS Architecture is a complete software stack, ready for infotainment, driver assistance apps, and full vehicle integration.  Let’s dive into its main layers. Android Automotive Architecture A...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
Fragment add() vs replace()

Fragment add() vs replace(): The Ultimate Guide for Android Developers

If you’ve been working with Android and Fragments, you’ve probably faced this decision: should I use add() or replace() when switching Fragments? It might sound simple — but the difference between FragmentTransaction.add() and FragmentTransaction.replace() can lead to bugs, memory leaks, or even unexpected UI behavior if misunderstood. This guide breaks it down clearly and aligns with modern...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
How Android ViewModel Survives Configuration Changes

How Android ViewModel Survives Configuration Changes and the Role of HashMap Behind the Curtain

In the world of Android development, configuration changes are one of those things that often trip up even seasoned developers. You rotate your device, and suddenly your Activity is destroyed and recreated — poof! That counter you were tracking? Gone. Thankfully, Android ViewModel has your back. In this article, we’ll dive deep into how Android ViewModel survives...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
error: Content is protected !!