- JetpackCompose.app Dispatch
- Posts
- JetpackCompose.app's Dispatch Issue #19
JetpackCompose.app's Dispatch Issue #19
💌 In today’s Dispatch: 💰 Selling Android apps 🛠️ Metro DI takes the crown, 🚨 AndroidView anti‐pattern exposed & 🔐 Telegram’s hash‐coded file bans
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, rolling into your inbox like a perfectly optimized recomposition that skips all the unnecessary work.
This is Issue # 19 and we are covering a ton of interesting content, right from shake up in Dependency Injection land to the hidden world of selling Android apps. Let’s do this!
🤿 Deep Dive
The Great DI Revolution: Metro Takes the Stage as Anvil Bows Out
Hold onto your dependency graphs, folks, because we're witnessing a major shift in the Android dependency injection landscape! 🌋
Last month, Zac Sweers dropped Metro, a brand-new dependency injection framework that's already causing waves big enough to influence a $44 billion company. And in a move that shows the true power of open source innovation, Square just announced that Anvil is moving into maintenance mode as they pivot to adopting Metro for their production apps.
Let me break down why you need to understand this change:
What Makes Metro Special?
Metro isn't trying to reinvent the wheel—it's trying to make all the existing wheels work better together. Drawing heavy inspiration from Dagger, Anvil, and kotlin-inject, it unifies their best features while addressing their pain points:
@DependencyGraph
interface AppGraph {
val httpClient: HttpClient
@Provides
private fun provideFileSystem(): FileSystem {
return FileSystem.SYSTEM
}
}
@Inject
class HttpClient(private val fileSystem: FileSystem)
val graph = createGraph<AppGraph>()
The API feels immediately familiar if you've worked with any modern DI framework, but the magic happens under the hood. Metro is implemented as a pure Kotlin compiler plugin, which means it eliminates both KAPT and KSP from your build toolchain for DI purposes. The performance improvements are substantial—Zac's benchmarks on his CatchUp app showed:
ABI changes: 47% faster builds
Non-ABI changes: 56% faster builds
Clean builds: 25-28% faster across the board
But speed isn't the only win. Because everything runs through a single framework, error messages become dramatically clearer. No more debugging whether an issue stems from Anvil, Dagger, or KAPT—it's all unified.
Why Square Made the Switch?
Square's evaluation process was thorough and pragmatic. They migrated two internal development apps to Metro, implemented backwards-compatible toggles to switch between Dagger+Anvil and Metro, and ran extensive benchmarks. The results convinced them that Metro represents the future of DI on Android.
Here's what really impressed me: Square didn't just adopt Metro—they actively contributed back to it.
The Broader Implications
This move shows how individual developers can create solutions that influence entire ecosystems. Zac built Metro as a personal project, but its technical merits were compelling enough to change the direction of a major company.
The timing is also significant. With Kotlin 2.0 stabilizing and K2 becoming the default, we're in a period where compiler plugins can really shine. Metro takes full advantage of newer Kotlin language features and the improved compiler architecture.
Should You Care?
If you're currently happy with your Dagger + Anvil setup and not hitting performance or complexity walls, there's no rush. But if you're starting a new project, dealing with slow build times, or finding yourself frustrated with DI debugging, Metro deserves serious consideration. It's multiplatform-ready, has excellent IDE integration, and supports advanced features like:
Optional dependencies with default parameters
Top-level function injection (perfect for Compose!)
Assisted injection with
@AssistedFactory
Private providers and member injection
The fact that it's Zac driving this gives me confidence too. His track record with tools like Slack's Circuit and contributions to the Android ecosystem speaks for itself. This isn't a weekend project—it's a thoughtfully designed solution to real problems.
Keep an eye on this space. I have a feeling we're witnessing the beginning of a new era in Android dependency injection.

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
Take a look at this seemingly innocent Compose code that integrates a custom View. Can you spot what's wrong with this approach?
@Composable
fun MyCustomViewComposable() {
val view = remember { MyCustomView(LocalContext.current) }
AndroidView(
factory = { context ->
view.apply {
// Configure the view
setBackgroundColor(Color.RED)
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
200.dpToPx()
)
}
},
update = { view ->
// Update view properties
view.updateSomeProperty()
}
)
}
See the answer in a section below ⬇️

😆 Dev Delight

We have feelings too 🥺

