- JetpackCompose.app Dispatch
- Posts
- JetpackCompose.app's Dispatch Issue #18
JetpackCompose.app's Dispatch Issue #18
💌 In today’s Dispatch: 📸 Screenshot testing pro-tips ✨ Native shadows in Compose, 🤖 Kotlin powering AI SDKs, 📱 Android 16 adaptivity shift & ⚡️ 6× faster transforms
Runway's 2025 Mobile Release Survey
Responses from 300+ mobile teams show that automation alone isn't solving core challenges: teams that invest significantly in automation still lose 6–10 hours per release to manual busywork and coordination overhead – about the same as teams with less automation in place! See why unresolved friction keeps derailing your releases.
GM Friends. This is JetpackCompose.app’s Dispatch, sliding into your inbox smoother than a perfectly optimized LazyColumn scroll 🚀
This is Issue #18 and we've got some delicious Android nuggets to share with you today 🥔
🤫 Insider Insight
Ever found yourself squinting at your Figma designs, trying to recreate those gorgeous drop shadows and inner shadows in Compose, only to end up with something that looks like it was designed by a caffeinated intern at 3 AM? Well, my friend, those days are officially behind us!
Compose 1.9 alpha just dropped two game-changing modifiers that'll make your designer's heart skip a beat: Modifier.dropShadow()
and Modifier.innerShadow(
).
Here's how ridiculously easy it is to create a beautiful drop shadow:
@Composable
@Preview(showBackground = true)
fun SimpleDropShadowUsage() {
val pinkColor = Color(0xFFe91e63)
val purpleColor = Color(0xFF9c27b0)
Box(Modifier.fillMaxSize()) {
Box(
Modifier
.size(200.dp)
.align(Alignment.Center)
.dropShadow(
RoundedCornerShape(20.dp),
dropShadow = DropShadow(
15.dp,
color = pinkColor,
spread = 10.dp,
alpha = 0.5f
)
)
.background(
purpleColor,
shape = RoundedCornerShape(20.dp)
)
)
}
}
These modifiers are part of the ExperimentalMaterial3ExpressiveApi
, which means Google is serious about making expressive, beautiful UIs easier to build. No more hacky workarounds with custom Canvas drawing or third-party libraries – it's all built right into Compose now.

Bitdrift pulled back the curtain on how they engineered blazing-fast “Session Replays” for Jetpack Compose—swapping fragile reflection for the Semantics API, side-stepping ProGuard breakages, and slashing capture time from ~800 ms to single-digit milliseconds! Read the full story
When this code runs, scrolling the list causes items to lose their expanded state unexpectedly. Why does this happen?
@Composable
fun ItemsList(items: List<String>) {
LazyColumn {
itemsIndexed(items) { index, item ->
var isExpanded by remember { mutableStateOf(false) }
Card(
modifier = Modifier
.fillMaxWidth()
.clickable { isExpanded = !isExpanded }
) {
Column {
Text(text = item)
if (isExpanded) {
Text(
text = "Extended for $item",
)
}
}
}
}
}
}
See the answer in a section below ⬇️

🤔 Interesting tid-bits
The Kotlin Takeover is Real (And Hilarious)
Here's something that made me do a double-take: both OpenAI's and Anthropic's official Java SDKs are written entirely in Kotlin. Let that sink in for a moment. These companies, building SDKs specifically for Java developers, chose Kotlin as their implementation language.
This isn't just a fun fact, it's a testament to Kotlin's incredible interoperability story. When you need to support Java but want the productivity and safety of modern language features, Kotlin is the obvious choice. The generated bytecode is indistinguishable from Java, but the development experience is light-years ahead.

Honestly, this isn’t even newsworthy at all but I didn’t realize that this was a fairly common practice by the library maintainers who have the (mis)fortune of maintaining Java compatible SDKs/libraries.
The Great Android Adaptivity Awakening
If you missed it (or were stuck in sprint meetings), Android 16 is shaking up app adaptivity. Starting with SDK 36, orientation and resizability restrictions will be ignored on large screens so portrait-only apps will rotate, and non-resizable windows go split-screen.

Before you freak, this is a huge win: over 500 million large screen devices await better apps, and users engage 3x more.
The guide breaks down the adaptation process into digestible chunks:
Start with the foundations: Remove orientation locks, support resizability, prevent UI stretching
Implement canonical layouts: List-detail views, navigation rails, supporting panes
Support multiple input methods: Keyboard, mouse, trackpad, stylus
Add differentiating experiences: Tabletop mode, dual-screen layouts
What I love about this approach is that it's not asking you to rebuild your entire app overnight. It's about incremental improvements that compound over time. Start with your most critical user journeys, then expand from there.

😆 Code Corner
Performance optimization in Kotlin often comes down to understanding what the compiler can and can't do for you. Romain Guy recently shared a brilliant example from the Jetpack Compose codebase that perfectly illustrates the power of manual function inlining.
// This matrix is cached and re-used across invocations
val m: Matrix ...
// Reset to identity
m.reset()
// Move to the pivot point
m.translate(-pivotX, -pivotY)
// Apply the user's transforms
m *= Matrix().apply {
translate(translationX, translationY)
rotateX(rotationX)
rotateY(rotationY)
rotateZ(rotationZ)
scale(scaleX, scaleY)
}
m *= Matrix().apply { translate(pivotX, pivotY) }
Readable? Yes. Efficient? Not so much—about 1,062 ARM64 instructions and 39 branches thanks to:
two throw-away matrices,
a full
reset()
you mostly overwrite,generic 4×4 multiplies for data you don’t need.
Romain rewrote it as one inline helper (resetToPivotedTransform) and slashed the cost to 168 instructions, 1 branch—just 15 % of the original work. This is why, it’s valuable to use tools like Kotlin Explorer to peek at generated code and find opportunities in hot code paths.

