JetpackCompose.app's Dispatch Issue #27

๐Ÿ’Œ In todayโ€™s Dispatch: Android agents arrive ๐Ÿ› ๏ธ Compose 1.11 stacks up ๐ŸŽจ KMP gets cleaner ๐Ÿงฑ Pulsar brings the buzz ๐Ÿช„

Release white-label apps with a single click

Shipping white-label apps used to mean repeating the same steps and signing in and out of Google Play Console dozens of times per release. With Runway, ship everything in one place, just once.

GM Friends. This is JetpackCompose.appโ€™s Dispatch. The #1 Doctor recommended source for your weekly Android knowledge.

This is Issue # 27 and boy do we have a lot in store for you.

๐Ÿฅ‚ Tipsy Tip

The Android Agent Tooling Triumvirate

Google just dropped something that's going to quietly reshape how a lot of us work: a unified set of agentic tooling for Android development that works with any agent โ€” Gemini, Claude Code, Codex, Antigravity, whatever you're into.

There are three pieces, and they're all worth knowing about:

Android CLI ๐Ÿ› ๏ธ

A real, official, regularly-updated CLI for Android development. Commands like:

  • android sdk install โ€” install only the components you need

  • android create โ€” generate projects from official templates

  • android emulator / android run โ€” manage virtual devices and deploy

  • android update โ€” keep your tooling fresh

Google claims it reduces LLM token usage by 70% and makes agent-driven tasks 3x faster versus letting an agent flail with raw shell commands. The underlying idea is solid: give the agent a constrained, predictable interface instead of letting it find / -name "*.gradle" 2>/dev/null its way through your filesystem. ๐Ÿ˜ฌ

Android Knowledge Base ๐Ÿง 

This is the piece I'm most excited about. Most LLMs have a training cutoff that's months (sometimes a year+) old. That's why your agent keeps suggesting Navigation 2 APIs when you're on Navigation 3.

The Android Knowledge Base gives agents two new tools:

  • search_android_docs โ€” searches authoritative Android docs (developer.android.com, Firebase, Kotlin docs, Google Developers)

  • fetch_android_docs โ€” pulls full document content

It's already built into Gemini in Android Studio. And via Android CLI's android docs, any agent can use it. So when you're prompting Claude Code in your terminal, you can say "Upgrade navigation to Navigation 3. Refer to Android documentation for guidance" and it'll actually go fetch the latest official guidance instead of hallucinating from 2023 training data.

Android Skills ๐Ÿ“š

Modular markdown instruction sets (SKILL.md files) that auto-trigger based on your prompt. These cover the gnarly workflows where LLMs typically suggest outdated patterns:

  • Navigation 3 setup and migration

  • Edge-to-edge implementation

  • AGP 9 migration

  • XML โ†’ Compose migration

  • R8 config analysis

Browse and install via android skills. These can sit alongside your own CLAUDE.md or AGENTS.md โ€” they're additive.

Survived an Android production crash?

๐Ÿซก bitdrift's Beyond the Noise is where mobile engineers tell the stories that never made the postmortem: crashes, ANRs, "how did this ship?" moments at scale, and AI's impact on how we build. With guests like Jesse Wilson, Kelsey Hightower, and many more. ๐ŸŽง

Look at this code from the Kotlin stdlib:

public actual fun CharSequence.isBlank(): Boolean =
    length == 0 || indices.all { this[it].isWhitespace() }

Beautiful one-liner, right? Clean, idiomatic Kotlin written by the developers of the Kotlin language itself. What could be wrong about it?

Hint: Itโ€™s not a correctness bug

You will find the answer in a section below โฌ‡๏ธ

๐Ÿคฟ Deep Dive

Compose 1.11 Is Stacked โ€” Here's What Actually Matters

Every Compose release lately has been chunky, but the April '26 release (Compose 1.11) is genuinely one of the more substantive one. Here are a few features that stand out for me.

๐Ÿงช Coroutine execution in tests

This is THE change that's going to trip up half the codebases out there. The v2 testing APIs are now the default, and v1 is deprecated.

Why does this matter? Previously, Compose tests used UnconfinedTestDispatcher, which executed coroutines immediately. That made tests feel synchronous and "just work" โ€” but it also masked a LOT of race conditions that absolutely existed in production.

