Writing

Senior Android engineering, in depth

Every couple of days I publish one focused piece — Compose internals, coroutines, performance, architecture, and how I actually work with AI as a senior engineer. No beginner tutorials.

AIAccessibilityAndroidAndroid TVApp StartupArchitectureBaseline ProfilesBenchmarkingBuild LogicBuild PerformanceCI/CDCancellationCareerClean ArchitectureCode QualityCode ReviewComposeConcurrencyCoroutinesCrashlyticsDatabaseDebuggingDependency InjectionDeveloper WorkflowEngineeringEngineering LeadershipEngineering WorkflowFlowFocusGitHub ActionsGoogle PlayGradleHiltIncident ResponseJankJetpack ComposeKMPKotlinKotlin MultiplatformLazyColumnLegacy CodeLifecycleMachine LearningMemory LeaksMentoringMigrationMigrationsModularizationMonitoringNavigationOTTOffline FirstPaparazziPerfettoPerformanceProGuardProcessProduct EngineeringProfilingR8RecompositionRefactoringRelease EngineeringRoomSavedStateHandleScreenshot TestingSeriesStartupState ManagementStatic AnalysisTeam CultureTest DoublesTestingType SafetyWorkManageriOS
6 min readAICode Review

Reviewing AI-Generated Code Like It Came From a Junior

The junior-developer framing is a good default that breaks in three specific ways: the errors don't cluster, the confidence is flat, and nothing is learned. Those three differences tell you exactly where to spend review attention.

Read the article
5 min readAndroidJetpack Compose

Compose Deferred Reads: Passing Lambdas Instead of Values

Where you read a State decides which of Compose's three phases invalidates. Reading a scroll offset in a composable body recomposes; reading it inside a layout lambda only re-lays-out. The technique, and the far more important question of when not to use it.

Read the article
5 min readAndroidRelease Engineering

Staged Rollouts and the Halt Criteria You Set in Advance

A rollout percentage isn't a safety mechanism — a pre-agreed halt threshold is. Which numbers to fix before you ship, how long each stage really needs, and why halting on Android is not a rollback.

Read the article
6 min readKotlin MultiplatformAndroid

Kotlin Multiplatform: When NOT to Share

Shared code carries a coordination tax — two build systems, two release trains, two review queues. The useful test isn't “can we share this” but “does the cost of divergence exceed the cost of coordination”, and for UI and platform integration it usually doesn't.

Read the article
5 min readAIAndroid

Teaching AI Your Codebase's Conventions (And Why It Forgets)

A model follows your conventions for forty lines, then reverts to the internet average. Why that happens — a training prior against three examples — and the practical fix: exemplar files, constraints with reasons, and lint rules that don't decay.

Read the article
5 min readKotlinCoroutines

Cancellation Is Cooperative — And Your Code Isn't Cooperating

cancel() doesn't stop a coroutine — it sets a flag that only suspension points check. The four places Android code breaks the chain: CPU loops, catch(Exception), suspending cleanup in finally, and blocking Java calls.

Read the article
5 min readAndroidTesting

Screenshot Tests That Don't Cry Wolf

A screenshot suite dies the day the team learns to run --update-screenshots without looking. Removing the false positives — determinism, JVM rendering, component-level scope, a deliberate tolerance — is what keeps the diff worth reading.

Read the article
5 min readAndroidJetpack Compose

Compose Navigation: Type-Safe Routes and Why Strings Rot

String routes are an untyped API between your screens — renames don't propagate, every argument is a String, and special characters break matching silently. How Navigation 2.8's @Serializable routes fix it, and what still needs strings.

Read the article
5 min readAIAndroid

AI-Assisted Incident Triage: From Crashlytics Stack to Hypothesis

Under incident pressure, a confident wrong answer is indistinguishable from a right one. How to use AI to narrow a crash fast — asking what a trace rules out, scoping to the release diff, demanding a reproduction — while keeping the rollback decision human.

Read the article
6 min readAndroidRefactoring

The Strangler Pattern on a 400k-Line Android App

Big-bang rewrites fail predictably: two codebases, a slipping switch-over date, and a v2 that never reaches feature parity. The strangler pattern replaces a large app incrementally behind flags — always one flip from working, always shipping value.

Read the article
5 min readAndroidPerformance

App Startup: The Five Phases and Where Your Time Really Goes

A single startup number tells you nothing actionable. Startup is five phases with five different fixes — and the one that usually dominates, time-to-usable, is the one nobody measures. How to attribute the time before optimising it.