👩💻 Featured Subscriber
I’m thrilled to sit down with Arnold Noronha, the mind behind Screenshotbot. At Meta, Arnold didn’t just create and open-source the wildly popular screenshot-tests-for-android library—he championed screenshot testing across Facebook’s entire mobile org, turning it into a standard part of every engineer’s workflow. Now he’s taken that obsession with pixel-perfect UIs a step further by creating a platform that lets any team embrace screenshot testing. It’s safe to call him a domain expert and he’s spent the last decade forming opinions about screenshot testing. I’m super excited to bring his insights about screenshot testing to .

Screenshotbot allows you to see the UI changes happening in your codebase with very little effort and works seamlessly with screenshot testing libraries like Paparazzi
Give us the quick elevator pitch for Screenshotbot. Who should use it?
Screenshotbot removes the grunt work so you can run tens-of-thousands of screenshot tests per PR with one-click reviews, automatic storage and zero repo bloat. If your goldens live in Git and your diff PRs make teammates groan, you’re our ideal user.
Why is storing the golden images of your screenshot tests in GitHub problematic?
Binary bloat slows every clone, and you will eventually hit repo size limits. Teams waste time deciding which screens are “worth” testing. Off-loading images to Screenshotbot lets you test everything—light, dark, XXL font, etc without a repo diet.
Where do screenshot tests sit compared with Espresso, Robolectric and unit tests?
Think of them as the visual contract layer: unit tests guard logic, Espresso guards interaction, screenshots guard rendering. Because failures come with a before/after diff, they’re rarely viewed as “red tape”; in fact they often encourage teams to add more tests elsewhere.
What pain points inside Meta/Facebook pushed you to create facebook/screenshot-tests-for-android?
At Facebook every screen could be in dozens of A/B-test states, so visual regressions slipped through all the time. Pair-programming with Kent Beck, I hacked together a way to snapshot UIs, then realized News Feed—pulling code from every team—urgently needed it. Generating thousands of feed snapshots caught bugs we’d never have spotted manually and the practice spread organically across the company.
Most common mistakes teams make with screenshot tests?
Only testing “finished” screens. The fastest-changing surfaces are exactly where regressions hide; add tests early and let the tooling absorb the churn.
Favourite tricks for making screenshot tests deterministic?
- Run them only on CI so you get identical fonts, locales and hardware.
- Mask dynamic regions (timestamps, ads) so they never fail the build.
- Have some analytics to surface your flakiest tests and the exact OS/device that caused them.
- When a failure really is noise, have a way to bypass these failures to unblocks the team without a rebase-fest.
Goes without saying, Screenshotbot makes it easier to have some of these out of the box 😉
How should an engineer convince leadership to adopt screenshot testing?
Unlike a typical library drop-in, screenshots touch every developer so they have to know how to re-record baselines.
Start small: wire Screenshotbot into CI for one team, show how a single click accepts legitimate changes, and share the early wins. Once people see regressions caught and review friction lowered, buy-in comes for free.
Screenshotbot is open-source and also offered as a hosted service. Why both, and what’s the delta?
I’ve been an OSS tragic since high school; the core engine was always going to be free so teams can self-host. The SaaS version adds convenience features (single-tenant storage, team management, analytics) that would drain months of dev time to replicate but the diffing engine is identical.
What new features are you cooking up?
Figma links – jump straight from a failing screenshot to the canonical design.
Flakiness labels – tag a failure as “noise” so DevEx can see the worst offenders.
AI diff summaries – “The CTA button’s blue changed from #1976D2 → #2196F3.”
LLM-ready datasets – use your approved screenshots as ground truth for UI code-gen.

The issue is that remember
without a key doesn't survive item recomposition during scrolling. LazyColumn recycles composables, so the isExpanded
state gets reset when items scroll in and out of view.
Fix it by providing a stable key:
var isExpanded by remember(item) { mutableStateOf(false) }
// or better yet, manage state outside the composable

🦄 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’d you think of this email? Tap your choice below and lemme hear it 👇
🚀🚀🚀🚀🚀 AMAZING-LOVED IT!
🚀🚀🚀 PRETTY GOOD!
On that note, here’s hoping that your bugs are minor and your compilations are error free,
![]() | Tech Lead Manager @ Airbnb | Google Developer Expert for Android | ex-Snap, Spotify, Deloitte Vinay Gaba is a Google Developer Expert for Android and serves as a Tech Lead Manager at Airbnb, where he spearheads the UI Tooling team. His team's mission is to enhance developer productivity through cutting-edge tools leveraging LLMs and Generative AI. Vinay has deep expertise in Android, UI Infrastructure, Developer Tooling, Design Systems and Figma Plugins. Prior to Airbnb, he worked at Snapchat, Spotify, and Deloitte. Vinay holds a Master's degree in Computer Science from Columbia University. |
Reply