Sparround

Legacy refactoring: XML → Compose, Java → Kotlin

Migration is where an agent helps most and can also do the most damage. The help: mechanical conversion is voluminous and tedious for a human. The damage: a wrong migration compiles but changes behaviour.

The official recommendation is incremental migration: Compose and the View system coexist for a while. That is exactly what the interop APIs are for:

  • `ComposeView` — putting a Composable inside an existing View-based layout.
  • `AndroidView` — using a View component inside a Compose screen.
  • `AndroidViewBinding` — using existing view binding from Compose.
Migration stepSuits the agentReason
Planning the sequencePartly — in plan mode, with human approvalBusiness priorities are unknown to the agent
Writing tests for current behaviourYes — high valueMechanical, and the result is checkable
Converting one screenYesNarrow scope, guarded by tests
Converting 20 screens at onceNoReview becomes impossible and regression causes get lost
Automatic Java → Kotlin conversionNo — the IDE tool does it betterThe agent's value is in the cleanup *after* conversion
Making converted Kotlin idiomaticYesNullability, data classes, scope functions

The most important rule: don't mix migration with improvement. While converting XML to Compose the agent tends to "improve the logic while I'm here". The PR then blurs conversion and change, and if a regression appears the cause is hard to find. State it explicitly in the task: "don't change behaviour, only convert".

In a Java → Kotlin migration the agent's real value is not the automatic conversion — Android Studio's own tool does that well. The value is in the step after: converted code is technically Kotlin but not idiomatic.

Typical cleanup work:

  • Nullability — automatic conversion sprays ? and !! everywhere. Real null-safety analysis is needed.
  • `data class` — a getter/setter class can often become one.
  • Scope functions — simplification with let, apply, also.
  • Collection operations — loops replaced by map, filter, groupBy.
  • `when` — long if-else chains.

All of these carry a risk of changing behaviour, so test coverage matters.

📚 Sources and documentation