AA5.2 Preparar y publicar la app¶
Qué se ha ejecutado y qué no
El estado de Compose (mutableStateOf, derivedStateOf, snapshotFlow) y la lógica de versiones se han ejecutado de verdad al construir esta web, con el runtime de Compose en la JVM. Los composables, la configuración de Gradle y los pasos de Play Console no se pueden ejecutar aquí (necesitan Android o son procesos manuales): están marcados «sin ejecutar». Las versiones de herramientas cambian con el tiempo: comprueba la documentación oficial al usarlas.
Del modo debug al modo release¶
Hasta ahora has ejecutado tu app en modo debug: sin optimizar, con herramientas de depuración y firmada con una clave de pruebas. Para publicar, se genera una compilación release: más pequeña, más rápida y firmada con tu clave.
| Debug | Release | |
|---|---|---|
| Código | Sin reducir ni ofuscar | Reducido y ofuscado con R8 |
| Recursos | Todos | Se eliminan los que no se usan |
| Firma | Clave de pruebas del IDE | Tu clave |
| Depuración | Sí | No (debuggable = false) |
| Velocidad | Lenta (da medidas falsas) | La real |
Versiones: versionCode y versionName¶
Cada compilación que subas necesita dos números:
| Campo | Para qué | Cómo |
|---|---|---|
versionName |
Lo que ve el usuario (1.4.2) |
Texto libre; lo habitual es mayor.menor.parche |
versionCode |
Lo que usa la tienda para saber cuál es más nueva | Un entero que solo puede subir en cada subida |
Si tienes un versionName con el formato mayor.menor.parche, el versionCode se puede derivar para no llevar dos contadores a mano. Es lógica pura, así que se ejecuta de verdad:
data class Version(val mayor: Int, val menor: Int, val parche: Int) : Comparable<Version> {
init { require(mayor >= 0 && menor in 0..99 && parche in 0..99) { "versión fuera de rango: $mayor.$menor.$parche" } }
// Google Play exige un entero que SOLO pueda subir. Lo derivamos del nombre de la versión.
val codigo: Int get() = mayor * 10_000 + menor * 100 + parche
override fun compareTo(other: Version) = compareValuesBy(this, other, { it.mayor }, { it.menor }, { it.parche })
override fun toString() = "$mayor.$menor.$parche"
companion object {
fun parse(texto: String): Version {
val trozos = texto.trim().split(".")
require(trozos.size == 3) { "«$texto» no tiene el formato mayor.menor.parche" }
val n = trozos.map { it.toIntOrNull() ?: throw IllegalArgumentException("«$it» no es un número") }
return Version(n[0], n[1], n[2])
}
}
}
fun main() {
val v = Version.parse("1.4.2")
println("versionName = $v · versionCode = ${v.codigo}")
val versiones = listOf("2.0.0", "1.10.0", "1.9.5", "1.10.1").map(Version::parse)
println("ordenadas: ${versiones.sorted().joinToString(" < ")}")
println("sus códigos: ${versiones.sorted().map { it.codigo }}")
println("¿1.10.0 es más nueva que 1.9.5? ${Version.parse("1.10.0") > Version.parse("1.9.5")}")
for (mala in listOf("1.4", "1.x.0", "1.4.150")) {
try { Version.parse(mala) } catch (e: IllegalArgumentException) { println("«$mala» -> ${e.message}") }
}
}
Salida:
versionName = 1.4.2 · versionCode = 10402
ordenadas: 1.9.5 < 1.10.0 < 1.10.1 < 2.0.0
sus códigos: [10905, 11000, 11001, 20000]
¿1.10.0 es más nueva que 1.9.5? true
«1.4» -> «1.4» no tiene el formato mayor.menor.parche
«1.x.0» -> «x» no es un número
«1.4.150» -> versión fuera de rango: 1.4.150
Fíjate en el tropiezo clásico que evita: como texto, "1.10.0" es menor que "1.9.5" (el 1 va antes que el 9), pero como números es mayor. Por eso las versiones se comparan por partes. El código resultante nunca baja al subir de versión, que es lo que exige Google Play.
El versionCode no se puede repetir ni bajar
La tienda rechaza una subida con un versionCode igual o menor al ya publicado. Y no se puede deshacer: si te equivocas y subes uno enorme, ya no podrás usar los números intermedios.
Reducir la app: R8¶
R8 hace tres cosas al compilar en modo release:
- Reduce (shrink): elimina el código que nadie llama.
- Optimiza: simplifica código.
- Ofusca: renombra clases y métodos a nombres cortos (
a.b.c). Es más pequeño y dificulta (sin impedirlo) que otros lean tu código.
Además, shrinkResources quita los recursos (imágenes, textos) que no se usan. Se activa en build.gradle.kts:
// Sin ejecutar: configuración de Gradle de un proyecto Android
android {
defaultConfig {
applicationId = "com.ejemplo.miapp" // identifica la app en la tienda PARA SIEMPRE
versionCode = 10402
versionName = "1.4.2"
}
buildTypes {
release {
isMinifyEnabled = true // R8: reducir, optimizar y ofuscar
isShrinkResources = true // quitar recursos que no se usan
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
}
El applicationId es para siempre
Una vez publicada la app, no se puede cambiar su applicationId: cambiarlo crea una app distinta. Elígelo con cuidado desde el principio (suele ser tu dominio al revés: com.ejemplo.miapp).
Cuando R8 rompe algo¶
La reducción asume que sabe qué código se usa. Si algo se usa sin llamarse explícitamente (por reflexión: algunas librerías de JSON, clases que se crean por su nombre), R8 puede eliminarlo o renombrarlo, y la app falla solo en release. Se arregla con reglas -keep en proguard-rules.pro:
# Sin ejecutar
-keep class com.ejemplo.miapp.datos.red.** { *; } # los DTO que se leen por reflexión
Por eso la regla de oro: prueba siempre la compilación release antes de subirla, no solo la debug. Las librerías modernas (Retrofit, Room, Hilt) suelen traer sus propias reglas.
Firmar la app¶
Android solo instala apps firmadas. La firma demuestra que las actualizaciones vienen de ti y no de otra persona. La clave se guarda en un archivo keystore.
La clave de firma es lo más delicado de la app
- Nunca la subas a un repositorio (ni las contraseñas).
- Haz copia de seguridad en un sitio seguro: sin ella, no podrías actualizar tu app como tú.
- Hoy lo habitual es Play App Signing: Google guarda la clave de firma definitiva y tú solo usas una clave de subida que se puede restablecer si la pierdes.
En el proyecto, los datos de la clave se leen de fuera del código (variables de entorno o un archivo que no se sube):
// Sin ejecutar: configuración de Gradle
android {
signingConfigs {
create("release") {
storeFile = file(System.getenv("CLAVE_RUTA") ?: "no-existe.jks")
storePassword = System.getenv("CLAVE_ALMACEN")
keyAlias = System.getenv("CLAVE_ALIAS")
keyPassword = System.getenv("CLAVE_PASSWORD")
}
}
buildTypes {
release { signingConfig = signingConfigs.getByName("release") }
}
}
.apk frente a .aab¶
| APK | AAB (Android App Bundle) | |
|---|---|---|
| Qué es | La app lista para instalar | Un paquete para la tienda |
| Uso | Pruebas y enviar a mano | Obligatorio para subir a Google Play |
| Ventaja | Se instala directamente | Google genera APK a medida de cada móvil (solo su idioma y pantalla): descargas más pequeñas |
Se genera con Gradle (o desde Android Studio, Build → Generate Signed App Bundle):
# Sin ejecutar
./gradlew bundleRelease
# resultado habitual: app/build/outputs/bundle/release/app-release.aab
Publicar en Google Play (proceso manual, sin ejecutar)¶
- Crea una cuenta de desarrollador de Google Play (tiene una cuota de registro única) y verifica tu identidad.
- En Play Console, crea la app: nombre, idioma, si es gratuita o de pago.
- Rellena la ficha de la tienda: descripción corta y larga, icono, capturas de pantalla y gráfico de cabecera.
- Rellena los formularios obligatorios: clasificación de contenido, público objetivo, política de privacidad (una URL pública, imprescindible si la app maneja datos personales) y el apartado de seguridad de los datos (qué datos recoges y para qué).
- Sube el
.aaba una pista de pruebas. - Cuando esté probada, envíala a producción.
Las pistas de pruebas permiten ir ganando confianza antes de enseñarla al mundo:
| Pista | Quién la ve | Para qué |
|---|---|---|
| Pruebas internas | Hasta un pequeño grupo de tu equipo | Comprobar cada compilación en minutos |
| Pruebas cerradas | Una lista de testers que invitas | Probar con gente real. Las cuentas personales nuevas pueden tener requisitos mínimos de pruebas antes de pasar a producción: consúltalos |
| Pruebas abiertas | Cualquiera que se una | Probar con muchos usuarios |
| Producción | Todo el mundo | La versión pública. Se puede publicar por fases (por ejemplo, al 10 % de los usuarios) para detectar fallos antes de llegar a todos |
Las normas de la tienda cambian
Google Play actualiza cada cierto tiempo sus requisitos (versión mínima de Android a la que debe apuntar la app, formularios, pruebas previas a producción). No te fíes de una lista fija: consulta la documentación de Play Console cuando vayas a publicar.
Después de publicar¶
- Informes de fallos: Play Console y Firebase Crashlytics te dicen en qué móviles y con qué error se cierra tu app.
- Actualizaciones por fases para limitar el daño si una versión falla.
- Reseñas: contestarlas mejora la valoración y te da pistas de qué arreglar.
- Versionado: cada actualización,
versionCodemás alto.
Lista de comprobación antes de subir¶
| ¿Hecho? | Comprobación |
|---|---|
| ☐ | versionCode mayor que el último publicado |
| ☐ | isMinifyEnabled = true y probada la compilación release |
| ☐ | Firmada con la clave de subida; la clave fuera del repositorio y con copia de seguridad |
| ☐ | debuggable en false |
| ☐ | Sin claves de API ni contraseñas escritas en el código |
| ☐ | Permisos revisados: solo los que de verdad se usan |
| ☐ | Política de privacidad publicada y formulario de seguridad de datos rellenado |
| ☐ | Probada en un móvil de verdad (y con pantalla pequeña, modo oscuro y otro idioma) |
| ☐ | Textos revisados en todos los idiomas que declaras |
Errores frecuentes¶
| Error | Cómo evitarlo |
|---|---|
| Probar solo en debug | Prueba siempre la compilación release |
| Subir la clave de firma (o sus contraseñas) a GitHub | Variables de entorno o un archivo ignorado por Git |
| Perder la clave de firma | Copia de seguridad y Play App Signing |
Elegir un applicationId provisional |
Es definitivo: elígelo bien desde el principio |
| Comparar versiones como texto | Comparar mayor, menor y parche como números |
| Dejar claves de API en el código | Fuera del repositorio (y, mejor, en un servidor tuyo) |
| Publicar a todos de golpe | Pistas de prueba y publicación por fases |
Para practicar¶
Los ejercicios AA5.2, AA5.3, AA5.5 y AA5.6 de AA5 · Ejercicios practican las versiones, la configuración de release y una lista de comprobación automática.