- JetpackCompose.app Dispatch
- Posts
- JetpackCompose.app's Dispatch Issue #27
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 needandroid createโ generate projects from official templatesandroid emulator / android runโ manage virtual devices and deployandroid 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 โ
sharedLogicCompose 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:
Allocates a new
IntRangeobjectCalls
iterator()on it, allocating anIntProgressionIteratorIterates 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? |
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