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.

AIAPI DesignAccessibilityAnalyticsAndroidAndroid TVApp LinksApp StartupArchitectureBaseline ProfilesBenchmarkingBuildBuild LogicBuild PerformanceCICI/CDCachingCancellationCareerClean ArchitectureCode GenerationCode QualityCode ReviewCommunicationComposeCompose MultiplatformConcurrencyConvention PluginsCoroutinesCrashlyticsDRMDatabaseDebuggingDecision MakingDeep LinksDependenciesDependency InjectionDeveloper ExperienceDeveloper WorkflowDocumentationEngineeringEngineering CultureEngineering LeadershipEngineering PracticeEngineering ProcessEngineering WorkflowExoPlayerFlowFocusGitHub ActionsGoogle PlayGradleHiltIncident ResponseJankJankStatsJetpack ComposeKMPKoinKotlinKotlin MultiplatformKtorLayoutLazyColumnLeadershipLeakCanaryLegacy CodeLibrariesLifecycleMVIMachine LearningMacrobenchmarkMaintenanceMedia3MemoryMemory LeaksMentoringMigrationMigrationsModularizationMonitoringNavigationNetworkingOTTOffline FirstOkHttpOnboardingPaging 3PaparazziPerfettoPerformancePlay StoreProGuardProcessProduct EngineeringProductivityProfilingR8RecompositionRefactoringReleaseRelease EngineeringRepository PatternRetrofitRoomSDKSavedStateHandleScreenshot TestingSeriesSoftware CraftSoftware DesignStabilityStartupStateState ManagementStatic AnalysisStreamingTVTalkBackTeamTeam CultureTest DoublesTestingToolingType SafetyUIUXVersion CatalogsViewModelWidevineWorkManagerWorkflowiOS
8 min readKotlinCoroutines

Testing Coroutines: Virtual Time, Not Thread.sleep

Sleeping in a coroutine test is slow, flaky and asserts nothing. How runTest's virtual clock works, choosing between the two test dispatchers, the backgroundScope pattern for Flow collectors, and the four mistakes that make a virtual-time test behave like a real-time one.

Read the article
7 min readLegacy CodeAndroid

Deleting Code Is a Feature

Dead code charges rent daily and nobody bills it. The real costs, the reachability probe that beats every static analyser, where dead code hides on Android, and how to delete safely without the ceremony that stops people trying.

Read the article
8 min readAIEngineering Culture

Keeping AI Honest: Verification Habits That Scale

Generation was never the bottleneck — verification is. Seven habits that make checking cheap, the team convention worth adopting, the confident-and-wrong signature to recognise, and why unverified code is worse than it looks.

Read the article
8 min readAndroidNetworking

Pagination That Doesn't Lie to the User

Offset pagination duplicates and skips items on any list that changes. Why keyset cursors fix it, the tiebreak everyone forgets, the three other lies a paged API tells, and the one test that catches the whole class of bug.

Read the article
7 min readArchitectureAndroid

The Cost of an Abstraction You Added Just in Case

Speculative abstraction arrives as a reasonable question in code review and bills everyone daily. What it actually costs, why the refactor you're avoiding is nearly free, which abstractions are worth building up front, and how to delete the ones that aren't.

Read the article
8 min readAIPerformance

AI-Assisted Performance Work: Reading Traces Together

A Perfetto trace won't fit in a context window and summarising it destroys the signal. Querying it with SQL will. The loop that makes a model useful on real performance work, what it reliably gets wrong, and the part that doesn't compress at all.

Read the article
7 min readLeadershipEngineering Culture

Technical Writing Is an Engineering Skill

Writing is the same skill as API design — choosing an abstraction for a reader with limited context — and it's the only artefact that works while you sleep. Five rules, the four documents worth being good at, and the failure mode that costs more credibility than the mistake.

Read the article
8 min readAndroidMedia3

Playback Analytics That Explain a Bad Night

Uptime dashboards say a streaming night was fine while support says otherwise. The five session metrics that explain quality of experience, the dimensions that make them diagnostic, and how to instrument Media3 so a bad night takes ten minutes to explain instead of a week.

Read the article
8 min readAIKotlin Multiplatform

Migrating a Module to KMP With AI Doing the Grunt Work

