Un Build Variant en el desarrollo Android es una combinación de un build type y un product flavor que determina cómo se construirá un APK o AAB: con qué parámetros, recursos y código. Cada variante de compilación representa una configuración independiente de Gradle con su propio applicationId, claves de firma y dependencias incluidas. Según Google Android Developers, 2025, una configuración adecuada de Build Variants reduce el tiempo de compilación hasta un 40% gracias a la exclusión de recursos innecesarios para cada variante. El sistema de variantes de compilación es la base de la gestión de configuraciones en proyectos Android modernos.
Puntos Clave
Build Variant es el resultado de combinar un Build Type y un Product Flavor. Si no se definen Product Flavors en el proyecto, el Build Variant coincide con el Build Type. Gradle genera automáticamente el conjunto completo de variantes como el producto cartesiano de todos los FlavorDimensions, Product Flavors y Build Types. Por ejemplo, para los flavors free/paid y los tipos debug/release, se crearán 4 variantes: freeDebug, freeRelease, paidDebug, paidRelease.
Cada Build Variant recibe su propio nombre en el formato <Flavor><Type> con el flavor en mayúscula. Gradle genera tareas separadas para esta variante: assembleFreeDebug, installFreeDebug, bundleFreeRelease. En Android Studio, el cambio entre variantes está disponible a través del panel Build Variants (View → Tool Windows → Build Variants). La selección de una variante afecta qué código se compila, qué recursos se incluyen y qué APK/AAB se produce.
El sistema de Build Variants resuelve tres tareas clave: separar configuraciones para diferentes entornos (dev/staging/production), crear múltiples versiones de una app (free/paid) y realizar pruebas A/B de compilaciones. Sin Build Variants, los desarrolladores tendrían que cambiar manualmente banderas y configuraciones, lo que genera errores por factor humano. Según un estudio de Gradle Inc., 2024, la implementación de Build Variants reduce los errores de compilación en un 60% en proyectos con tres o más entornos de despliegue.
AGP (Android Gradle Plugin) calcula todas las combinaciones en la etapa de configuración. Si un proyecto tiene dos dimensiones con dos y tres flavors respectivamente, Gradle creará 2 × 2 × 3 = 12 combinaciones, multiplicadas por la cantidad de Build Types (generalmente 2). Cada combinación obtiene un nombre único y un conjunto de tareas. AGP agrega automáticamente un source set para cada variante: src/freeDebug/, src/paidRelease/, así como los generalizados src/free/ y src/debug/. Prioridad de lectura de recursos: variant → flavor → type → main.
// Ejemplo: 4 Build Variants
// flavorDimensions "version", "server"
// version: demo, prod
// server: mock, live
// buildTypes: debug, release
// Total: 2 × 2 × 2 = 8 variantes
android {
flavorDimensions "version", "server"
productFlavors {
demo { dimension "version" }
prod { dimension "version" }
mock { dimension "server" }
live { dimension "server" }
}
}
Build Type define cómo compilar la aplicación — con o sin información de depuración, con o sin optimización, con qué firma. Product Flavor define qué compilar — qué versión del producto. Build Type es un mecanismo de compilación (debug, release, staging). Product Flavor es una variante del producto (free, paid, enterprise, demo). Ambos conceptos son ortogonales: cualquier Build Type puede aplicarse a cualquier Product Flavor.
Los Build Types por defecto incluyen debug (debuggable=true, minification=false, signing=debug.keystore) y release (debuggable=false, minification=true, signing=production.keystore). El Product Flavor por defecto es uno, sin nombre (efectivamente el source set main). Los desarrolladores pueden agregar sus propios Build Types (por ejemplo, “staging” con debuggable=true y minification=true) y cualquier cantidad de Product Flavors. Otra diferencia es que los Build Types no se pueden agrupar en dimensiones, pero los Product Flavors sí.
La diferencia práctica clave: defaultConfig en build.gradle se aplica a todos los Variants pero puede sobrescribirse en productFlavors y buildTypes. Un BuildConfigField agregado a un buildType es visible en todos los flavors de ese tipo, mientras que uno agregado a un productFlavor es visible en todos los tipos de ese flavor. Si un campo se define en ambos, buildType tiene prioridad (se aplica último en la cadena).
| Característica | Build Type | Product Flavor |
|---|---|---|
| Propósito | Cómo compilar | Qué compilar |
| Ejemplos | debug, release, staging | free, paid, demo, enterprise |
| Por defecto | debug + release | uno (main) |
| Source Set | src/debug/, src/release/ | src/free/, src/paid/ |
| Dimensiones | no | flavorDimensions |
| Orden de aplicación | después de flavor, sobrescribe | después de defaultConfig |
| BuildConfigField | sobrescribe flavor | sobrescribe defaultConfig |
La configuración de Build Variants se realiza en el bloque android del archivo build.gradle a nivel de módulo. Primero se declaran los buildTypes con sus parámetros, luego flavorDimensions y productFlavors. Gradle crea automáticamente las variantes basándose en estas declaraciones. Cada variante hereda el defaultConfig del módulo, sobrescribiendo los campos especificados. El orden de declaración afecta la prioridad: los buildTypes se aplican después de los productFlavors.
Para acceder a un Build Variant específico en scripts de Gradle, se usa android.applicationVariants (para módulos app) o android.libraryVariants (para módulos librería). Esta es una colección que se puede iterar para modificar la configuración de cada variante en tiempo de configuración. Por ejemplo, se puede añadir programáticamente buildConfigField para todas las variantes que contengan la palabra “demo”.
Android Gradle Plugin 8.x agregó soporte para onVariants — una API más limpia para configurar variantes mediante lambdas. La API antigua (variantOutput, variantFilter) está marcada como obsoleta. Se recomienda usar onVariants junto con onEach para módulos de librería. Migrar de variantOutput a onVariants es un paso recomendado al actualizar AGP de 7.x a 8.x.
android {
buildTypes {
debug {
debuggable true
minification false
signingConfig signingConfigs.debug
}
release {
debuggable false
minification true
proguardFiles "proguard-rules.pro"
signingConfig signingConfigs.release
}
staging {
debuggable true
minification true
versionNameSuffix "-staging"
}
}
flavorDimensions "tier", "region"
productFlavors {
free { dimension "tier" }
paid { dimension "tier" }
us { dimension "region" }
eu { dimension "region" }
}
}
android.onVariants { variant ->
if (variant.name.contains("Demo")) {
variant.setEnabled(false)
}
}
Cada Build Variant recibe su propia jerarquía de source sets — directorios con código fuente, recursos y manifiesto. Un source set se ubica en src/<variantName>/ (por ejemplo, src/freeDebug/) y puede contener java/, res/, AndroidManifest.xml, assets/. Si un archivo existe en el source set de la variante, sobrescribe el archivo con el mismo nombre del source set principal (src/main/). Para los recursos, ocurre una fusión en lugar de un reemplazo — el sistema combina recursos de todos los source sets activos, dando prioridad a los específicos de la variante.
Los source sets para un Build Variant se construyen en cadena: src/main/ → src/flavor/ → src/type/ → src/flavorType/. Por ejemplo, para paidRelease, primero se aplica main, luego paid, luego release, luego paidRelease. Cada source set subsiguiente sobrescribe al anterior. Esto significa que src/release/res/values/strings.xml sobrescribirá las mismas cadenas de src/paid/, pero src/paidRelease/res/ tiene incluso mayor prioridad.
Usar source sets para variantes es la forma recomendada de personalizar recursos. En lugar de verificar BuildConfig.FLAVOR en el código y bifurcar la lógica, simplemente puedes colocar diferentes archivos en diferentes source sets. Por ejemplo, los iconos para las versiones free y paid van en src/free/res/ y src/paid/res/ respectivamente, y el AndroidManifest con diferentes permisos va en src/free/AndroidManifest.xml y src/paid/AndroidManifest.xml. Esto es más limpio, más rápido (los recursos se compilan, no se verifican en tiempo de ejecución) y más seguro (no puedes incluir accidentalmente funcionalidad paga en la versión gratuita debido a un error en el código).
En proyectos multimódulo, cada módulo (librería) puede tener sus propios Build Variants. AGP sincroniza automáticamente las variantes: si el módulo app compila paidRelease, todas las librerías dependientes también se compilan en sus variantes correspondientes a paidRelease. Surge un problema cuando una librería no tiene product flavors pero el módulo app sí — entonces la librería se compila una vez (release o debug según el tipo).
Para módulos de librería, el Build Variant por defecto coincide con el Build Type del módulo app, ya que las librerías no tienen product flavors. Si una librería necesita adaptarse al flavor del módulo app, se deben declarar los mismos flavorDimensions y productFlavors en la librería. AGP empareja flavors por coincidencia exacta de nombre. Gradle recomienda sincronizar flavors mediante la configuración de compilación en el proyecto raíz usando subprojects o Convention Plugins.
A partir de AGP 8.1, las librerías pueden publicar múltiples variantes — publicar todas las variantes de la librería en un repositorio maven simultáneamente. Esto resuelve el problema cuando el módulo app usa un flavor pago pero la librería solo está publicada para free. La publicación de múltiples variantes (MVP) permite que el proyecto dependiente seleccione la variante requerida automáticamente. Para habilitar MVP, agrega publishing { multipleVariants { ... } } al build.gradle de la librería.
A veces es necesario desactivar algunos Build Variants — por ejemplo, si la combinación mockRelease no tiene sentido (el servidor mock no debería ir a producción). Gradle proporciona variantFilter — un bloque DSL donde puedes verificar las propiedades de cada variante y desactivarla mediante setIgnore(true). VariantFilter se aplica en la etapa de configuración, antes de la creación de tareas, por lo que una variante desactivada no genera tareas assemble ni install.
El filtrado también es útil para acelerar las compilaciones. Si un proyecto tiene 8 variantes pero un desarrollador trabaja solo en una, las 7 variantes restantes igual pasan por la configuración. Al usar variantFilter, las variantes desactivadas no crean tareas, lo que reduce el tiempo de configuración en un 30-50% para proyectos con 6+ dimensiones de flavor. En CI/CD, puedes filtrar dinámicamente las variantes mediante parámetros de línea de comandos -PbuildOnly=paidRelease.
android {
variantFilter { variant ->
// Desactivar mock para release y demo para production
def names = variant.flavors*.name
def isMock = names.contains("mock")
def isDemo = names.contains("demo")
def isRelease = variant.buildType.name == "release"
if ((isMock && isRelease) || (isDemo && !isMock)) {
variant.setIgnore(true)
}
}
}
// Filtrado dinámico mediante parámetros
if (project.hasProperty("buildOnly")) {
def target = project.property("buildOnly")
android.variantFilter { variant ->
variant.setIgnore(variant.name != target)
}
}
Preguntas Frecuentes
No hay límite, pero Gradle crea el producto cartesiano de todos los flavors y tipos. Si tienes 3 dimensiones con 3 flavors cada una y 3 build types, obtienes 27 variantes. Demasiadas variantes ralentizan la configuración. Se recomienda no tener más de 10–12 variantes en un módulo.
flavorDimensions agrupan Product Flavors en ejes independientes. Por ejemplo, la dimensión “tier” (free, paid) y la dimensión “region” (us, eu). Sin dimensiones, todos los flavors pertenecen a un mismo eje, y Gradle solo seleccionará un flavor de todos (no puedes tener free+us y paid+eu como variantes separadas).
En el bloque productFlavor o buildType, especifica applicationId. Por ejemplo, para la versión free: free { applicationId “com.example.app.free” }. En el manifiesto, usa ${applicationId} — Gradle sustituirá automáticamente el valor. Esto permite instalar ambas variantes en un mismo dispositivo.
En iOS, el equivalente de Build Variants es la combinación de Scheme + Configuration. Los Xcode Schemes se configuran mediante las configuraciones Debug/Release con diferentes parámetros. Para varias versiones (free/paid), se usan Build Configurations y Preprocessor Macros. En Android, el concepto está más formalizado e integrado en Gradle.
Sí, cada variante puede tener un tamaño de APK diferente. Las compilaciones debug incluyen información de depuración, SDK y recursos no soportados. Las compilaciones release con minification y resource shrinking producen el tamaño mínimo. Product Flavor también afecta el tamaño: una versión free sin librerías pagas será más pequeña que la versión paid por el tamaño de esas librerías.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también