The scenario

Consider a useful Android product whose screens mix network requests, persistence, navigation, and business rules. Small changes affect unrelated features, yet customers still need regular releases. The proposed approach would modernize one vertical slice behind a stable boundary, learning from that migration before extending the new architecture across the codebase.

Choose a feature with a clear behavioural contract

The first candidate would have a contained user journey and a reason to change, such as an unreliable account-detail screen. A short behaviour inventory would record loading, cached data, errors, permissions, and navigation. Characterization checks would capture essential behaviour without preserving every accidental implementation detail.

The migration would start with a dependency map. Existing models and services might need an adapter, but unrelated screens would not be rewritten simply to make the directory tree look consistent. The team would define a small success criterion, such as replacing one data path without changing its public API contract.

Introduce boundaries that the build can enforce

A feature module would depend on explicit application and data contracts. The UI would render state and dispatch actions, while a repository boundary would coordinate remote and local data. Platform lifecycle concerns would remain visible rather than being hidden inside a long-lived global object.

Gradle dependencies and visibility would make the intended direction checkable. An adapter could bridge legacy code during the transition, with one owner for each piece of mutable state. Creating a module for every small class would add build complexity without necessarily improving the product.

Release a slice, then remove the old path

The replacement would be exercised with process recreation, interrupted requests, expired sessions, and malformed responses. Comparison would focus on the same customer tasks before and after the change. Any feature switch would select one implementation rather than leaving both paths writing competing state.

After a controlled release, the obsolete implementation and temporary adapters would have a removal plan. A migration note would explain the chosen contract, unexpected dependencies, and follow-up work. That evidence would guide the next feature instead of treating the first pattern as a universal template.

What to validate

  • Existing user-visible behaviour is recorded before replacement.
  • Dependency rules prevent the new feature from importing legacy internals.
  • Only one implementation owns a record or side effect at a time.
  • The release has a fallback and a plan to remove temporary migration code.

The tradeoff

Incremental migration keeps delivery possible but temporarily supports two architectural styles. A narrow, completed slice is more useful than a large new framework that no production feature fully uses.

Technical references

Official documentation supporting the tools and mechanisms discussed above. The proposed architecture is illustrative.