A KMP migration is mechanical in volume and subtle in a handful of decisions. The workflow that separates the two — compiler-generated task list, AI-generated categories, human-made policy, AI-applied volume — and the three things the model reliably got wrong.

Read the article
8 min readCI/CDTesting

Merge Queues and the Flaky-Test Tax

A merge queue eliminates semantic merge conflicts and multiplies the cost of every flaky test. The arithmetic of per-test reliability, why auto-retry hides real bugs, and the order of operations that makes a queue speed a team up instead of slowing it down.

Read the article
8 min readJetpack ComposeAndroid

Compose Custom Layouts: When Row and Column Stop Being Enough

The signs you've outgrown the standard containers, the four-rung ladder from Modifier.layout to SubcomposeLayout, a real Layout implementation in a dozen lines, and the single-measurement rule that makes Compose layout fast.

Read the article
8 min readAPI DesignKotlin

Deprecating an API Without Breaking Five Teams

Deprecation fails in two directions: breaking everyone at once, or a warning that survives three years. The Kotlin tooling that makes migration mechanical, the escalation schedule that makes a deadline real, and why counting call sites is the whole job.

Read the article
8 min readAIDeveloper Experience

Prompt Patterns That Work on Real Code (And Why)

Six prompt patterns that hold up on a large production codebase, the reasoning behind each one, the popular advice that measurably does nothing, and the repair loop to use when an answer comes back wrong.

Read the article
8 min readAndroidPerformance

Baseline vs Startup Profiles — And Measuring the Win

Baseline profiles and startup profiles come from the same test run and optimise different things — AOT compilation versus DEX layout. How each one works, the one Gradle argument teams miss, and a measurement methodology that produces a number you can defend.

Read the article
8 min readAndroidNavigation

Deep Links That Survive Cold Start

Deep links break on cold start because the intent arrives before the app is ready, and warm and cold take different code paths. Treating the link as state rather than an event, synthesising a back stack, surviving an auth redirect, and the adb command that is the only real test.

Read the article
8 min readAICode Review

AI in Code Review: What to Automate, What to Never Automate

AI review works on mechanical, objective, cheap-to-verify findings and fails on verdicts. Where the line sits, why precision matters far more than recall, how to roll one out in shadow mode, and the team dynamics that decide whether anyone keeps reading it.

Read the article
8 min readAndroidRelease Engineering

99.9% Crash-Free Is a Process, Not a Number

Crash-free rate is an average, and averages hide the regressions that matter. What the headline number conceals, how teams accidentally optimise the graph instead of the app, and the four-part process that turns stability from a report into a decision.

Read the article
7 min readKotlinCoroutines

Dispatchers: The IO Pool Is Not Infinite

Dispatchers.IO is capped at 64 threads and every blocking call holds one hostage. Why the failure is a cliff rather than a slope, how to see it in a thread dump, and how limitedParallelism partitions the pool so one slow subsystem can't starve the rest.

Read the article
7 min readAIDeveloper Experience

Building an Internal Tool With AI in an Afternoon

Internal tools have three users, no adversaries and no SLA — the risk profile that makes them the best AI use in a senior engineer's week. The workflow that gets one shipped in an afternoon, and the rules that stop it becoming a liability.

Read the article
8 min readKotlin MultiplatformCompose Multiplatform

Compose Multiplatform: The Reality Check

Sharing logic and sharing UI are different bets with different risk profiles. What Compose Multiplatform genuinely delivers, the platform-feel and organisational costs nobody puts in the evaluation doc, and the staged strategy that survives contact.

Read the article
7 min readJetpack ComposeTesting

Compose UI Tests: Synchronisation, Not Sleeping

Every Thread.sleep in a UI test is a flake you haven't seen yet plus time you pay on every CI run. The three real causes of Compose test flakiness — invisible work, infinite animations, interop — and the synchronisation primitives that replace guessing.

Read the article
7 min readAIEngineering

When to NOT Use AI: The Tasks It Makes Slower

A model is fast at generation and adds nothing to verification — so its value is (time to write − time to verify − the round trip). Seven categories where that maths goes negative, and why it still feels fast the whole time.

Read the article
7 min readAndroidNetworking

Caching That Respects the User's Data Plan