💻 Interesting Tid-bits
Telegram's Cryptic Code: When Obfuscation Meets Security
Sometimes you stumble across a piece of code that makes you do a double-take. A developer recently found this gem in Telegram's open-source repository:
String ext = fileName.substring(idx + 1);
int h = ext.toLowerCase().hashCode();
if (restrict && (h == 0x17a1c || h == 0x3107ab || h == 0x19a1b || h == 0xe55 || h == 0x18417)) {
return true;
}
At first glance, this looks like some kind of performance optimization—comparing hash codes instead of strings. But that doesn't make sense since String.hashCode() is already highly optimized, and you'd still need to call toLowerCase() anyway.
The real reason is much more interesting: security through obscurity. The Reddit community quickly cracked the mystery using brute force to reverse the hash codes:
0x17a1c
→ "html"0x3107ab
→ "apk"0x19a1b
→ "dex"0xe55
→ "jar"0x18417
→ "sh"
This is Telegram's way of blocking potentially malicious file types without making it immediately obvious which extensions are banned. It's a vulnerability checker that's designed to weed out basic scammers who might try to distribute malware through the platform.
Is This Good Practice?
From a security perspective, this is a classic example of "security through obscurity"—it might slow down attackers, but it's not a robust defense. Any determined bad actor can reverse-engineer these hash codes just like the Reddit community did.
If you really want to make it harder for attackers to discover your blocked extensions, consider:
Server-side filtering with regularly updated rules
Content-based detection instead of extension-based
Proper sandboxing and permission models

👩💻 Special Guest
I’m excited to introduce acquisition maestro Evelin Herrera, the powerhouse behind one of the the largest Apps & Games M&A firms. She was kind enough to share her insider playbook for turning Android apps into exit gold 🏅
Why should Android devs care? Because mobile‑app Mergers & Acquisitions is still the wild‑west corner of our ecosystem—quiet, under‑reported, but brimming with buyers who pay real cash for sticky retention and organic installs. Evelin lives at that crossroads, and in the Q&A below she breaks down valuations, hot niches (spoiler: utilities rule), and the metrics that make acquirers say “shut up and take my money.”

Evelin Herrera
Tell us a bit about your background and how it led you into the mobile ecosystem.
I’ve spent the last five years living and breathing apps—starting in growth marketing, moving into founder–investor matchmaking, and ultimately running the largest Apps & Games M&A firm.
Can you give us a behind-the-scenes, transparent look at the sale of one or two Android apps?
🤐 NDAs keep the juicy specifics under wraps, but here’s the valuation playbook:
Age matters
< 12 months old: price = monthly profit × multiplier
≥ 12 months old: price = annual profit × multiplier
Typical range: 2.5 – 4 x profit for apps launched within the last 3 years
Multipliers 1 – 8 x, driven mainly by:
Retention (higher stickiness = higher multiple)
Organic user acquisition (ASO / word‑of‑mouth)
What are the most popular genres of apps that buyers are actively looking for right now?
Utility is king 👑 Some specific examples are:
🧹 Phone cleaner
💬 Image-to-text converter
🌻 Always-on display
☎️ Second phone number
📺 Screen mirroring / recorder
📄 PDF “everything” tools
What kind of apps have you helped developers sell? Could you walk us through a few representative examples?
In the last weeks, we've been selling a lot of AI Health & Fitness, AI companions (advisor, coach, assistant), classic utilities, and reference (how-to) apps.
What metrics do buyers care most about?
Things that make buyers say WOW when they look at opportunities:
Subscriber retention (churn kills deals)
Organic installs from the Play Store / App Store
Daily active users from high‑CPM countries (US, UK, DE, AU, etc.)
What role does platform (Android vs iOS) play in a buyer's decision-making process? Has the tech stack (like if it’s Native vs Flutter vs React Native) ever had an impact on the acquisition price/potential?
Platform: Some buyers target Android, some iOS, some both. More platforms = more potential bidders, but excellence on a single OS can still command a premium.
Tech stack: Usually buyer preference (do they already employ Flutter devs?). It rarely changes valuation unless the codebase is built with a truly outdated framework.
What’s the minimum traction or revenue threshold that starts to attract interest from buyers?
There isn’t one. Our buyer pool spans hobby apps with $0 MRR all the way up to products doing $1.5 Million MRR—someone’s interested at every tier.

At first glance, the code snippet might’ve looked reasonable. However, creating and managing the View outside of AndroidView's factory is considered an anti-pattern by the Compose team.
Here's why this matters:
Timing Issues: The
factory
andupdate
lambdas are special—they run as callbacks on the underlying Node being created/managed, with specific timing guarantees you can't replicate elsewhere in Composition.Side Effect Ordering: If you modify the View outside of the
update
lambda during Composition, you're creating a side effect that might execute before the rest of the composition is applied. This can cause problems with features likePausable Composition
that batch and delay changes.Future Compatibility: The Compose team wants to add more scenarios where compositions might be abandoned or delayed. Your external View manipulation could execute even when the composition changes are never applied.
The Fix: Always create and manage your View within AndroidView's factory:
@Composable
fun MyCustomViewComposable() {
var viewRef by remember { mutableStateOf(null) }
AndroidView(
factory = { context ->
MyCustomView(context).also { view ->
viewRef = view
// Configure here
}
},
update = { view ->
// All updates here
}
)
}

🦄 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