Jetpack Compose

CompositionLocal

CompositionLocal Deep Dive: Writing Scalable UI in Jetpack Compose

When building Android apps with Jetpack Compose, you’ll often need to share data across your UI tree. Passing parameters down every Composable quickly becomes messy. That’s where CompositionLocal comes in. Think of CompositionLocal as a smart way to provide values (like theme, locale, or user preferences) to multiple Composables without having to manually thread them...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
Scrollable Sticky Table/Grid UIs in Jetpack Compose

Build Scrollable Sticky Table/Grid UIs in Jetpack Compose Like a Pro

If you’ve ever built dashboards, spreadsheets, or financial apps, you know how important tables are. But a plain table isn’t enough — you often need a Scrollable Sticky Table/Grid where headers stay in place while data scrolls smoothly. In the past, building this in Android XML layouts was painful. With Jetpack Compose, you can achieve it with...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
Jetpack Compose LazyColumn Sticky Header

Jetpack Compose LazyColumn Sticky Header: Complete Implementation Guide

When you’re building long lists in Jetpack Compose, sometimes you need certain sections to stand out and stay visible while scrolling. That’s exactly where Sticky Header comes in. Imagine scrolling through a contacts app — the alphabet letter headers (A, B, C…) stick at the top while you browse through names. Jetpack Compose makes this easy with...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
Sticky Header in Jetpack Compose

How to Create a Sticky Header in Jetpack Compose

If you’ve ever scrolled through a long list in an app and noticed that the section title stays pinned at the top until the next section appears, you’ve seen a sticky header. Sticky headers make lists easier to navigate, especially when content is grouped by category. In this post, we’ll learn step by step how...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
State Hoisting in Jetpack Compose

State Hoisting in Jetpack Compose: Best Practices for Scalable Apps

When building Android apps with Jetpack Compose, state management is one of the most important pieces to get right. If you don’t handle state properly, your UI can become messy, tightly coupled, and hard to scale. That’s where State Hoisting in Jetpack Compose comes in. In this post, we’ll break down what state hoisting is,...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
State Management in Jetpack Compose

Mastering State Management in Jetpack Compose: A Comprehensive Guide

State management is one of the most critical aspects of building dynamic and interactive Android applications. With Jetpack Compose, Android’s modern UI toolkit, managing state becomes more intuitive, but it also introduces new paradigms that developers need to understand. In this blog, we’ll explore state management in Jetpack Compose in detail. We’ll break down essential...

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here
collectAsStateWithLifecycle

Lifecycle-Aware State in Compose: Why collectAsStateWithLifecycle Outperforms collectAsState

Jetpack Compose makes UI state management feel almost magical — you observe a Flow, call collectAsState(), and your composable stays up to date. But here’s the catch: not all flows are equal when it comes to lifecycle awareness. If you’re building Android apps today, you should almost always be reaching for collectAsStateWithLifecycle instead of the collectAsState. Let’s break...

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.

Jetpack Glance Media Playback

A Deep Dive into Using Jetpack Glance for Media Playback in Android Automotive OS

As vehicles evolve into digital experiences, the need for glanceable, fast, and distraction-free interfaces becomes paramount. In Android Automotive OS (AAOS), this demand has led to the emergence of the Jetpack Glance framework — a powerful tool for creating UI surfaces that are lightweight, fast to load, and safe for drivers to interact with.

In this blog post, we’ll explore how Jetpack Glance can be used to build a media playback card for Android Automotive OS. From setting up dependencies to implementing a full-featured glanceable media widget with play/pause/skip functionality — we’ll walk through the full picture with code, context, and best practices.

What is Jetpack Glance?

Jetpack Glance is a declarative UI library designed for building remote user interfaces, including:

  • App widgets (for Android homescreens)
  • Glanceable UIs for wearables (e.g., Tiles)
  • Future-facing vehicle dashboards and clusters in Android Automotive

Think of Glance as the Compose-inspired sibling of RemoteViews, but tailored for rendering quickly, efficiently, and safely on surfaces with strict interaction rules — like a car’s infotainment screen.

Why Use Glance in Android Automotive?

Using Glance in AAOS allows developers to:

  • Create lightweight UIs for media, navigation, or vehicle info
  • Ensure low distraction by adhering to system-level constraints
  • Maintain fast rendering even on constrained hardware
  • Leverage Jetpack Compose-like syntax without full Compose overhead

Key Use Cases in AAOS

