Gradle: flavors y variantes
Al terminar: Crear dos flavors (Gratis y Pro) con sus carpetas y recursos, y compilar las cuatro variantes.
Marcador ya se construye de dos maneras, debug y release. Hoy se construye también en dos versiones: una «gratis» y otra «pro», cada una con su identificador, su nombre en el cajón de aplicaciones y sus datos propios, y las dos instaladas a la vez en el mismo móvil.
Al terminar, la pantalla de Marcador mostrará una línea como Modo: DEBUG · plan gratis · com.sebastiannarvaez.marcador.debug, y podrás compilar las cuatro variantes que salen de combinar versiones y tipos de compilación. Temas: product flavors, conjuntos de código (src/pro) y los comandos que cambian.
Product flavor: qué versión se construye
Hay dos ejes independientes.
- Build type: cómo se construye (
debugorelease). Es lo que hiciste en la lección anterior. - Product flavor: qué versión de la app se construye (gratis o pro). Puede tener su propio identificador, sus constantes de
BuildConfig, su código y sus recursos.
Con dos flavors y dos build types salen cuatro variantes, cada una con su applicationId:
gratisDebug com.sebastiannarvaez.marcador.debug
proDebug com.sebastiannarvaez.marcador.pro.debug # el sufijo del flavor va antes que el del build type
gratisRelease com.sebastiannarvaez.marcador # sin sufijos: ni gratis ni release añaden ninguno
proRelease com.sebastiannarvaez.marcador.pro # solo el sufijo de proEl nombre de la variante en Gradle es el del flavor en minúsculas seguido del build type con la primera letra en mayúscula: gratis + Debug = gratisDebug. Lo verás en las tareas y en las rutas del apk.
Antes de escribirlo en Marcador, así se declaran dos flavors en otra app inventada, con otros nombres (flavors demo y completa). No es el código de Marcador:
android {
// += añade un elemento a una lista que ya existe (aquí, la lista de dimensiones)
// Una dimensión es un grupo de flavors; de cada dimensión se elige uno por variante
flavorDimensions += "edicion"
productFlavors {
create("demo") { // create(nombre) crea un flavor con ese nombre
dimension = "edicion"
// Constante que el código lee como BuildConfig.EDICION; las comillas del texto llevan barra (\"), como en la lección anterior.
buildConfigField("String", "EDICION", "\"demo\"")
}
create("completa") {
dimension = "edicion"
applicationIdSuffix = ".completa" // otro identificador: las dos apps conviven
buildConfigField("String", "EDICION", "\"completa\"")
}
}
}Reto · Ejercicio: los cuatro nombres de una variante
En el Kotlin Playground, fuera de tu proyecto:
- Guarda en una lista los dos flavors, gratis y pro, y en otra los dos build types, debug y release.
- Recorre todas las combinaciones e imprime el nombre de cada variante en la forma de Gradle: el flavor en minúsculas seguido del build type con la primera letra en mayúscula.
- En la misma línea, tras una flecha, imprime su
applicationId, sabiendo que la base escom.sebastiannarvaez.marcador, que pro añade.proy que debug añade.debug(primero el sufijo del flavor).
Lo que debe salir:
gratisDebug -> com.sebastiannarvaez.marcador.debug
gratisRelease -> com.sebastiannarvaez.marcador
proDebug -> com.sebastiannarvaez.marcador.pro.debug
proRelease -> com.sebastiannarvaez.marcador.proPista, una pieza nueva: para poner en mayúscula solo la primera letra de un texto, Kotlin tiene replaceFirstChar { it.uppercase() } (uppercase() ya lo conoces; it es cada carácter, como en las lambdas de Lambdas). Por ejemplo, "debug".replaceFirstChar { it.uppercase() } da Debug.
Ver solución
fun main() {
val flavors = listOf("gratis", "pro")
val tipos = listOf("debug", "release")
val base = "com.sebastiannarvaez.marcador"
for (flavor in flavors) {
for (tipo in tipos) {
val nombre = flavor + tipo.replaceFirstChar { it.uppercase() }
val sufijoFlavor = if (flavor == "pro") ".pro" else ""
val sufijoTipo = if (tipo == "debug") ".debug" else ""
println("$nombre -> $base$sufijoFlavor$sufijoTipo")
}
}
}Salen cuatro líneas, una por variante. Las de debug tienen que coincidir con las de Marcador: gratisDebug con com.sebastiannarvaez.marcador.debug y proDebug con com.sebastiannarvaez.marcador.pro.debug. Que sean cuatro y no dos es la idea de la lección: el número de variantes es el producto de flavors por build types.
Paso · Los flavors gratis y pro
Objetivo. Los flavors gratis y pro, el texto propio de la versión pro, y mostrar el modo, el plan y el applicationId: Modo: DEBUG · plan gratis · com.sebastiannarvaez.marcador.debug.
Intenta escribirlo tú. Dos flavors de la misma dimensión, gratis y pro, que dejan en BuildConfig su plan; pro, además, con su propio identificador de app (el de siempre más .pro). Y que la pantalla muestre, además del modo y el identificador, el plan. Lo que debes ver con la variante gratis de debug:
Modo: DEBUG · plan gratis · com.sebastiannarvaez.marcador.debug1 · Los flavors en build.gradle.kts
En build.gradle.kts (Module :app), dentro de android { … }, debajo de defaultConfig { … } y encima de buildTypes { … }:
Intenta primero. Cuando termines, compara con la solución.Mostrar el código
// FLAVORS: versiones distintas de la MISMA app (gratis y de pago, por ejemplo).
// Una «dimension» es un grupo de flavors entre los que se elige uno (+= añade a una lista).
// Las variantes salen de multiplicar flavor x build type: gratisDebug, gratisRelease, proDebug, proRelease.
flavorDimensions += "plan"
productFlavors {
create("gratis") { // create crea un flavor con ese nombre
dimension = "plan" // todo flavor pertenece a una dimension
// buildConfigField(tipo, nombre, valor): una constante que el codigo lee como BuildConfig.PLAN.
// BuildConfig es Java y el valor se copia TAL CUAL: por eso las comillas del texto van escapadas (\").
buildConfigField("String", "PLAN", "\"gratis\"")
}
create("pro") {
dimension = "plan"
// Se suma al applicationId: com.sebastiannarvaez.marcador.pro. Sin sufijo
// distinto, gratis y pro serian «la misma app» para Android y una pisaria
// a la otra al instalarla. Ademas habra un app_name distinto en src/pro.
applicationIdSuffix = ".pro"
buildConfigField("String", "PLAN", "\"pro\"")
}
}Pulsa Sync Now. Debe terminar sin errores rojos.
2 · Los recursos del flavor pro
El flavor pro lleva su propio app_name (el texto que sale bajo el icono), en su conjunto de código (source set): una carpeta src/<nombre> que se suma a src/main (todo lo que ves hasta hoy en la vista Android) al construir esa variante. Gradle ya creó el conjunto pro al declarar el flavor, aunque su carpeta no exista aún. Créala desde la vista Android:
- Clic derecho sobre
app → res→ New → Android Resource File. - En el diálogo, rellena tres campos: File name (nombre del fichero):
strings; Resource type (tipo de recurso):Values; y Source set (conjunto de código):pro, nomain. Los demás campos se dejan como salen. Si el desplegable de Source set no ofrecepro, repite Sync Now y mira quebuild.gradle.kts (Module :app)no tenga rojo. - Acepta. El fichero queda en
app/src/pro/res/values/strings.xml; en la vista Android sale bajores → values, junto alstrings.xmlque ya tenías: abre el que la vista Android rotulastrings.xml (pro).
Antes de escribirlo, así se ve un fichero de textos en otra app de verdad, una calculadora:
<resources>
<!-- name es la clave: el manifiesto y el código piden el texto por ese nombre
(@string/app_name en el manifiesto, R.string.app_name en Kotlin). -->
<string name="app_name">Calculadora</string>
</resources>El fichero nace con un <resources> vacío. Intenta escribirlo tú: un único texto, con el mismo nombre que el de la app en main (app_name), que diga «Marcador Pro». Así la variante pro sale en el cajón de aplicaciones con ese nombre.
Intenta primero. Cuando termines, compara con la solución.Mostrar el código
<resources>
<!-- Este fichero solo existe en el flavor «pro»: sustituye al app_name de main. -->
<string name="app_name">Marcador Pro</string>
</resources>3 · MainActivity.kt
Abre app → kotlin+java → com.sebastiannarvaez.marcador → MainActivity.kt. Ya tiene la línea Text("Modo: …") de la lección anterior; solo falta el plan:
Intenta primero. Cuando termines, compara con la solución.Mostrar el código
Text("Modo: ${BuildConfig.MODO} · plan ${BuildConfig.PLAN} · ${BuildConfig.APPLICATION_ID}")Con flavors, cambian los comandos
Sin flavors existía la variante debug; ahora hay cuatro, y cambian los comandos y las rutas. Con el emulador arrancado como en La Activity, en la Terminal de Android Studio (en la raíz, donde está gradlew):
# Antes, assembleDebug construía la app; ahora construye LAS DOS de debug (y tarda más). Para una sola:
./gradlew :app:assembleGratisDebug
# El apk ya no está en apk/debug/app-debug.apk, sino en apk/gratis/debug/ (y apk/pro/debug/app-pro-debug.apk)
adb install -r app/build/outputs/apk/gratis/debug/app-gratis-debug.apk
# am start arranca una pantalla y -n dice cuál: el paquete es el applicationId; la clase, la del namespace
adb shell am start -n com.sebastiannarvaez.marcador.debug/com.sebastiannarvaez.marcador.MainActivityGradle también trae una tarea que construye e instala de una vez: ./gradlew :app:installGratisDebug.
Y el botón Run. Al añadir los flavors, la variante elegida por defecto es gratisDebug (compruébalo en la ventana de abajo), así que Run instala la gratis. Para probar la pro, cámbiala en View → Tool Windows → Build Variants (fila de :app, columna «Active Build Variant»; los menús pueden variar entre versiones).
La pantalla dice Modo: DEBUG · plan gratis · com.sebastiannarvaez.marcador.debug. Instala también la versión pro y comprueba que conviven:
./gradlew :app:assembleProDebug
adb install -r app/build/outputs/apk/pro/debug/app-pro-debug.apk
adb shell pm list packages marcador
adb shell am start -n com.sebastiannarvaez.marcador.pro.debug/com.sebastiannarvaez.marcador.MainActivitySalen com.sebastiannarvaez.marcador.debug y com.sebastiannarvaez.marcador.pro.debug. La pro dice Modo: DEBUG · plan pro · com.sebastiannarvaez.marcador.pro.debug.
En el cajón de aplicaciones del emulador verás las dos Marcador, y también los iconos «Leaks» de LeakCanary, de Fugas de memoria: cada versión de depuración instalada añade el suyo. La pro es Marcador Pro porque su strings.xml de src/pro/res sustituye al app_name de main: los ficheros de src/<flavor>/res se suman a los de src/main/res, y los del mismo nombre ganan.
Tres avisos:
- Datos y permisos empiezan de cero. La app pro es «otra app» para Android: no comparte datos ni permisos con la gratis, y hay que volver a conceder
POST_NOTIFICATIONS. - Tiempos. La primera compilación completa tras añadir los flavors tarda unos 30 segundos.
- Release.
./gradlew :app:assembleGratisReleasecompila, pero el apk sale sin firmar y no se puede instalar tal cual; por eso en pantalla solo verásModo: DEBUG.PRODUCCIONaparece cuando publicas una versión firmada, algo que esta lección no cubre.
Punto de control
build.gradle.kts (Module :app)tiene los flavorsgratisypro, los dos build types,buildConfig = trueeimplementation(libs.bundles.compose). Sync Now terminó sin errores rojos.- Desde la Terminal,
./gradlew :app:assembleGratisDebugtermina enBUILD SUCCESSFUL, y:app:assembleProDebugtambién. - La pantalla de la variante gratis muestra
Modo: DEBUG · plan gratis · com.sebastiannarvaez.marcador.debug, y la pro,Modo: DEBUG · plan pro · com.sebastiannarvaez.marcador.pro.debug. - Con
adb shell pm list packages marcadorsalen los paquetes.debugy.pro.debuga la vez, y en el cajón de aplicaciones la pro se llama Marcador Pro. - Si comentas la línea
buildConfig = true(con//delante) y ejecutas./gradlew :app:compileGratisDebugKotlin, saleBuild Type 'debug' contains custom BuildConfig fields, but the feature is disabled.Quita el//antes de seguir.
Comprueba lo que entendiste
4 preguntas · necesitas 3 para completar la lección
1.Un proyecto tiene los flavors
gratisyproy los build typesdebugyrelease. ¿Cuántas variantes se construyen?2.Sin flavors,
./gradlew :app:assembleDebugconstruía la app. Con los flavorsgratisypro, ¿qué hace ese comando?3.La variante pro de Marcador sale en el cajón de aplicaciones como «Marcador Pro». ¿De dónde sale ese nombre?
4.Con los flavors
gratisypro, ¿cómo se llama la variante deprocon el build typerelease?