JetpackCompose.app's Dispatch Issue #15

💌 In today's issue, we talk about the 💸 strange hiring market and salaries, 💣 latest BOM drop, 📈 sorting UUIDs and obviously...AI 👂

Your mobile release “tax” costs more than you realize.

Join mobile leaders from Skyscanner and SoFi on May 22 to measure exactly what you’re losing—and learn the real ROI of improving your mobile release process.

GM Friends. This is JetpackCompose.app’s Dispatch, dropping into your inbox with the kind of fresh perspective that only comes after finally figuring out that obscure bug at 2 AM ☕️

This is Issue # 15 and we’ve got a mega-stack of Android/Compose goodness sizzling on the grill today, so let’s get cookin 👨🏻‍🍳

🕵🏻‍♂️ Insider Insight

Unpacking the Compose April '25 Release

Alright fam, the Compose team just dropped the April '25 Bill of Materials (BOM version 2025.04.01), and with it comes Compose UI & Foundation 1.8. As usual, there's a bunch of goodies, but let's focus on a few things that really caught my eye. You can read the full announcement here, but here’s my take:

1. autoSize Parameter for Text is HERE! 🎉

Yes, you read that right! After years of developers rolling their own solutions (or just giving up and hoping for the best), BasicText now has an autoSize parameter built-in.

Box {
    BasicText(
        text = "Hello World",
        maxLines = 1,
        autoSize = TextAutoSize.StepBased()
    )
}

This lets the text size automatically shrink to fit its container. You can customize the min/max sizes and the step granularity. It's currently in BasicText, with the Material Text overload expected soon.

Hallelujah! No more complex calculations, workarounds or hoping your text doesn't awkwardly wrap or get cut off.

2. Smarter Visibility Tracking with onLayoutRectChanged

Remember onGloballyPositioned? It’s powerful, but often overkill and can be a performance hog, especially inside lazy lists, because it fires constantly. Enter the newest modifier in this circus of life - onLayoutRectChanged.

Modifier.onLayoutRectChanged(
    debounceMillis = 100L, // Optional: Debounce callbacks
    throttleMillis = 50L,  // Optional: Throttle callbacks
    callback = { layoutRect, parentLayoutRect ->
        // layoutRect: Rect of the composable in its parent's coords
        // parentLayoutRect: Rect of the parent in its parent's coords
        // Do something based on visibility/position...
    }
)

This new modifier is designed specifically for tracking a composable's position and size changes relative to its parent, but with built-in debouncing and throttling! This makes it way more efficient for common use cases like impression tracking or triggering animations based on visibility within a LazyColumn. Basically every real app needs visibility tracking so this single modifier is a must-know for everybody that’s working on an app that’s scaled!!!

The official blog post hints that higher-level abstractions built on this are coming in Compose 1.9, which is exciting. So I’d wait to see that this looks like before building anything custom just yet.

3. Automated Accessibility Checks in Tests!

This one is HUGE and one that I’ve been personally been following up about over the years!!

As you can see, I was starting to get impatient after waiting for this for 2+ years 😅

You can now enable automated accessibility checks directly within your Espresso tests using enableAccessibilityChecks.

@get:Rule
val composeTestRule = createAndroidComposeRule<MyActivity>()

@Before
fun setup() {
    composeTestRule.enableAccessibilityChecks() // Magic happens here!
}

@Test
fun myComposable_isAccessible() {
    // Your test logic...
    // Espresso will now automatically run checks from the
    // Accessibility Test Framework (ATF) on view interactions.
}

This leverages the Accessibility Test Framework (ATF) to catch common issues like missing content descriptions, insufficient touch target sizes, and contrast problems during your regular UI tests. Seriously, add this to your test setup today. I’m thinking about adding it to Showkase out-of-the-box so if you are already using it for screenshot testing, it will just take care of making sure all your components also get accessibility verified.

4. AnnotateString now supports HTML bullets

AnnotateString expanded it’s HTML support by also supporting bullets. So now you can do this—

Text(
  AnnotatedString.fromHtml(
    """
    <h1>HTML content</h1>
    <ul>
      <li>Hello,</li>
      <li>World</li>
    </ul>
    """.trimIndent()
  )
)

5. Experimental API Cleanup & Stabilization

The Compose team acknowledged the community's feedback about "experimental fatigue" – that feeling when core features stay marked @Experimental forever. In this release, they've made a significant push to stabilize APIs, reducing the number of experimental annotations in UI and Foundation from 172 down to 70 🙌 

This includes stabilizing FlowRow and FlowColumn. This move towards stability is super welcome and builds confidence in the maturity of the toolkit.

Bonus: A user on Reddit mentioned this was their smoothest Compose update yet, where they saw improvements in Crashlytics metrics and found the new auto text size to be great. (Though they also noted Live Edit/Preview still has its quirks... relatable 😅).

🪖 Comrades, this is overall a solid release focused on practical improvements, performance, testing, and stability. Go update your BOM!

implementation(platform("androidx.compose:compose-bom:2025.04.01"))

🥂 Tipsy Tip

Ever needed sortable unique identifiers? Your homeboy got you covered!

We all use UUIDs (Universally Unique Identifiers) constantly, right? They're great for generating unique IDs for database records, tracking requests, whatever. But they have one annoying limitation: they're basically random, which means you can't easily sort them chronologically based on when they were created. Sure you can always add an extra created_at column and depend on it, but wouldn’t it be nice if your unique identifier also had that property 🤤

Well, meet ULID (Universally Unique Lexicographically Sortable Identifier)!

Think of a ULID as a UUID with a superpower. Like UUIDs, they are 128-bit values, guaranteeing uniqueness (well, practically speaking). But here's the clever part:

  • First 48 bits: Encode a millisecond-precision Unix timestamp.

  • Last 80 bits: Are completely random, ensuring uniqueness even if generated in the same millisecond.

