The starting point

The app behind these notes is a HIPAA-compliant community and collaboration platform, live on Android for over a decade and used by more than 1.3 million users and organizations. The codebase had grown the way most long-lived codebases do: roughly 391,000 lines of Java and XML views in a single module, with Activities, MVP, and RxJava layered through it, one era at a time.

None of that made it a bad app. It made it a slow one to change. Incremental builds took 88 seconds, every feature touched code it didn't own, and each new hire inherited all of it at once. A rewrite was the obvious suggestion and the wrong one: the product had a roadmap, paying organizations, and no appetite for a feature freeze.

The approach: strangler fig, screen by screen

We ran the migration as a strangler fig. The modular architecture grew around the monolith and took it over one screen at a time, while the app on the store kept releasing on its normal cadence.

The order of operations mattered more than any individual technique:

  • The module graph came first. Before any UI moved, we designed the target graph: feature modules behind clear API surfaces, shared core modules for design, data, and networking, and dependency injection rules that made illegal imports impossible rather than discouraged.
  • Each screen moved whole. A screen left the monolith as a unit: Java to Kotlin, XML views to Jetpack Compose, MVP and RxJava to MVVM with ViewModel, Coroutines, and Flow. No screen lived half-migrated, which is where strangler figs usually rot.
  • Features kept shipping. New work landed in new modules from day one. The monolith only ever shrank.
  • Release discipline was the safety net. Migrated screens shipped behind backend-driven feature flags with fast rollback that needed no app release. Test coverage grew four-fold during the migration, with JUnit, Mockito, Espresso, and Appium suites wired into CI so every change was exercised before it reached users.

The result of that ordering: 100% uptime across the whole migration, and a build that got faster every month the work went on, ending 93% quicker for the entire team.

Where the agentic workflows fit

This migration is also where our AI-native operating model was forged. The rule we settled on: agents do the volume, humans own the structure. Nothing architectural shipped on autopilot.

  • Agentic refactors with review gates. AI coding agents carried the mechanical weight of conversion work, inside guardrails: every structural decision designed and reviewed by a senior engineer, every change through the same CI as human-written code.
  • AI on release health. Scheduled agent pipelines run daily health checks against crash reporting, triage new crashes, and produce candidate fixes. That system cut crash-affected users by 80% within three months and holds the app at 99.7% crash-free.
  • AI in the whole lifecycle. Code review, crash triage, visual QA against emulators, and release automation all run agent-assisted, with human sign-off where it counts.

What we'd tell you before you start

  1. Draw the module graph before you touch a screen. Boundaries are the migration; Compose is just the payoff.
  2. Migrate state management with the screen, not before it. Half-moved architecture costs more than un-moved architecture.
  3. Treat build time as a first-class metric. It is the compounding return that keeps the team invested for the months the migration takes.
  4. Put the rollback path in place before the first migrated screen ships, not after the first incident.
  5. Give AI the volume and keep the steering. Agentic tools made this migration fast; review gates made it safe.