The new default is StandardTestDispatcher. Coroutines launched in your tests now queue up and only execute when you advance the virtual clock. Translation: half your "passing" tests are about to reveal they were relying on a happy accident. ๐Ÿ˜…

If you're running a large codebase, do yourself a favor: read the migration guide before you bump the BOM. Otherwise your CI is going to look like a Christmas tree, and not in the fun way ๐ŸŽ„

๐Ÿ–Œ๏ธ Styles API โ€” modifiers, but make them declarative

This is super interesting to me because I spent a few years in a previous role on the Design Systems team at Airbnb and we missed the ability to do something like this.

With the new (experimental) Style API lets you customize components via a state-aware property block instead of stacking modifiers:

Button(
    onClick = { /* ... */ },
    style = {
        background((listOf(lightPurple, lightBlue))
        width(75.dp)
        height(50.dp)
        externalPadding(16.dp)
        pressed {
            background(listOf(Color.Magenta, Color.Red))
        }
    }
) { Text("Login") }

That pressed { } block? Chef's kiss. ๐ŸคŒ 

No more InteractionSource gymnastics for the 90% case. Google is reporting meaningful performance benefits here too, because styles can be applied without going through the full modifier chain reconciliation. Material components will eventually adopt Styles once the API stabilizes.

๐ŸŽจ Grid and FlexBox โ€” finally, real layout primitives

These are experimental but they're a BIG deal. We've been bending Row/Column/LazyVerticalGrid to do things they were never quite designed for. Now we get:

Grid โ€” actual 2D layouts with tracks, gaps, cells, percentages, intrinsic sizes, and "Fr" units. If you've ever written CSS Grid, you know exactly why this is exciting.

@OptIn(ExperimentalGridApi::class)
@Composable
fun GridExample() {
    Grid(
        config = {
            repeat(4) { column(0.25f) }
            repeat(2) { row(0.5f) }
            gap(16.dp)
        }
    ) {
        Card1(modifier = Modifier.gridItem(rowSpan = 2))
        Card2(modifier = Modifier.gridItem(columnSpan = 3))
        Card3(modifier = Modifier.gridItem(columnSpan = 2))
        Card4()
    }
}

๐ŸŽฌ Preview wrappers

Custom previews with @PreviewWrapper finally let you stop wrapping every preview in your theme manually:

class ThemeWrapper : PreviewWrapper {
    @Composable
    override fun Wrap(content: @Composable (() -> Unit)) {
        JetsnackTheme { content() }
    }
}

@PreviewWrapperProvider(ThemeWrapper::class)
@Preview
@Composable
private fun ButtonPreview() {
    Button(onClick = {}) { Text("Demo") }
}

๐Ÿ˜ This has been such a Small quality-of-life win, but multiplied across hundreds of previews? BIG DEAL.

Now, wouldnโ€™t it be nice if every preview didnโ€™t need that extra annotation? I think so too โ€” I should add native support for this in Showkase. If you are looking for open source projects to contribute to, hit me up!

๐Ÿค” Interesting tid-bits

KMP finally gets a saner default structure ๐Ÿงฑ

JetBrains is changing the default Kotlin Multiplatform project structure, and honestlyโ€ฆ this feels like one of those โ€œboring but extremely importantโ€ changes.

The old world often had a composeApp module doing multiple jobs:

  • shared KMP library

  • Android app entry point

  • desktop app bits

  • web app bits

  • Compose Multiplatform UI

  • platform packaging config

  • probably your hopes, dreams, and one cursed Gradle workaround from 2023

The new default is much cleaner:

shared/
androidApp/
desktopApp/
webApp/
iosApp/

The big idea: shared code lives in a shared library module, runnable apps live in app modules.

This matters because Android Gradle Plugin 9.0 no longer supports applying the Android application plugin inside a multiplatform module. So for Android-targeting KMP projects, itโ€™s also future-proofing.

If youโ€™re using native UI on some platforms, the structure can split into:

sharedLogic/
sharedUI/

Which I love because it makes the decision obvious:

  • business rules, models, validation โ†’ sharedLogic

  • Compose Multiplatform UI โ†’ sharedUI

๐Ÿ”ฆ Community Spotlight

Friends at Software Mansion just open-sourced Pulsar, and it's honestly a most delightful library.

Pulsar is a haptics library with 150+ reusable haptic patterns for Android, iOS, React Native, Flutter, and Kotlin Multiplatform. Tere's a companion app that lets you connect your phone to their web playground and physically feel each preset before you wire it into your app.

Think about how absurdly good this DX is. Haptics are inherently impossible to design without a device โ€” you can't preview them in Figma, you can't audition them via headphones (well, you can sort of, via audio approximation), and historically the only way to evaluate one was to write code, compile, deploy, feel, repeat. Pulsar collapses that loop to seconds.

The presets cover everything from subtle "tick" feedback for selection to elaborate accelerometer-driven sequences.

If you've been on the fence about adding haptics to your app, this is your no-excuses moment ๐Ÿช„

Android OG Romain Guy was poking around the Jetpack Compose runtime and stumbled into something interesting.

public actual fun CharSequence.isBlank(): Boolean =
    length == 0 || indices.all { this[it].isWhitespace() }

That innocent-looking indices.all { } does NOT compile to a simple for loop. Because indices returns an IntRange and all { } is a generic extension on Iterable, the compiler:

  1. Allocates a new IntRange object

  2. Calls iterator() on it, allocating an IntProgressionIterator

  3. Iterates via the generic interface, with all the indirection that implies

For a list of 1,000 strings, the stdlib version allocates 2,001 objects and takes ~38,000ns. A simple manual for loop allocates zero and runs in ~15,000ns โ€” 60% faster.

private inline fun CharSequence.fastIsBlank(): Boolean {
    for (i in 0..<length) {
        if (!this[i].isWhitespace()) return false
    }
    return true
}

You can push it further by skipping the redundant Character.isSpaceChar() check that Char.isWhitespace() makes under the hood:

private inline fun CharSequence.fastIsBlank(): Boolean {
    for (i in 0..<length) {
        val c = this[i]
        if (!Character.isWhitespace(c) &&
            c != '\u00a0' && c != '\u2007' && c != '\u202f') {
            return false
        }
    }
    return true
}

That gets you to ~13,500ns โ€” 65% faster than stdlib, with zero allocations.

The lesson: This doesn't matter when you're validating a form field on a button click. It matters enormously when you're inside the Compose runtime processing strings on every recomposition, or parsing a large document. Innocent-looking one-liners can have outsized perf impact in hot paths. The key detail is hot paths โ€” for most, this is a nice-to-know. Please donโ€™t find and replace all usages of isBlank in your codebase ๐Ÿ˜… 

๐Ÿฆ„ How you can help?

If you enjoy reading this newsletter and would like to keep Dispatch free, here are two quick ways to help:

๐Ÿ‘‰๐Ÿป Chip in via Github Sponsors. If your company offers an education budget, contributing a โ€œcoffeeโ€ to keep this newsletter going will certainly qualify. Youโ€™ll get an instant receipt for reimbursement, and Iโ€™ll keep Dispatch running โ˜•๏ธ

๐Ÿ‘‰๐ŸปSpread the word. Tweet, Bluesky, toot, Slack, or carrierโ€‘pigeon this issue to someone who loves Android. Each new subscriber pushes Dispatch closer to sustainability.

๐Ÿ‘‚ Let me hear it!

What did you think of this email?

Login or Subscribe to participate in polls.

On that note, hereโ€™s hoping that your bugs are minor and your compilations are error free,

โ€”

Engineering Leader @ Databricks | Google Developer Expert for Android | ex-Airbnb, Snap, Spotify, Deloitte

Vinay Gaba is a Google Developer Expert for Android and serves as an Engineering Leader at Databricks, where he supports the AI Devtools team. His team's mission is to enhance developer productivity through cutting-edge tools leveraging LLMs and Generative AI. Vinay has deep expertise in AI Tooling, Android, UI Infrastructure, Developer Tooling, Design Systems and Figma Plugins. Prior to Databricks, he worked at Airbnb, Snapchat, Spotify, and Deloitte. Vinay holds a Master's degree in Computer Science from Columbia University.

Reply

or to participate.