Most Android apps ship with no HTTP cache configured and re-download the same payload every cold start. How to set up OkHttp caching, get 304s from your backend, size images properly, and honour metered connections and Data Saver.

Read the article
7 min readJetpack ComposeAndroid

Compose Side Effects: LaunchedEffect Keys and the Restart You Didn't Want

LaunchedEffect's keys are the effect's lifecycle, not a cache hint. Five gotchas — unstable keys, lazy list items, stale lambdas, input-driven work and one-off events — and the mental model that prevents all of them.

Read the article
7 min readAIAndroid

AI-Assisted Accessibility Passes That Actually Ship

Accessibility work stalls because it's one undifferentiated backlog card. AI is good at turning it into forty reviewable diffs — provided you run the deterministic scanners first and keep every judgement call, because it can't see pixels, focus order, or your users.

Read the article
7 min readGradleAndroid

Build Logic as a First-Class Module

Convention plugins fix duplicated build scripts; where you put them decides whether they become infrastructure or a second mess. Why an included build beats buildSrc, and how to engineer build logic like the production code it is.

Read the article
7 min readAndroidArchitecture

Repositories: The Layer Most Teams Get Wrong

A repository's only job is owning the decision of where data comes from. If it forwards a call to Retrofit it isn't abstraction, it's indirection — here's the shape that survives a real app, and when to skip the layer entirely.

Read the article
7 min readAndroidPerformance

Jank Hunting: Frame Timing Beyond "It Feels Smooth"

"It feels smooth" is a claim about your device, not your app. Jank is a distribution problem: measure P99 and frozen frames with FrameTimingMetric in CI and JankStats in production, and only then open a trace.

Read the article
7 min readAIAndroid

Getting AI to Explain a Codebase You Just Inherited

"Explain this codebase" returns a summary of your folder names that feels like understanding. Traversal questions — trace the launch path, slice one feature, find the seam between two architectures — turn a model into a fast reader, provided you verify every edge it claims.

Read the article
7 min readAndroidDependency Injection

Hilt vs Koin: What Actually Differs at Scale

In a sample app they're interchangeable. At 60 modules and a dozen engineers, four things separate them: when a missing binding fails, build cost, multiplatform reach, and test ergonomics. Everything else is syntax.

Read the article
7 min readAIAndroid

UX for Latency: Designing Around a 3-Second Model

Model calls take seconds, vary wildly and sometimes refuse — and every loading pattern Android has was built for a 300ms REST call. Streaming, shaped skeletons, honest intermediate state, cancellation, and the Compose and lifecycle traps that make a slow feature feel broken.

Read the article
7 min readAIAndroid

AI and Flaky Tests: Root-Causing Instead of Retrying

A flaky test is the rare bug that arrives with a control group — passing and failing runs of identical code. Feed a model the whole set rather than one trace, ask it to enumerate dependencies and produce a 100%-failing reproduction, and refuse every fix that adds a wait.

Read the article
7 min readAndroidLegacy Code

De-risking a Big Rewrite Nobody Asked For

The engineer proposing a rewrite is closest to the pain, so a flat no costs you the person. Convert the request into a one-page case with numbers, apply the three tests that actually justify a rewrite, and if it proceeds, use exit conditions and shipped value to keep it stoppable.

Read the article
7 min readAndroidCI/CD

Release Automation: Tag → Signed Build → Store, No Humans

A push of a tag should produce a signed AAB, an uploaded mapping file and a store listing on the internal track. The pipeline steps, the two rules that make it trustworthy, and the real test: shipping a hotfix at 11pm without the person who knows the signing setup.

Read the article
7 min readAIArchitecture

Writing an ADR With AI — Deciding, Not Just Drafting

A model will happily produce a polished architecture decision record for a decision you never made. Used the other way round — you write the context, it generates options and attacks your choice — it turns a one-hour document nobody writes into a fifteen-minute argument that changes the outcome.

Read the article
6 min readAndroidKotlin

Flow Backpressure: buffer, conflate, collectLatest

Flow handles backpressure by suspending, which means a slow collector throttles the producer. buffer decouples them, conflate drops values before work starts, collectLatest cancels work already running — three different answers to what a dropped value costs.

Read the article
6 min readAndroidJetpack Compose