Read the article
5 min readAndroidRoom

Room Migrations You Can Actually Roll Back

A bad database migration edits every user's data remotely with no undo. Why fallbackToDestructiveMigration belongs in debug only, how to test migrations against realistic data, and the additive-first strategy that keeps a rollback possible.

Read the article
5 min readAIRefactoring

Using AI to Write the Migration You're Scared Of

The migration everyone agrees on and nobody starts isn't hard, it's large — and largeness is its own risk. AI makes the two expensive parts cheap: taking inventory and repeating your decision at volume. The four-step workflow and the two rules that keep it safe.

Read the article
5 min readAndroidAI

On-Device vs API: Choosing Where Inference Runs

Where an AI feature runs is a product decision, not an infrastructure one. Four questions that settle it — data residency, offline requirements, iteration speed, and true per-user cost — plus the hybrid pattern and the app-size constraint nobody plans for.

Read the article
5 min readAndroidGradle

Cutting Gradle CI Time Without Buying Bigger Machines

Slow Android CI is usually a caching problem, not a hardware one. The remote build cache, dropping clean, running only affected tests, caching the right directories, and the configuration cache — with the measurement discipline that keeps you honest.

Read the article
5 min readAndroidJetpack Compose

Lazy Lists in Compose: Keys, contentType, and the Jank You Can't Profile Away

A LazyColumn without keys identifies items by position, so inserting at the top invalidates everything. Add contentType for mixed lists so compositions can be reused. The two parameters that fix most list jank, and why the profiler won't point at them.

Read the article
5 min readAICode Review

Prompting for a Code Review of Someone Else's PR

Reviewing someone else's PR with AI is a social act, not just a technical one. Use it to orient on unfamiliar code, surface assumptions your unfamiliarity hides, and stress-test your own objections — but never to generate the comment you post.

Read the article
5 min readAndroidArchitecture

Modularization That Survives a Growing Team

Splitting an Android app by layer looks clean and delivers nothing — every feature still touches every module. Split by feature instead, keep features independent of each other, and measure the blast radius of a one-line change.

Read the article
5 min readAndroid TVOTT

Focus Handling on Android TV: The 10-Foot UI Nobody Tests

On Android TV there is no touch — focus is the entire navigation model. The four failure modes that make a ported phone app unusable on a television, how to fix them in Compose for TV, and the test that costs nothing: unplug your mouse.

Read the article
5 min readKotlinCoroutines

Flow Operators That Quietly Change Your Threading

flowOn affects everything upstream of it and nothing downstream — the opposite of RxJava's observeOn. Why operator order silently moves work onto the main thread, why there's no collectOn, and the mental model that stops the guessing.

Read the article
5 min readAIGradle

Making AI Review Your Gradle Build — The Unglamorous Win

Build files are the least-reviewed, highest-impact code in an Android repo. Auditing them is a near-perfect task for AI: dense, declarative, and full of implications nobody memorises. The audit prompt, the five findings it produces most often, and how to verify.

Read the article
5 min readEngineering LeadershipCode Review

Code Review as Mentoring, Not Gatekeeping

The comments on a team's pull requests reveal more about its health than any standup. How to review so people learn a class of problem rather than a rule — explaining consequences, separating blocking from optional, and treating review as a conversation with a merge button.

Read the article
5 min readAndroidR8

R8 in Anger: What Actually Shrinks, What Actually Breaks

R8 removes 30–40% of a typical APK and speeds up startup. Almost every 'R8 broke my app' bug traces to reflection. What the three phases do, how to write precise keep rules instead of blanket ones, and how to read usage.txt to find what you broke.

Read the article
5 min readAndroidTesting

Fakes Over Mocks: Tests That Survive a Refactor

A mock encodes how your code calls its dependencies; a fake encodes what those dependencies do. One breaks on every refactor, the other doesn't. When to write a fake, when a mock is genuinely correct, and how to keep fakes honest.

Read the article
5 min readAIKotlin

Debugging a Concurrency Bug With AI as a Rubber Duck That Reads Code

Race conditions are hard because you can't hold every interleaving in your head. A model can enumerate them without getting bored or attached to a theory — provided you ask for orderings rather than causes, and demand a reproducible trigger for every hypothesis.

Read the article
5 min readAndroidJetpack Compose

derivedStateOf: The Compose API Everyone Reaches for Too Late

derivedStateOf converts a high-frequency input into a low-frequency output — turning 60 recompositions per second into one. When it's the right tool, when remember(key) is what you actually wanted, and the mistakes that make it a no-op.

