JetpackCompose.app's Dispatch Issue #8

💌 In today's issue, we discuss new creative 💰 vesting schedules in tech, an amazing 10 year old engineer, a public service announcement and why onGloballyPositioned is feared

Can a mobile team make their releases boring?

Squarespace’s Unfold team did! Read what their non-eventful mobile releases are like and how they came about, dig into how eventful they used to be, and hear why having uneventful releases is a superpower.

GM Friends. This is JetpackCompose.app’s Dispatch. The Android newsletter that collects ingredients and provides recipes, so you can get to cooking 🧑‍🍳

This is Issue #8 and you might be thinking, "Did this newsletter get lost in the matrix?" Well, not quite! You might be wondering why Issue #8 took a scenic six-week route to your inbox? Let’s address that first!

📣 Personal Update

The honest answer to that question is.… LIFE.

Being a parent is like running a marathon where the finish line keeps moving—and someone keeps handing you more coffee. Combine that with my main job, side projects, open-source, and crafting this very newsletter, and suddenly I've got more jobs than an overworked CPU thread.

Life's intensity ebbs and flows, and recently, it's been in overdrive.

I was busy bee-ing the bee-keeper to my bees 😅

I also realized I haven't spilled the beans about what I've been up to at Airbnb over the past year. So here are a few tech buzzwords to give y’all a clue: Generative AI/LLM's, IDE Plugins, Figma Plugins, UI Tools 😉 More to follow on this topic in a future edition of the newsletter, but it’s been quite a lot of fun leading a team that’s working on the cutting edge of how software is going to be built in this new era of software engineering.

In the past couple of weeks, I've focused on automating some of the repetitive tasks in my newsletter publishing process. For instance, email delivery can be affected by the size of the content, so I needed to optimize every image included in the emails. These logistical tasks can feel like barriers to getting the real content to you. By scripting and automating these processes, I can spend less time on the behind-the-scenes work and more time on the interesting stuff ✌🏻

Lastly, I've decided not to let the length of the newsletter hold me back anymore. I want to return to a bi-weekly schedule, and I've realized that not every edition needs to be a lengthy essay. By focusing on sharing more frequently—even if some issues are shorter—I can keep you updated regularly without getting bogged down by the pressure to make each one extensive. I'm excited to get back to my originally intended programming!

🥂 Tipsy Tip

Have you ever had a screen where you start with the status bar visible but later hide it for a full-screen immersive experience? Maybe you're building a game or a video player, and you have some UI elements positioned relative to the status bar.

You've probably used Modifier.windowInsetsPadding(WindowInsets.statusBars) to offset your UI elements, but then when the status bar disappears, poof, your padding goes with it, and your UI jumps around like it's had too much coffee. ☕️😵

The Solution? Use the statusBarsIgnoringVisibility WindowInset instead!

Modifier.windowInsetsPadding(
    WindowInsets.statusBarsIgnoringVisibility
)

This window inset ensures that your padding is calculated based on the status bar's height, regardless of its visibility. Your UI elements stay put, and your users stay happy.

Why This Works: The statusBarsIgnoringVisibility insets give you the raw size of the status bar area, even when it's hidden. This way, you can maintain consistent layout spacing without unexpected shifts when the system UI changes visibility.

😆 Dev Delight

OVER.MY.DEAD.BODY

Gradle builds are temporary. Deprecating APIs are permanent 😬

📢 Public Service Announcement

 If you're upgrading your compileSdk to 35 (Android 15), you might be in for a surprise. It turns out that using Kotlin's list.removeFirst() or list.removeLast() functions can cause your app to crash spectacularly. 😱

Wait, what? Let's unpack this.

In Kotlin, List is mapped to MutableList, and with Android 15 introducing new List.removeFirst() and List.removeLast() APIs, the Kotlin compiler gets a bit confused. It starts resolving calls to list.removeFirst() to the new platform APIs instead of the Kotlin standard library extensions.

So, if you recompile your app with compileSdk set to 35 and minSdk lower than 35, and then run it on Android 14 or lower, you'll be greeted with a lovely runtime error:

java.lang.NoSuchMethodError: No virtual method removeFirst()Ljava/lang/Object; in class Ljava/util/ArrayList;

Ouch! 😬

Use the NewApi lint option in the Android Gradle Plugin to catch these sneaky new API usages.

./gradlew lint

The Fix? The removeFirst() and removeLast() function calls can be replaced with removeAt(0) and removeAt(list.lastIndex) respectively in Kotlin. Starting Android Studio Ladybug | 2024.1.3, it also provides a quick fix option for these errors.