Use CaseDescription
Media CardsDisplay now-playing info and basic playback controls
Navigation PreviewsShow turn-by-turn summaries or route cards
Vehicle StatusFuel, tire pressure, battery charge level
Contextual AlertsDoor open, low fuel, safety notifications

Setting Up Jetpack Glance in Your Project

Add Required Dependencies

Update your build.gradle with the latest Glance libraries:

Kotlin
dependencies {
    implementation "androidx.glance:glance:1.0.0"
    implementation "androidx.glance:glance-appwidget:1.0.0"
    implementation "androidx.glance:glance-wear-tiles:1.0.0" // optional
    implementation "androidx.core:core-ktx:1.12.0"
}

Tip: Glance is backward-compatible with Android 12 and above, making it suitable for most AAOS setups.

Creating a Glanceable Media Widget for AAOS

Let’s walk through a full example where we build a media playback widget that can be shown in a center display or cluster (with OEM support).

Define the Glance Widget

Kotlin
class MediaGlanceWidget : GlanceAppWidget() {
    @Composable
    override fun Content() {
        val title = "Song Title"
        val artist = "Artist Name"

        Column(
            modifier = GlanceModifier
                .fillMaxSize()
                .padding(16.dp)
                .background(Color.DarkGray),
            verticalAlignment = Alignment.CenterVertically,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text("Now Playing", style = TextStyle(fontWeight = FontWeight.Bold, color = Color.White))
            Spacer(Modifier.height(8.dp))
            Text(title, style = TextStyle(color = Color.White))
            Text(artist, style = TextStyle(color = Color.LightGray))

            Spacer(Modifier.height(16.dp))
            Row(horizontalAlignment = Alignment.CenterHorizontally) {
                Image(
                    provider = ImageProvider(R.drawable.ic_previous),
                    contentDescription = "Previous",
                    modifier = GlanceModifier.size(32.dp).clickable {
                        actionStartService<MediaControlService>("ACTION_PREVIOUS")
                    }
                )
                Spacer(Modifier.width(16.dp))
                Image(
                    provider = ImageProvider(R.drawable.ic_play),
                    contentDescription = "Play",
                    modifier = GlanceModifier.size(32.dp).clickable {
                        actionStartService<MediaControlService>("ACTION_PLAY_PAUSE")
                    }
                )
                Spacer(Modifier.width(16.dp))
                Image(
                    provider = ImageProvider(R.drawable.ic_next),
                    contentDescription = "Next",
                    modifier = GlanceModifier.size(32.dp).clickable {
                        actionStartService<MediaControlService>("ACTION_NEXT")
                    }
                )
            }
        }
    }
}

Handling Playback Actions: MediaControlService

Since Glance doesn’t support direct onClick behavior like Compose, we use a Service to act on UI interactions.

Kotlin
class MediaControlService : Service() {
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        when (intent?.action) {
            "ACTION_PLAY_PAUSE" -> togglePlayPause()
            "ACTION_NEXT" -> skipToNext()
            "ACTION_PREVIOUS" -> skipToPrevious()
        }
        return START_NOT_STICKY
    }

    private fun togglePlayPause() {
        // Hook into MediaSession or ExoPlayer
    }

    private fun skipToNext() {
        // Forward playback command
    }

    private fun skipToPrevious() {
        // Rewind playback
    }

    override fun onBind(intent: Intent?): IBinder? = null
}

Integrating with AndroidManifest.xml

To register the widget and service:

Kotlin
<receiver
    android:name=".MediaGlanceWidgetReceiver"
    android:exported="true">
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>
    <meta-data
        android:name="android.appwidget.provider"
        android:resource="@xml/media_widget_info" />
</receiver>

<service
    android:name=".MediaControlService"
    android:exported="false" />

Widget Configuration XML

In res/xml/media_widget_info.xml:

Kotlin
<appwidget-provider
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="180dp"
    android:minHeight="100dp"
    android:updatePeriodMillis="60000"
    android:widgetCategory="home_screen" />

Best Practices for Automotive Glance UI

  • Keep UI distraction-optimized
  • Use readable font sizes and sufficient contrast
  • Avoid overloading the interface — 2–3 actions max
  • Make controls large and touch-friendly
  • Always test on real AAOS hardware or emulator

Conclusion

Jetpack Glance is quickly becoming a go-to tool for developers looking to build safe, fast, and flexible UI surfaces across Android form factors. In the automotive space, it shines by helping deliver minimalist, glanceable media controls that respect both performance and safety constraints.

As AAOS continues to evolve, expect more OEM support for Glance in clusters, dashboards, and center displays — especially with the push toward custom car launchers and immersive media experiences

error: Content is protected !!