Gradle: build types y BuildConfig
Al terminar: Entender los build types, el bundle de dependencias y BuildConfig.
Hasta ahora has tocado Gradle a trozos. Hoy lo usas para que la misma app salga de dos maneras: una de depuración y otra de producción, que conviven en el mismo móvil, y con datos que solo conoce la compilación, como el modo o el identificador de la app.
Al terminar, la pantalla de Marcador mostrará una línea como Modo: DEBUG · com.sebastiannarvaez.marcador.debug. En la lección siguiente, Gradle: flavors y variantes, esa misma línea ganará un dato más y la app tendrá dos versiones, «gratis» y «pro». Temas: build types, BuildConfig y los bundles del catálogo.
Build type: cómo se construye la app
Una misma app se puede construir de maneras distintas. Cada versión concreta que Gradle puede construir se llama variante; hoy las variantes son simplemente debug y release, y la lección siguiente añade otra forma de combinarlas. La de depuración (debug) es la que Android Studio instala en tu emulador cuando pulsas Run: lleva ayudas para ti, como LeakCanary. La de producción (release) es la que se publica y la que instalan los usuarios: sin ayudas, y con la firma y el recorte de código que veremos ahora. Cada una de esas maneras es un build type (tipo de compilación), y debug y release existen siempre en un proyecto de Android.
Dos palabras que saldrán enseguida. La firma es un sello digital, hecho con una clave tuya, que Android exige para instalar una app publicada. Ofuscar es renombrar clases y funciones a nombres cortos e ilegibles para que cueste leerlas y la app pese menos; R8 es la herramienta que lo hace (y recorta el código que nadie usa).
El applicationId, la identidad de la app en Android, puede ser distinto en cada build type. Android identifica una app por su applicationId: con el mismo, la segunda instalación pisaría a la primera. Con un sufijo distinto (applicationIdSuffix) la de depuración y la de producción son «dos apps» y conviven en el mismo móvil:
debug com.sebastiannarvaez.marcador.debug # el sufijo .debug lo añade el build type debug
release com.sebastiannarvaez.marcador # sin sufijo: es el identificador de siempreBuildConfig: datos que solo conoce la compilación
BuildConfig es una clase de constantes que Gradle genera al compilar (siempre trae APPLICATION_ID; tú añades las tuyas con buildConfigField). Así el código sabe en qué variante está sin adivinarlo.
Antes de escribirlo en Marcador, así se declaran un build type y un campo de BuildConfig en otra app inventada. No es el código de Marcador:
android {
buildTypes {
debug { // debug ya existe: aquí solo se ajusta
applicationIdSuffix = ".debug" // se suma al applicationId de la app
versionNameSuffix = "-debug" // se suma a la versión (1.0 pasa a 1.0-debug)
// Constante que el código lee como BuildConfig.MODO.
// El valor se pega TAL CUAL en un fichero de Java, donde un texto va entre comillas.
// Por eso, dentro de las comillas de Kotlin, las del texto llevan una barra delante (\"): la barra dice «esta comilla es una letra, no el final del texto».
buildConfigField("String", "MODO", "\"DEBUG\"")
}
}
buildFeatures {
buildConfig = true // sin esta línea, no se genera BuildConfig
}
}Reto · Ejercicio: el nombre de cada build type
En el Kotlin Playground, fuera de tu proyecto. La base del identificador es com.sebastiannarvaez.marcador y la versión, 1.0. El build type debug añade .debug al identificador y -debug a la versión; release no añade nada. Imprime una línea por build type con su identificador y, entre paréntesis, su versión. La consola debe mostrar:
debug -> com.sebastiannarvaez.marcador.debug (1.0-debug)
release -> com.sebastiannarvaez.marcador (1.0)Pista: guarda cada build type con su sufijo en un mapa y recórrelo con for ((tipo, sufijo) in …), como en Colecciones.
Ver solución
fun main() {
val base = "com.sebastiannarvaez.marcador"
val version = "1.0"
val sufijos = mapOf("debug" to ".debug", "release" to "")
for ((tipo, sufijo) in sufijos) {
val versionDelTipo = if (tipo == "debug") "$version-debug" else version
println("$tipo -> $base$sufijo ($versionDelTipo)")
}
}Salen las dos líneas. Es lo mismo que hará Gradle con tus sufijos: el identificador y la versión de cada build type se calculan sumando su sufijo a la base.
Paso · Instala el bundle de Compose
Antes de los build types, un cambio pequeño que te enseña una sección nueva del catálogo, [bundles]: un bundle agrupa librerías que siempre van juntas bajo un solo nombre, como un combo de menú. Compose UI y Material 3 se usan juntas en todo Marcador, así que en vez de dos líneas sueltas habrá una.
1 · Qué instalas. Nada que descargar: las dos librerías ya están; cambias solo cómo se piden.
2 · La sección. En Gradle Scripts → libs.versions.toml (Version Catalog "libs"), entre el final de [libraries] y la cabecera [plugins]:
[bundles]
# Nombre del bundle en minúsculas = lo que se escribe tras libs.bundles. en el código.
# La lista lleva alias de [libraries] con sus GUIONES y entre comillas (no con puntos).
# Sin versiones: cada librería usa la suya (aquí, la que fija el BOM de Compose, una lista de versiones de Compose que van bien juntas).
compose = ["androidx-compose-ui", "androidx-compose-material3"]3 · La dependencia. En dependencies { … } de build.gradle.kts (Module :app) hay dos líneas que se sustituyen por una. Están juntas: material3 y, justo debajo, ui. Sustitúyelas por la línea del bundle:
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.ui) // Las dos librerías del bundle de una vez. Las librerías cuelgan de libs, los plugins de
// libs.plugins y los bundles de libs.bundles. El resto de líneas de Compose se quedan igual.
implementation(libs.bundles.compose)4 · Sync Now. Sin errores rojos, y MainActivity.kt sin nada en rojo. Fallo típico: un alias del bundle con puntos ("androidx.compose.ui") o que no existe en [libraries]; el resto, en la caja «Si Sync sale en rojo» de Los ficheros de Gradle de tu proyecto.
Paso · Build types y BuildConfig
Objetivo. Los dos build types con su sufijo y su modo, BuildConfig generado, y una línea en pantalla: Modo: DEBUG · com.sebastiannarvaez.marcador.debug.
BuildConfig.MODO no lleva import: Gradle genera la clase en el namespace de la app, el paquete de MainActivity. Y verticalScroll(rememberScrollState()) hace la columna desplazable, para que los botones no se corten en un móvil pequeño.
Intenta escribirlo tú. Dos build types: debug, que añade .debug al identificador y -debug a la versión, con el modo «DEBUG», y release, con el modo «PRODUCCION». Que Gradle genere BuildConfig. Y que la pantalla muestre el modo y el identificador. Lo que debes ver con la variante debug:
Modo: DEBUG · com.sebastiannarvaez.marcador.debugSi no pides que se genere BuildConfig, verás el error de la caja «BuildConfig no se genera si no lo pides».
1 · El build.gradle.kts de :app
En build.gradle.kts (Module :app), dos cambios dentro de android { … }.
Los build types. La plantilla ya trae un buildTypes con release: modifica ese (debug es nuevo; en release solo se añade buildConfigField):
buildTypes {
release {
optimization {
enable = false
}
}
}Intenta primero. Cuando termines, compara con la solución.Mostrar el código
// BUILD TYPES: como se construye, no que se construye. `debug` y `release`
// existen siempre y por eso no llevan create: aqui solo se ajustan.
buildTypes {
debug {
// Se suma al applicationId: com.sebastiannarvaez.marcador.debug. Asi la
// version debug y la de produccion conviven instaladas en el mismo movil.
applicationIdSuffix = ".debug"
// Sale en «Informacion de la app»: 1.0-debug.
versionNameSuffix = "-debug"
buildConfigField("String", "MODO", "\"DEBUG\"")
}
release {
// Sin recortar ni ofuscar el codigo (R8) de momento: queda para cuando
// toque publicar, porque hay que probar la app ya recortada.
optimization {
enable = false
}
buildConfigField("String", "MODO", "\"PRODUCCION\"")
}
}Pedir BuildConfig, dentro de buildFeatures { … }, que ya existe con compose = true:
Intenta primero. Cuando termines, compara con la solución.Mostrar el código
buildFeatures {
compose = true
// Pide a AGP (Android Gradle Plugin, el plugin que enseña a Gradle a
// construir apps Android) que genere la clase BuildConfig. Sin esta linea,
// AGP falla al CONFIGURAR el proyecto.
buildConfig = true
}Pulsa Sync Now. Debe terminar sin errores rojos.
2 · MainActivity.kt
Abre app → kotlin+java → com.sebastiannarvaez.marcador → MainActivity.kt. Antes de escribirlo, así se leen constantes de BuildConfig en una columna con scroll. Usa dos que Gradle genera siempre en una app (BUILD_TYPE y DEBUG), no las tuyas:
Column(Modifier.verticalScroll(rememberScrollState())) { // verticalScroll: si no cabe, se desplaza con el dedo
// rememberScrollState() guarda hasta dónde has desplazado mientras la pantalla siga viva
Text("Tipo: ${BuildConfig.BUILD_TYPE}") // "debug" o "release"; sin import: está en el namespace
Text(if (BuildConfig.DEBUG) "depurando" else "publicada") // DEBUG es un Boolean
}Intenta escribirlo tú. Haz que la columna de la pantalla se pueda desplazar con el dedo y, debajo del título, añade un texto con dos constantes de la variante: el modo y el identificador de la app. Lo que debes ver:
Modo: DEBUG · com.sebastiannarvaez.marcador.debugLos dos import van junto a los demás import androidx.compose.foundation…, y el bloque de la Column sustituye a las dos primeras líneas de la Column del setContent (la de Column(Modifier.padding(24.dp)) { y el Text("Marcador")):
Intenta primero. Cuando termines, compara con la solución.Mostrar el código
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll Column(Modifier.verticalScroll(rememberScrollState()).padding(24.dp)) {
Text("Marcador")
Text("Modo: ${BuildConfig.MODO} · ${BuildConfig.APPLICATION_ID}")Esa línea es provisional: en la lección siguiente le añadirás el plan (gratis o pro).
Instala la versión de depuración
Con el emulador arrancado como en La Activity, pulsa Run: instala la versión debug, que ahora tiene su propio identificador. También puedes hacerlo a mano, en la Terminal de Android Studio (en la raíz, donde está gradlew):
./gradlew :app:assembleDebug
# El apk (el fichero que se instala) queda en app/build/outputs/apk/debug/
# adb install instala el apk en el emulador; -r significa «reinstala si ya existe»
adb install -r app/build/outputs/apk/debug/app-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.MainActivityLa pantalla dice Modo: DEBUG · com.sebastiannarvaez.marcador.debug. Ahora comprueba que conviven (pm list packages marcador, de Gradle: ficheros, catálogo y dependencias):
adb shell pm list packages marcadorSalen package:com.sebastiannarvaez.marcador (la que instalabas hasta hoy) y package:com.sebastiannarvaez.marcador.debug: dos apps distintas para Android. Quita la antigua para no confundir iconos:
adb uninstall com.sebastiannarvaez.marcadorDos avisos:
- Datos y permisos empiezan de cero. Al cambiar el
applicationId, la app «nueva» no comparte datos ni permisos con la anterior: hay que volver a concederPOST_NOTIFICATIONS. - Un aviso inofensivo. Cada
assembleDebugla primera vez tras limpiar (con la caché vacía) imprimeUnable to strip the following libraries, packaging them as they are: libandroidx.graphics.path.so. Run with --info option to learn more.Es de una librería de Compose y no impide nada.
Punto de control
- En
Gradle Scripts,libs.versions.tomltiene la sección[bundles]concompose, ybuild.gradle.kts (Module :app)tiene los dos build types,buildConfig = trueeimplementation(libs.bundles.compose). Sync Now terminó sin errores rojos. - Desde la Terminal,
./gradlew :app:assembleDebugtermina enBUILD SUCCESSFUL. - La pantalla muestra
Modo: DEBUG · com.sebastiannarvaez.marcador.debug. - Con
adb shell pm list packages marcadorsale el paquete.debug(y, si no desinstalaste la antigua, también el de siempre). - Si comentas la línea
buildConfig = true(con//delante) y ejecutas./gradlew :app:compileDebugKotlin, saleBuild Type 'debug' contains custom BuildConfig fields, but the feature is disabled.Quita el//antes de seguir.
Qué sigue
Hoy has separado cómo se construye la app. Falta la otra mitad: qué versión de la app se construye (gratis o pro), cada una con su nombre, sus recursos y su identificador. Es el tema de Gradle: flavors y variantes.
Comprueba lo que entendiste
5 preguntas · necesitas 4 para completar la lección
1.¿Para qué sirve
applicationIdSuffix = ".debug"en el build typedebug?2.Declaras
buildConfigField(...)pero no activasbuildFeatures { buildConfig = true }. ¿Qué pasa con AGP 9?3.¿Qué es
BuildConfig?4.Con los build types
debug(con sufijo.debug) yrelease(sin sufijo), ¿quéapplicationIdtiene la app dereleasesi la base escom.sebastiannarvaez.marcador?5.¿Qué ventaja tiene un bundle del catálogo de versiones, como
libs.bundles.compose?