Read the article
6 min readAndroidArchitecture

Offline-First Without a Sync Nightmare

Offline-first gets complicated when the network is treated as the source of truth. Invert it: the UI reads only from the database, the network is a background writer. Here's the read path, the harder write path, and the conflict decisions you must make explicitly.

Read the article
5 min readAndroidState Management

Process Death Is Not an Edge Case

Android kills backgrounded processes routinely, restores the back stack, and leaves your ViewModel gone. What survives process death, what doesn't, how to reproduce it in ten seconds with adb, and the rule for deciding what belongs in saved state.

Read the article
5 min readAITesting

Generating Tests Worth Keeping (And Deleting the Ones That Aren't)

AI generates test coverage effortlessly, and most of it is a liability — assertions coupled to the implementation you're about to change. The single bar that separates a useful test from a maintenance tax, and the prompt that produces the first kind.

Read the article
5 min readAndroidGradle

Gradle Convention Plugins: Killing 40 Copies of the Same Build Script

Copy-pasted build.gradle.kts files are duplication like any other. Convention plugins move shared build logic into typed, testable Kotlin in build-logic — one place to change, modules that declare intent, and a configuration cache that stays valid.

Read the article
5 min readAndroidPerformance

Reading a Perfetto Trace: From 'It's Janky' to a Line Number

A repeatable workflow for turning vague jank reports into a specific method: capture a short trace, find the dropped frame, identify which stage blew the frame budget, then read the slice stack — with your own trace sections making your code visible.

Read the article
5 min readAndroidKotlin

StateFlow vs SharedFlow: Choose by Replay Semantics, Not Habit

The StateFlow/SharedFlow decision is really one question: should a late subscriber learn what it missed? State says yes, events say no. Plus the three behaviours — conflation, deduplication and replay — that cause the bugs.

Read the article
5 min readAndroidKotlin

Refactoring Legacy Java to Kotlin With an AI Pair — Safely

The risk in a Java→Kotlin migration isn't syntax, it's the nullability contract you invent while cleaning up the conversion. How to use AI for the mechanical work while keeping every nullability decision evidence-based and human-owned.

Read the article
5 min readAndroidArchitecture

Clean Architecture on Android: Which Layers Actually Earn Their Keep

After 12 years of shipping, inheriting and deleting Clean Architecture on Android: the two boundaries that consistently pay for themselves, the three that usually don't, and the single test to apply before adding a layer.

Read the article
5 min readAndroidJetpack Compose

@Immutable vs @Stable: What You're Actually Promising the Compose Compiler

@Stable and @Immutable are contracts, not hints — the Compose compiler trusts them completely and skips recomposition on that basis. What each one guarantees, how a List field quietly breaks them, and how to verify stability instead of guessing.

Read the article
5 min readAndroidHilt

Hilt Scoping Mistakes That Leak a Whole Screen

Hilt scoping bugs compile cleanly, pass tests, and quietly leak memory. Four scoping mistakes I keep finding in production Android codebases — and the one rule that prevents all of them.

Read the article
5 min readAICode Review

Reviewing Your Own Diff With AI: The Prompt That Actually Finds Bugs

Asking an AI to 'review this code' returns style opinions, not bugs. A constrained prompt — diff only, named failure axes, concrete failure scenarios, ranked by severity — turns it into a checklist that reads your code.

Read the article
5 min readAndroidPerformance

Baseline Profiles: The Startup Win Most Android Apps Leave on the Table

Baseline Profiles tell the Android runtime which code to AOT-compile at install time, removing interpretation from your cold start. Here's how they work, how to generate one that reflects real usage, and how to prove the win with macrobenchmark.

Read the article
6 min readKotlinCoroutines

Structured Concurrency Is a Feature, Not a Formality

Most coroutine bugs aren't about threads — they're about lifetimes. A senior tour of structured concurrency: scopes, cancellation, and the myths that cause leaks.

Read the article
6 min readJetpack ComposePerformance

Your Compose Performance Bug Isn't Slow Code — It's Recomposition

Most Compose jank isn't heavy work — it's work happening too often. A senior look at recomposition, stability, and how to actually measure it before you optimise anything.

Read the article
3 min readAndroidCareer

Start Here: 100 Posts on Senior Android Engineering (and Working With AI)

A 100-post series sharing 12 years of production Android experience — Compose internals, coroutines, performance, architecture, and how a senior engineer actually works with AI. Here's the plan.

Read the article