Saved State: rememberSaveable vs SavedStateHandle

Both survive process death, so teams pick by habit. The real axis is ownership and timing: SavedStateHandle is readable before the ViewModel's init runs and can start the right load; rememberSaveable arrives a frame later and only the composable can see it.

Read the article
6 min readAndroidJetpack Compose

Compose Modifier Order Is Not Cosmetic

A Modifier chain is a stack of wrappers, outermost first — constraints flow down it left to right and sizes flow back right to left. That single model explains padding vs background, dead touch targets, clip after background, and why size().padding() is the icon-button idiom.

Read the article
7 min readAIAndroid

AI for the Boring 80%: Boilerplate, Mappers, Parsers

Cheap verification — not difficulty — is what makes a task a good fit for generation. The categories that qualify, the four failures that recur in exactly this kind of work (optimistic nullability, dropped fields, enum valueOf, dates), and why you write the assertions.

Read the article
7 min readEngineering LeadershipMentoring

Unblocking a Junior Without Taking the Keyboard

Five kinds of stuck, each needing an opposite response — a missing fact, an unrankable choice, a missing debugging method, missing permission, and a genuinely hard problem. Diagnosing which one you're looking at is the whole skill.

Read the article
8 min readAndroidPerformance

Halfway: The 10 Android Performance Rules I'd Keep

Fifty posts in, the ten performance rules that survive contact with real apps: measure on real hardware at p90, know your startup phase, ship a Baseline Profile, fix stability rather than composable bodies, and never claim a win without a before and an after.

Read the article
7 min readAndroidTesting

What's Actually Worth Testing on Android

Coverage rewards testing the code that doesn't break. Two questions that decide whether a test earns its place, the six categories that consistently pass them, the five that don't, and why feedback time belongs in the value calculation.

Read the article
7 min readAIAndroid

Using AI to Find Dead Code and Unused Resources

R8 and lint already compute reachability properly — use their output as evidence, then use a model for the part they can't do: classifying why a candidate is dead. The categories it finds that static analysis misses, and the four it confidently gets wrong.

Read the article
7 min readAndroidMedia3

Media3 + Widevine: The Licence Flow Nobody Documents

The six steps of a Widevine licence request, and the five places production breaks: runtime security-level downgrades, device provisioning, mid-playback licence expiry, leaked offline licences, and auth tokens that die before the session does.

Read the article
7 min readAndroidNetworking

Retrofit vs Ktor in 2026: An Honest Comparison

One input decides this: whether you're shipping Kotlin Multiplatform. Everything else — the interceptor ecosystem, testability, streaming support, and the failure-mode defaults that differ dangerously — is what you should weigh once that's answered.

Read the article
7 min readAndroidArchitecture

Unidirectional Data Flow Without the Boilerplate Tax

UDF earns its keep; the canonical MVI scaffolding around it often doesn't. The four properties that actually make unidirectional data flow work, the three pieces of ceremony you can delete, and where the sealed-intent version genuinely pays off.

Read the article
7 min readAIArchitecture

Pair-Designing an Architecture With AI: Where It Helps, Where It Lies

An AI is a strong design partner for option coverage, steelmanning and failure-mode interrogation — and it fabricates consensus, invents numbers, has no cost model, and agrees with whatever framing you hand it. The four lies, and the prompting habits that neutralise them.

Read the article
6 min readAndroidGradle

Gradle Version Catalogs and the Dependency Drift They Prevent

Dependency drift is structural, not a discipline problem: the same version string living in five build files will diverge. What a version catalog fixes by construction, what bundles and plugin aliases add, and the transitive conflicts it deliberately does not solve.

Read the article
6 min readAndroidJetpack Compose

Memory Leaks in Compose: What LeakCanary Won't Tell You

LeakCanary watches Activities, Fragments, Views and ViewModels — not remembered objects. The four Compose leak shapes it misses, why the fourth isn't technically a leak at all, and the four-minute heap-dump routine that finds all of them.

Read the article
6 min readAndroidAPI Design

Designing an Internal SDK Other Teams Won't Hate

Internal libraries get resented for five predictable reasons: no test fake, leaked internals, a long happy path, opaque errors, and deprecations with no migration. Fix those and the wrappers your consumers built around you disappear.

Read the article
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