🤔 Interesting tid-bits

The Great Vesting Schedule Shuffle 💰

While this isn't strictly Android development, it's something that affects many of us in the tech industry. Have you noticed companies getting creative with their equity vesting schedules lately?

Amazon's well-known for their quirky 5/15/40/40 vesting schedule, but Google, Uber, Coupang, ByteDance, and Stripe are joining the party with their own unique twists. It's like they're playing a numbers game, and we're all just trying to keep up! 🎲

Company

Vesting Schedule

Google

38/32/20/10 over 4 years

Stripe

Now offers a 1 year grant

Uber

35/30/20/15 over 4 years

Coupang

10/10/40/40 over 4 years

ByteDance

20/25/25/30 over 4 years

Pinterest

50/33/17 over 3 years

DoorDash

40/30/20/10 over 4 years

Mercury

16.67 every year over 6 yrs

What's the deal? Companies are adjusting vesting schedules to better align with market conditions, retain talent, and manage compensation costs. For us developers, it means being extra vigilant when evaluating job offers. That tantalizing first-year grant might look great, but what's happening in years two, three, and beyond?

Why do Compose APIs use Present Tense Callbacks?

Ever wondered why callback lambdas in Compose are named in the present tense, like onClick instead of onClicked? Is it just a quirky stylistic choice? Let's delve into the rationale.

The reasoning is all about semantics. When you see onClick, it's akin to saying "on click event," suggesting that this function is called when a click is happening, not after it has happened. Using the present tense implies immediacy and aligns with the idea that the callback might influence the outcome.

For instance, in a TextField, the onValueChange callback doesn't just inform you that the value has changed—it gives you the opportunity to decide whether the value should change. If it were named onValueChanged, it would suggest that the change has already occurred, which isn't necessarily accurate.

As you can tell, the Compose team put in a lot of thought in coming up with the right APIs and this is one fun detail that I uncovered in some old Slack conversations ✌🏻 So the next time you write a callback in Compose, remember: Present tense keeps you in the present moment 🧘‍♂️

The performance implications of onGloballyPositioned

Performance is king, and when it comes to Compose modifiers, choosing the right one can make a meaningful difference. After all, the Modifier system revamp was one of the very first large improvements Google invested in to improve the performance of Jetpack Compose. Let's talk about Modifier.onGloballyPositioned in particular.

While onGloballyPositioned is incredibly powerful, it comes with a performance cost. Most of us know this already, however, most don’t know why it has such a bad rep.

This modifier gets called whenever any node in the UI tree is placed, which can be quite expensive 😱 It's similar to using ViewTreeObserver. OnGlobalLayoutListener in the old View system—powerful but potentially heavy.

Enter Modifier.onPlaced

If you only need to know when your Composable has been placed by its parent, onPlaced is your friend. It focuses on the node itself and won't trigger when ancestor nodes are placed. This can lead to significant performance improvements, especially in complex UIs. However, there might be valid use cases where you might need to know the position of the node relative to the screen and the ancestor might’ve placed again. In those cases, this modifier might not help so it’s important to know what you are working with and it’s limitations.

When to Use Which:

  • Use onGloballyPositioned when you need to know the global position in the window or screen coordinates, perhaps for creating overlays or implementing drag-and-drop features across different parts of the UI.

  • Use onPlaced when you're only interested in your Composable's position relative to its parent, and you want to avoid the overhead of monitoring the entire UI tree.

🎥 Media Player

One of my favorite videos this year was watching this 10 year old girl use the Cursor IDE to make a fun little website using AI. This is such a wholesome video but more importantly highlights how LLMs are removing barriers to entry in becoming software engineers. If you are an AI pessimist, watch this video and change your mind in real time because there’s no way you can watch it without feeling inspired about the future of programming and the role AI is going to play in enabling more people to build their ideas.

🦄 How you can help?

👉🏻 If you enjoyed this newsletter, I'd greatly appreciate it if you could share it with your peers and friends. Your support helps me reach more Android developers and grow our community. Writing this newsletter takes time and effort, and it truly makes my day when I see it shared within your network. Consider tweeting about it or sharing it in your team's Slack workspace. Thank you!

👉🏻 If you find this newsletter or any of my other free websites or open source contributions useful, consider supporting my work 🙏🏻

👉🏻 Get my Maker OS Notion Template that I use to run my life on Notion. If you are into productivity, you will appreciate it! Here’s a quick preview of what it looks like—

👂 Let me hear it!

What’d you think of this email? Tap your choice below and lemme hear it 👇

On that note, here’s hoping that your bugs are minor and your compilations are error free,

Reply

or to participate.