AI coding agents are not a drop-in replacement for Android engineers. They are a new layer in a system that still requires humans to make architectural calls, enforce quality standards, and decide what counts as done. A developer named rayford published a detailed breakdown of how to structure that system so agents produce output you can actually ship.
Three components that determine output quality
Every AI coding system has three parts: the foundation model (Claude, GPT, Gemini), the coding agent tool (Claude Code, Codex, CodeBuddy), and the context and verification system supplied by the engineering team. The model sets the ceiling on reasoning quality. The agent tool wraps that model in file search, editing, command execution, and task decomposition. But the third component is where teams have the most control and where Android projects are most likely to break down.
Android adds complexity that most coding benchmarks do not capture: Gradle builds, build variants, resource and manifest merging, Jetpack component lifecycles, JNI/NDK, R8 obfuscation, and UI rendering all increase what an agent needs to understand before it can make a reliable change.
Context engineering: what the agent sees
A large language model is stateless. Each output depends entirely on what is in the current context window. Rayford recommends a layered context approach: a project map and navigation index at the root level, module-level documents for local rules, and dedicated documents for builds, tests, performance, releases, analytics, and privacy. The agent reads only the top-level guide at the start of a task, then pulls the relevant sub-documents as needed.
A project-level AGENTS.md or CLAUDE.md should answer the questions an Android agent hits most often: what the app does, how Gradle modules are organized, which architecture pattern is in use (MVVM, MVI, Clean Architecture), the UI stack (View System, Jetpack Compose, or both), and the build and test entry points including lint, unit tests, instrumentation tests, and NDK builds.
Context compaction matters too. Build logs, failed attempts, long JSON payloads, and search results pollute the window. At each Research, Plan, and Implement stage, the agent should compress noisy output into a structured artifact: which files were read, what is currently understood, which options were ruled out, and what risks remain.
️ Harness engineering: defining done
Context answers whether the agent knows what to do. Harness answers whether it can verify the result. Rayford splits harness controls into two categories. Feedforward controls set constraints before the agent acts: architecture rules, module boundaries, coding standards, shared-component usage guides, and testing requirements. Feedback controls run after the agent acts: Gradle builds, Android Lint, Detekt/Ktlint, unit tests, Compose UI Tests, Robolectric, instrumentation tests, NDK builds, gtest, and static architecture checks.
The key principle: move deterministic, CPU-executable checks as early as possible. Compilation, lint, type checks, unit tests, module-dependency checks, and coverage checks should all run before anything reaches human review. Semantic judgments about design quality, test coverage of core business behavior, and UI fit for product expectations still require a human call.

The six-step workflow
Rayford describes a six-step process that keeps humans at the high-leverage decision points rather than in the repetitive coding loop:
- Requirements analysis: clarify the business goal, reproduction steps, impact, and acceptance criteria.
- Codebase research: the agent reads relevant modules and call chains and produces a research document before touching any code.
- Solution planning: the agent identifies which files to change, why, how to verify the changes, and where the risks are.
- Human plan review: engineers check whether the direction is correct, abstractions are appropriate, and verification is sufficient.
- Staged implementation: the agent implements the plan and runs a build, tests, or device verification after each runnable increment.
- Human code and report review: engineers inspect key diffs, verification results, and uncovered risks, then decide whether to extend, roll back, or hand off to QA.
Testability: IoC, DI, and layered tests
Much Android legacy code is hard to test because business logic is scattered across Activities and Fragments, ViewModels access global Managers directly, and network, database, and experiment flags are read through singletons. The fix is Inversion of Control and Dependency Injection: separate external dependencies from business logic and pass them in through interfaces and constructor injection. On the Kotlin/Java side, that means Hilt, Dagger, Koin, or an in-house container. On the C++ side, Fruit or a lightweight factory.
With proper DI in place, an agent can mock networking, databases, account state, experiment flags, and the time source when testing a ViewModel, and swap out remote services and local caches when testing a Repository. JVM unit tests cover ViewModels, UseCases, Repositories, state machines, and error handling at the fastest feedback speed. Robolectric sits in the middle. Instrumentation tests, Compose UI Tests, UIAutomator, and screenshot tests validate screen behavior on real devices. Native logic should run with local gtest before requiring a full app launch.
Integration acceptance and device verification
Unit tests protect client-side logic, but a requirement is not delivered just because the client code compiles. Complete delivery also depends on server APIs, configuration delivery, experiment flags, assets, analytics instrumentation, staged rollout, and test cases that confirm the primary flow works end to end.
The Android SDK provides adb, UIAutomator, screenshots, accessibility trees, log capture, file retrieval, and Intent-based launching. The open source project Trailblaze connects device control, screen understanding, test recording, and verification into one workflow. One lesson from Trailblaze: reading the UI hierarchy and accessibility information first reduces token usage and makes control identification more reliable. Screenshots and OCR are better as fallbacks when the UI tree is unavailable.
If a QA team maintains integration test cases, a practical workflow is to export those cases and convert them to structured Markdown, connect a test device, have the agent execute each case step by step, capture the UI tree, screenshots, and logs at each step, and produce a report identifying passed, unexecuted, blocked, and human-review-required cases. If no test cases exist, Journeys can generate them.
The maintainability warning most teams ignore
Most current coding benchmarks reward passing tests but do not penalize damage to maintainability. An agent can pass tests with local hacks, duplicated logic, poor abstractions, excessive fallbacks, or workarounds that bypass the type system. The costs of those choices often appear only weeks, months, or a year later. Faster code generation does not answer the fundamental questions of how modules should be divided, how dependencies should be controlled, or how state should be managed. Those judgments still belong to the engineers who will maintain the codebase.

