¿Por qué y cómo dividirías una app Android en crecimiento en múltiples módulos de Gradle?
Un módulo :app monolítico compila todo como un solo bloque y deja que cualquier pantalla toque cualquier otra — modularizar es cómo los equipos grandes de Android recuperan velocidad de build y límites claros.
APRENDE HACIENDOPon a prueba tu intuición3 pasos
¿Por qué y cómo dividirías una app Android en crecimiento en múltiples módulos de Gradle?
Usa los ejemplos y la explicación de la lección para comprobar tu idea.
Reflexión personal. No se envía a ningún servicio de IA.#Por qué un solo módulo :app deja de escalar
Todo proyecto Android arranca como un solo módulo de Gradle, y durante un tiempo es la decisión correcta — todavía no hay ningún límite que valga la pena imponer. Pero cuando el código crece más allá de un puñado de desarrolladores y unas cuantas docenas de pantallas, ese único módulo empieza a jugar en tu contra de tres maneras concretas. Primero, los tiempos de build: Gradle solo puede paralelizar y recompilar de forma incremental a nivel de módulo, así que tocar un archivo en una app de un solo módulo obliga a Gradle a reconsiderar recompilar y relinkear todo lo que depende de él, y cada build incremental (y cada build limpio de CI) paga el costo de toda la app aunque hayas cambiado una sola pantalla. Segundo, los límites: sin paredes entre módulos, nada impide que la pantalla de checkout importe una clase interna del paquete de la pantalla de perfil, así que los detalles "internos" se filtran por todos lados y refactorizar se convierte en arqueología. Tercero, la propiedad del equipo y el CI: con un solo módulo, "correr los tests" significa "correr todos los tests," y el code review no tiene ninguna costura natural que mapee a "quién es dueño de esto."
Dividir la app en módulos de Gradle ataca los tres problemas. El grafo de dependencias de Gradle se vuelve un contrato explícito — un módulo solo puede ver la API pública de los módulos de los que depende — y la build cache y la configuration cache de Gradle pueden saltarse trabajo para módulos cuyas entradas no cambiaron. Esa es la razón mecánica por la que a los entrevistadores les importa esto: modularizar no es una preferencia de estilo, es una funcionalidad de rendimiento de build que trae límites arquitectónicos como efecto secundario.
#La forma a la que converge la mayoría de los equipos
La capa común — usada por el propio sample "Now in Android" de Google y por la mayoría de las apps grandes en producción — tiene tres tipos de módulos:
:app— la raíz de composición, delgada. Conecta laApplication, el grafo de inyección de dependencias y el host de navegación. Debería contener casi nada de lógica de negocio.:feature:*— un módulo por feature de cara al usuario (:feature:home,:feature:checkout,:feature:profile). Cada uno es dueño de sus propias pantallas, ViewModels y lógica específica de esa feature.:core:*— bloques compartidos, agnósticos de feature::core:ui(design system, composables compartidos),:core:network(configuración de Retrofit/OkHttp),:core:database(Room),:core:common(utilidades, clases base, sin dependencia de Android cuando se puede evitar, para poder testear en la JVM).
La regla de dependencia es direccional: las features dependen del core, nunca al revés, y los módulos core nunca saben que existe un módulo de feature. Esa única regla es lo que evita que el grafo de dependencias vuelva a convertirse en un monolito con carpetas extra.
Fijate que :feature:checkout no depende de todo :feature:profile — depende de un módulo delgado :feature:profile:api que expone solo las interfaces y data classes que checkout realmente necesita (digamos, interface ProfileSummaryProvider). El módulo real :feature:profile implementa esa interfaz y se conecta a nivel de :app vía inyección de dependencias. Ese salto extra es lo que permite la comunicación entre features sin que dos features compilen contra los internals de la otra, y también es lo que hace posible borrar o reemplazar un módulo de feature sin tocar a sus vecinos.
#La mecánica que hace que funcione en la práctica
Dos detalles de Gradle deciden si modularizar realmente vale la pena:
apivs.implementation. Declarar una dependencia conimplementation(la opción por defecto, y casi siempre la correcta) la mantiene fuera del classpath de compilación que ven los consumidores aguas abajo — así que si la implementación de:core:networkcambia de versión de OkHttp, solo:core:networkrecompila, no cada feature que depende transitivamente de él. Usarapivuelve a exponer esa dependencia a todo lo que está aguas abajo y reintroduce silenciosamente el acoplamiento que modularizaste para evitar.- Plugins de convención. Treinta módulos de feature necesitan bloques
android { compileSdk = ...; minSdk = ...; buildFeatures { compose = true } }casi idénticos. Copiar y pegar eso treinta veces significa que actualizar la versión del SDK es un diff de treinta archivos. Un composite build (un included buildbuild-logic, el reemplazo moderno debuildSrc) define un puñado de plugins de convención de Gradle —android-feature,android-library-compose,jvm-library— que cada módulo aplica con una sola línea, así que la configuración de build compartida vive en exactamente un solo lugar.
#Más allá de la velocidad de build: entrega
Modularizar también habilita Play Feature Delivery: un módulo :feature:* puede convertirse en un dynamic feature module, descargado bajo demanda en vez de ir incluido dentro del APK/AAB base — útil para una feature poco usada (digamos, un flujo de onboarding de una sola vez, o una feature de prueba en AR) que no debería inflar el tamaño de instalación de todos los usuarios. Eso solo es posible una vez que la feature ya tiene un límite de módulo limpio; es una razón más por la que los entrevistadores tratan la modularización como una decisión de arquitectura fundacional y no como un "nice to have."