Product Flavor en el desarrollo de Android es un mecanismo de Gradle que permite crear múltiples variantes de la misma aplicación desde una base de código compartida. Cada flavor puede tener su propio applicationId, recursos, dependencias y funcionalidad — por ejemplo, versiones gratuita y de pago. Según Google Android Developers, 2025, los Product Flavors forman parte del sistema Build Variants y se combinan con los Build Types a través de flavorDimensions. Este es el enfoque estándar para publicar múltiples versiones de una aplicación en Google Play.
Puntos Clave
Product Flavor es una configuración de Gradle en el bloque android.productFlavors que describe una variante de producto. Cada flavor puede sobrescribir applicationId, versionName, versionCode, minSdkVersion, targetSdkVersion, signingConfig y otros parámetros de defaultConfig. Los Product Flavors no tienen límite de cantidad: un proyecto puede contener 2, 5 o 10 flavors — Gradle maneja todas las combinaciones.
Product Flavor resuelve el problema de reutilización de código base (codebase reuse) — cuando se necesita compilar varias aplicaciones diferentes desde un solo repositorio. Escenarios típicos: una versión gratuita con anuncios y una de pago sin ellos; una versión demo con funcionalidad limitada; versiones corporativa y de consumo; aplicaciones white-label para diferentes clientes. Sin Product Flavors, cada versión tendría que mantenerse en un proyecto separado, lo que genera una duplicación de código del 60-70%.
Históricamente, los Product Flavors aparecieron en Android Gradle Plugin 0.9 (2013) como reemplazo de las configuraciones ant. Antes de eso, los desarrolladores usaban proyectos separados para diferentes versiones o reemplazo manual de recursos antes de la compilación. La introducción de flavors en AGP unificó el enfoque y lo convirtió en el estándar. Según una encuesta de JetBrains, 2024, el 78% de los proyectos Android con múltiples versiones usan Product Flavors, mientras que el resto usa conmutación manual mediante BuildConfig o reflection.
Build Type gestiona el proceso de compilación (debug con depuración, release con optimización). Product Flavor gestiona el contenido de la compilación (free sin funciones de pago, paid con ellas). Build Type es una configuración de infraestructura, Product Flavor es una configuración de producto. Ambos conceptos son ortogonales: una compilación debug del flavor free difiere de una compilación release del flavor free solo en parámetros de compilación, no en funcionalidad. Product Flavor no se puede usar para deshabilitar el depurador — esa es tarea de Build Type.
Flavor Dimensions son un mecanismo de agrupación de Product Flavors en categorías independientes. Si una aplicación tiene versión gratuita/de pago y por separado una región americana/europea, los flavors se agrupan en dos dimensiones: "tier" (free, paid) y "region" (us, eu). Gradle crea el producto cartesiano de las dimensiones: freeUs, freeEu, paidUs, paidEu — 4 variantes. Sin dimensiones, Gradle trataría los cuatro flavors como un solo plano, y solo se podría seleccionar uno.
Las dimensiones se declaran en el bloque flavorDimensions como una cadena o lista de cadenas. El orden de las dimensiones afecta la prioridad de los source sets: la primera dimensión tiene la prioridad más alta. Si la dimensión A (tier) se especifica primero, entonces src/free/ sobrescribirá src/us/ en caso de conflictos de recursos. El orden también afecta cómo se forma el nombre del Variant: primero vienen los flavors de la primera dimensión, luego los de la segunda, luego Build Type: freeUsDebug.
El número de dimensiones no está limitado, pero cada nueva dimensión multiplica la cantidad de Build Variants. Para un proyecto con 4 dimensiones (2 flavors cada una) y 2 build types, se obtienen 2 × 2 × 2 × 2 × 2 = 32 variantes. El límite práctico es 3 dimensiones (máximo 8-12 variantes). Más allá, la configuración de Gradle se ralentiza y el panel Build Variants en Android Studio se vuelve ilegible.
android {
flavorDimensions "tier", "api"
productFlavors {
free {
dimension "tier"
applicationId "com.example.app.free"
versionNameSuffix "-free"
}
paid {
dimension "tier"
applicationId "com.example.app.paid"
}
minApi21 {
dimension "api"
minSdk 21
}
minApi26 {
dimension "api"
minSdk 26
}
}
}
// Resultado: freeMinApi21, freeMinApi26, paidMinApi21, paidMinApi26
// Cada × debug/release = 8 Build Variants
Para crear un Product Flavor, es necesario agregar un bloque productFlavors dentro de android, especificar el nombre del flavor y sus parámetros. La declaración mínima de flavor es el nombre y la dimensión. Todos los demás parámetros se heredan de defaultConfig y pueden sobrescribirse. El flavor hereda defaultConfig por completo, incluyendo applicationId, versionCode, testInstrumentationRunner.
Cada flavor puede sobrescribir applicationId — esto permite instalar múltiples versiones de la aplicación en el mismo dispositivo simultáneamente. Por ejemplo, la versión free será com.example.app.free, paid — com.example.app.paid. Si no se sobrescribe applicationId, todos los flavors tendrán el mismo identificador y no se podrán instalar lado a lado. applicationId debe coincidir con el package en el manifiesto (a menos que se use applicationIdSuffix).
AGP 8+ recomienda usar Kotlin DSL en lugar de Groovy para build.gradle. Kotlin DSL proporciona acceso type-safe a la configuración: el IDE sugiere nombres de parámetros, verifica tipos en tiempo de compilación y resalta errores. La migración de Groovy a Kotlin DSL para Product Flavors generalmente consiste en reemplazar comillas por paréntesis y agregar tipos. AGP es compatible hacia atrás — ambas sintaxis funcionan en paralelo dentro del mismo proyecto.
// build.gradle.kts — Kotlin DSL
android {
flavorDimensions += "tier"
productFlavors {
register("free") {
dimension = "tier"
applicationId = "com.example.app.free"
versionNameSuffix = "-free"
buildConfigField("boolean", "IS_PREMIUM", "false")
}
register("paid") {
dimension = "tier"
applicationId = "com.example.app.paid"
versionNameSuffix = "-paid"
buildConfigField("boolean", "IS_PREMIUM", "true")
}
}
}
Cada Product Flavor crea su propio source set — un directorio src/<flavorName>/. Este directorio puede contener recursos sobrescritos, archivos fuente y el manifiesto. El source set del flavor actúa como una superposición sobre main: los archivos de src/free/res/ sobrescriben los archivos de src/main/res/ con los mismos nombres. Esto permite tener diferentes cadenas, iconos, colores y diseños para cada flavor sin modificar el código principal.
Para sobrescribir clases Java/Kotlin, existen dos enfoques: implementación específica de flavor (implementar una clase abstracta en cada flavor) y campo BuildConfig (ramificación en código). El primer enfoque es más limpio: se define una interfaz o clase abstracta en main, y las implementaciones concretas en src/free/ y src/paid/. Durante la compilación, solo se compila la implementación del flavor actual. Esto proporciona beneficios simultáneos: menor tamaño del APK (el código de pago no entra en la versión free) y seguridad (es imposible llamar accidentalmente a una función de pago).
AndroidManifest.xml en un source set de flavor no reemplaza sino que se fusiona con el manifiesto principal. La fusión sigue las reglas de Android: los atributos duplicados en el mismo elemento se sobrescriben, los únicos se agregan. Por ejemplo, si el manifiesto principal declara el permiso INTERNET y free no, el permiso de internet permanece. Sin embargo, tools:node="replace" permite reemplazar un bloque completo del manifiesto para un flavor específico. Esto es útil cuando diferentes flavors requieren diferentes permisos (escritura en SD para paid, cámara para free).
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:label="Free App"
tools:replace="android:label">
</application>
</manifest>
Consideremos un escenario típico: free — una versión con anuncios y funciones básicas, paid — sin anuncios, con funcionalidad extendida. Para la versión free, applicationId se establece como "com.example.app.free", para paid — "com.example.app.paid". Ambas versiones se pueden instalar en el mismo dispositivo simultáneamente, ya que applicationId es el identificador único de la aplicación en el sistema Android.
Arquitectónicamente, la separación se construye a través de interfaz + implementación de flavor. En el source set main se declara la interfaz PaymentService. En src/free/ hay una implementación que muestra un anuncio antes del pago mediante AdMob. En src/paid/ — una implementación que procede directamente a la pasarela de pago. El código que usa PaymentService no sabe qué implementación está cargada — esto se resuelve en tiempo de compilación. Este enfoque garantiza que el código de gestión de suscripciones no termine en la versión free, incluso si el desarrollador lo llama accidentalmente.
El tamaño del APK para diferentes flavors puede diferir en 5-15 MB debido a la inclusión/exclusión de dependencias. Para excluir una biblioteca de un flavor específico, use dependencias específicas de flavor en build.gradle: freeImplementation 'com.google.android.gms:play-services-ads:23.0.0'. Esta dependencia se agregará solo para la variante free y no aumentará el tamaño de la versión paid. Para dependencias compartidas, use implementation — todos los flavors las incluyen.
// src/main/kotlin/com/example/payment/PaymentService.kt
interface PaymentService {
fun processOrder(amount: Double, callback: (PaymentResult) -> Unit)
}
// src/free/kotlin/.../FreePaymentService.kt
class FreePaymentService : PaymentService {
override fun processOrder(amount: Double, callback: (PaymentResult) -> Unit) {
AdManager.showInterstitial {
PaymentGateway.charge(amount, callback)
}
}
}
// src/paid/kotlin/.../PaidPaymentService.kt
class PaidPaymentService : PaymentService {
override fun processOrder(amount: Double, callback: (PaymentResult) -> Unit) {
PaymentGateway.charge(amount, callback)
}
}
En proyectos multimódulo, los módulos de biblioteca pueden no tener sus propios Product Flavors, lo que crea un problema: la biblioteca se compila una vez (como release), mientras que el módulo app con flavor espera la biblioteca con la variante correspondiente. A partir de AGP 8.1, las bibliotecas pueden publicar múltiples variantes a través del bloque publishing.multipleVariants — esto permite publicar todas las variantes de flavor de la biblioteca en un solo repositorio maven, y el módulo app seleccionará automáticamente la correcta.
Un enfoque alternativo es declarar los mismos flavorDimensions y productFlavors en la biblioteca que en el módulo app. AGP empareja automáticamente los flavors por coincidencia exacta de nombre dentro de una dimensión. Si el nombre del flavor en la biblioteca coincide con el nombre en la app, AGP creará variantes consistentes. Para facilitar el mantenimiento, se recomienda extraer las definiciones comunes de flavor en un Convention Plugin — un plugin de Gradle aplicado a todos los módulos del proyecto.
Para bibliotecas no destinadas a publicación (módulos internos), es suficiente sincronizar los flavors a través del build.gradle del proyecto raíz. Gradle proporciona el método subprojects, que permite aplicar configuración a todos los subproyectos. Sin embargo, tenga en cuenta que demasiada configuración en subprojects ralentiza la fase de configuración. Se recomienda usar Convention Plugins — se compilan una vez y se reutilizan, reduciendo el tiempo de configuración en un 15-30%.
Preguntas Frecuentes
No hay límite en la cantidad, pero cada dimensión multiplica el número de Build Variants. 4 flavors en una dimensión + 2 build types = 8 variantes. 4 + 4 en dos dimensiones = 16 variantes. Se recomienda no usar más de 3 dimensiones y 10-12 variantes totales.
Sí, a través del source set src/<flavor>/AndroidManifest.xml. El manifiesto se fusiona con el principal. Para reemplazar un bloque completo, use tools:node="replace". Por ejemplo, reemplace la etiqueta de la aplicación o los permisos para un flavor específico.
Use la configuración <flavorName>Implementation. Ejemplo: freeImplementation 'com.google.android.gms:play-services-ads:23.0.0'. Esta dependencia se incluirá solo al compilar la variante free. Para paid: paidImplementation. Las dependencias comunes se especifican mediante implementation.
Product Flavor define la versión del producto (free, paid, demo), Build Type define el método de compilación (debug, release). Los flavors pueden sobrescribir applicationId, versionName, recursos. Build Type controla debuggable, minification, signing. Ambos son ortogonales y se combinan en Build Variant.
Sí, los Product Flavors funcionan con Compose sin restricciones. Diferentes flavors pueden tener diferentes pantallas de Compose a través de source sets o implementaciones de clases abstractas. También se pueden agregar dependencias de Compose específicas de flavor: freeImplementation 'androidx.compose.ui:ui-tooling'.
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