Saltar al contenido
K
Android: la plataformaPaso a paso35 min de lectura · 8 min de máquina Básico

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:

Los dos build types de Marcador (con comentarios)
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 siempre

BuildConfig: 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:

ejemplo mínimo: un build type, un buildConfigField y buildFeatures (con comentarios)
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
solución del reto
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.

Gradle Scripts
libs.versions.toml (Version Catalog "libs")se modifica: sección nueva [bundles]
build.gradle.kts (Module :app)se modifica: dependencies
Los dos ficheros de este paso.

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]:

catálogo: sección nueva (con comentarios)
[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:

build.gradle.kts (Module :app): las dos líneas que se borran
    implementation(libs.androidx.compose.material3)
    implementation(libs.androidx.compose.ui)
build.gradle.kts (Module :app): lo que escribes en su lugar
    // 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.

Gradle Scripts
build.gradle.kts (Module :app)se modifica
app
kotlin+java → com.sebastiannarvaez.marcador → MainActivity.ktse modifica
Dos ficheros, los dos se modifican (el catálogo ya lo tocaste).

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.debug

Si 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):

build.gradle.kts (Module :app): buildTypes, antes
    buildTypes {
        release {
            optimization {
                enable = false
            }
        }
    }
Intenta primero. Cuando termines, compara con la solución.Mostrar el código
build.gradle.kts (Module :app): buildTypes, después
    // 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
build.gradle.kts (Module :app): buildFeatures, después
    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:

ejemplo mínimo: BuildConfig y una columna con scroll (con comentarios)
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.debug

Los 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
app/src/main/java/com/sebastiannarvaez/marcador/MainActivity.kt
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
app/src/main/java/com/sebastiannarvaez/marcador/MainActivity.kt
                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.MainActivity

La 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 marcador

Salen 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.marcador

Dos 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 conceder POST_NOTIFICATIONS.
  • Un aviso inofensivo. Cada assembleDebug la primera vez tras limpiar (con la caché vacía) imprime Unable 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.toml tiene la sección [bundles] con compose, y build.gradle.kts (Module :app) tiene los dos build types, buildConfig = true e implementation(libs.bundles.compose). Sync Now terminó sin errores rojos.
  • Desde la Terminal, ./gradlew :app:assembleDebug termina en BUILD SUCCESSFUL.
  • La pantalla muestra Modo: DEBUG · com.sebastiannarvaez.marcador.debug.
  • Con adb shell pm list packages marcador sale 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, sale Build 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. 1.¿Para qué sirve applicationIdSuffix = ".debug" en el build type debug?

  2. 2.Declaras buildConfigField(...) pero no activas buildFeatures { buildConfig = true }. ¿Qué pasa con AGP 9?

  3. 3.¿Qué es BuildConfig?

  4. 4.Con los build types debug (con sufijo .debug) y release (sin sufijo), ¿qué applicationId tiene la app de release si la base es com.sebastiannarvaez.marcador?

  5. 5.¿Qué ventaja tiene un bundle del catálogo de versiones, como libs.bundles.compose?

Faltan 5 de 5