Because the timestamp is baked into the beginning of the ID, you can simply sort ULIDs lexicographically (like regular strings) and boom – they're ordered chronologically! This is incredibly useful for things like:

  • Sorting database records by creation time without needing a separate createdAt timestamp column (or indexing it).

  • Easily finding records created before or after a specific time just by comparing IDs.

  • Time-based pagination using the ID itself.

Most major languages have libraries available for generating ULIDs (just search for "ULID library [your language of choice]").

One Caveat: While ULIDs generated on the same machine are reliably sortable, comparing timestamps across different machines in a distributed system still has the usual clock skew caveats. Don't rely on perfect ordering between IDs generated on separate servers simultaneously.

But for many common Android specific use cases, swapping UUIDs for ULIDs can simplify your life and your database queries. Give it a look!

📈 Hiring Pulse

Recently, I’ve had a lot of conversations with folks across experience levels. One thing that’s common is that it’s a really weird time for everybody. There’s a new AI company raising capital every day and yet a lot of folks feel stuck in their current situation because at least it feels “comfortable” and they know what to expect. The reality is that careers are made in periods of chaos and hyper-growth. If you feel unsure, the best thing you can do yourself is to be “ready” to jump on the opportunity that excites you. The best roles aren’t going to be open for very long so you need to be on your A-game and be super prepared.

System Design rounds tend to be a key differentiator when you are interviewing as they are often proxies of “leveling”. If you’d like to up your game and be ready for any opportunity that comes your way, my friends at Educative offer a fantastic course called “Grokking the Modern System Design Interview” to help you out. They are offering a 56% discount if you use this link to sign up. Check em out, I’ve personally used them in the past.

😆 Dev Delight

🤔 Interesting tid-bits

🤑 AI Skills == More $$$?

Zuhayeer Musa, co-founder of Levels.fyi, shared some striking data showing a significant pay gap between AI/ML specialized software engineers and others, especially at senior levels. We're talking potentially $100k-$200k+ difference for Staff/Principal roles! While Android folks might not all be training models daily, it's a stark reminder of how specialized skills are valued and the importance of keeping our skillsets sharp in this rapidly evolving tech landscape.

I have so many thoughts on this subject as I’ve been leading an AI-first team in my most recent role at Airbnb and have been embracing AI on a daily basis since ~2022. And just to be clear so that I don’t lose credibility, I didn’t give 2 s#*ts about crypto. So I’m not a hype hunter. But this feels/is different and hence has a lot of my attention. I want articulate a lot of my thoughts in a future edition of this newsletter but I’ll leave you with this one quote that I absolutely love. Hopefully you will take the hint on where I stand.

Pessimists avoid risk. Optimists change the world.

🧩 Dagger + KMP = Eventually?

Good news for Dagger/Hilt fans dipping their toes into Kotlin Multiplatform! A Googler confirmed on a GitHub issue that adding KMP support is officially on the roadmap. The bad news? There's no timeline. They're working through hurdles like migrating to XPoet for Kotlin generation, dealing with nullability/visibility/generics, shedding javax/jakarta dependencies, and KSP2 support is also a blocker. So, it's coming... someday. But hey, it's not not coming! That’s gotta mean something right? right???

🔦 Community Spotlight

Meet Pdf-Viewer by Rajat Mittal. What's so special? Most Android PDF viewing libraries weigh in at several megabytes (often 10-16MB+) because they bundle native C/C++ code (like Poppler or MuPDF). Rajat's library achieves the same goal in around 88 KILOBYTES. 🤯

How is this possible? Rajat wrote a fantastic blog post detailing his approach, but here are the highlights of his lightweight strategy:

  • No Native Dependencies: It relies solely on Android's built-in android.graphics.pdf.PdfRenderer (API 21+). This immediately sheds megabytes of native .so files.

  • Minimal HTTP Stack: For loading PDFs from URLs, it uses the standard HttpsURLConnection with coroutines, avoiding heavier dependencies like OkHttp/Retrofit!!

  • Lean UI: Built with standard Android Views (RecyclerView, etc.) without extra layers like DataBinding initially. (Jetpack Compose support is available now too!)

  • Custom Zooming RecyclerView: He built his own PinchZoomRecyclerView to handle gestures precisely without third-party libs.

  • R8/ProGuard Friendly: The design is simple and avoids reflection, making it highly shrinkable.

  • Zero Assets: No bundled fonts or sample files.

Despite the tiny footprint, it packs features like:

  • Loading from URL, local storage, and assets.

  • RAM and Disk caching for smooth performance.

  • Page prefetching.

  • Optional screenshot protection (FLAG_SECURE).

  • Jetpack Compose integration (PdfRendererViewCompose).

If you need PDF viewing capabilities without bloating your app, definitely check out Rajat's library 🌟

 🚀 Keep Dispatch Dispatching

Dispatch lands in your inbox free of charge, but sending emails is a costly affair and Beehiiv (the newsletter provider I use) want their honey. Here’s a behind-the-scenes picture of what my expenses end up looking like as the subscribers continue to grow:

Subscribers

My monthly bill

2500

$69/month

5000

$89/month

10,000

$109/month

I cover the tab thanks to generous early supporters like Runway—but the meter keeps ticking faster than a flaky CI build. If you enjoy this newsletter and would like to keep Dispatch free and my accountant conscious, here are two quick ways to help:

👉🏻 Chip in via Github Sponsors. If your company offers an education budget, this will absolutely qualify. You’ll get an instant receipt for reimbursement, 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—and my dopamine closer to the stratosphere.

Thanks for fueling this experiment in insightful‑yet‑snarky Android content. See you next week!

👂 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,

Reply

or to participate.