Antes de empezar — dos precondiciones
Migrar una app existente a multimodulo sin estas dos cosas en su lugar es garantía de caos:
- Convention plugins básicos: aunque sea solo uno que configure las opciones de compilación comunes. Sin esto, cada módulo nuevo requiere copiar y pegar 30 líneas de Gradle manualmente.
- La app ya usa inyección de dependencias: Hilt o Koin. Extraer un módulo de una app donde todo se instancia directamente es muchísimo más difícil que si ya tenés DI. Si no tenés DI, migrá primero.
Qué extraer primero — el orden importa
El error más común es intentar extraer una feature completa como primer módulo. Las features tienen muchas dependencias. Lo correcto es empezar por lo que menos dependencias tiene y es más estable:
# Orden recomendado de extracción:
# 1. :core:domain (o similar)
# → Modelos de datos puros, interfaces de repositorio
# → No depende de nada de Android
# → Es el módulo más consumido — extraerlo primero desbloquea todo lo demás
# → Riesgo: bajo. Es solo clases Kotlin.
# 2. :core:ui
# → Componentes visuales compartidos, el Theme, colores, tipografía
# → Depende de Android pero no de la lógica de la app
# → Riesgo: bajo. Si algo se rompe, es visible inmediatamente.
# 3. :core:network
# → OkHttpClient, Retrofit base, interceptors
# → Depende de librerías externas, no de features
# → Riesgo: medio. Requiere ajustar algunos imports.
# 4. Una feature estable que no cambia seguido
# → El módulo de "Ajustes" o "Perfil" suele ser buen candidato inicial
# → Riesgo: medio-alto. Primer contacto real con dependencias circulares.
# 5. Las features más activas (checkout, productos, etc.)
# → Dejás para el final lo que más cambia y más dependencias tiene
# → Ya tenés experiencia de los módulos anteriores
El proceso para extraer un módulo
# Antes de mover una sola línea de código, mapear las dependencias:
# 1. Identificar qué clases van en el nuevo módulo
# Ej: todo en el paquete ar.pensa.app.domain.*
# 2. Listar qué importa ese código (qué necesita)
# → ¿Importa android.*? → necesita módulo Android library
# → ¿Solo Kotlin/coroutines? → puede ser java-library (más liviano)
# → ¿Importa Room, Retrofit? → necesita esas dependencias en el nuevo módulo
# 3. Listar qué usa ese código (quién lo consume)
# → ¿Solo el :app? → la extracción es simple
# → ¿Todo el proyecto? → el nuevo módulo lo van a usar todos → hacelo :core
# 4. Crear el módulo con la estructura correcta
# 5. Mover el código
# 6. Corregir errores de compilación
# 7. Verificar que los tests siguen pasando
Crear el módulo — la mecánica
# Android Studio: File → New → New Module → Android Library (o Java/Kotlin Library)
# Nombre: core-domain, core-ui, feature-checkout, etc.
# Android Studio crea la estructura y agrega a settings.gradle automáticamente
// settings.gradle — verificar que el módulo quedó declarado
include(":app")
include(":core:domain") // nuevo
include(":core:ui") // nuevo
// :core:domain/build.gradle — módulo Kotlin puro
plugins {
alias(libs.plugins.android.miapp.kotlin.library) // convention plugin
// O si no tenés convention plugins todavía:
id("java-library")
alias(libs.plugins.kotlin.jvm)
}
dependencies {
implementation(libs.kotlinx.coroutines.core)
}
// :app/build.gradle — agregar la dependencia al nuevo módulo
dependencies {
implementation(project(":core:domain")) // agregar esto
// ... resto de las dependencias
}
Verificar que el módulo es realmente independiente
# La prueba más importante: compilar SOLO el módulo sin el resto del proyecto
./gradlew :core:domain:compileKotlin
# Si compila solo → el módulo es verdaderamente independiente
# Si falla → hay dependencias que no declaraste en su build.gradle
# Ver el grafo de dependencias del módulo:
./gradlew :core:domain:dependencies
# Ver qué módulos dependen de este módulo (dependientes reversos):
./gradlew :app:dependencies | grep "core:domain"
# Correr solo los tests del módulo extraído:
./gradlew :core:domain:test
El módulo que no compila solo no está listoSi el módulo solo compila cuando está el proyecto completo, tiene dependencias implícitas que no declaraste. Esas dependencias van a causar problemas cuando alguien intente reusar el módulo o cuando cambies la estructura del proyecto. Resolvé esto antes de seguir extrayendo.
Iterar gradualmente — no en un sprint
La migración de una app grande a multimodulo no se hace en una semana. El proceso correcto:
# Sprint 1: infraestructura
# → Convention plugins básicos
# → Módulo :core:domain extraído y compilando solo
# → CI/CD actualizado para correr tests por módulo
# Sprint 2-3: core modules
# → :core:ui, :core:network, :core:database
# → Cada extracción se integra, se testea, se hace merge antes de la siguiente
# Sprint 4+: feature modules
# → Una feature por sprint, empezando por la más estable
# → No apresurarse — cada feature trae sus propios desafíos de DI y navegación
Errores comunes en la migración
# Error 1: mover código sin ajustar los imports
# Los package names cambian cuando el código va a un módulo nuevo
# Solución: usar "Move to module" de Android Studio que actualiza los imports
# Error 2: olvidar agregar dependencias al nuevo build.gradle
# El código compilaba en :app porque :app tenía todo en el classpath
# Al moverlo a un módulo propio, las dependencias no están
# Solución: agregar explícitamente cada dependencia que usa el módulo
# Error 3: dependencias circulares no detectadas antes
# :feature:checkout usa algo de :feature:productos
# Al intentar hacer módulos separados, Gradle falla
# Solución: mover el código compartido a :core:domain primero
# Error 4: la app deja de compilar por semanas
# Migrar demasiadas cosas a la vez sin hacer merge frecuente
# Solución: extraer un módulo, hacer merge, verificar que todo funciona, repetir
# Error 5: duplicar configuración de Gradle por no tener convention plugins
# Cada módulo tiene su propio build.gradle con las mismas 40 líneas
# Solución: crear convention plugins ANTES de crear más módulos