La API de Actualización en la App de Android en 2026: flexible vs. inmediata, y cuándo forzar una
Una guía práctica de la API In-App Update de Google: flujos flexible e inmediato, prioridad de actualización, días de obsolescencia y la comprobación de reanudación que todos olvidan.
La actualización automática de Play Store cubre a la mayoría de tus usuarios, tarde o temprano — pero “tarde o temprano” hace mucho trabajo en esa frase. Un ajuste de solo-Wi-Fi, presión de almacenamiento, o un usuario que simplemente nunca abre la app de Play Store pueden dejar a alguien tres versiones atrás — todavía en la build con el error que arreglaste hace dos semanas, o peor, todavía chocando contra un contrato de servidor que tu backend ya no soporta. La API In-App Update de Google existe para ese vacío: permite que tu app compruebe si hay una versión más nueva y solicite la actualización desde dentro de sí misma, sin enviar al usuario a ningún otro sitio.
La API tiene dos flujos distintos, y elegir el equivocado es la forma más común de implementar esto mal.
Cómo funciona realmente
La API vive en Play Core (com.google.android.play:app-update-ktx). Le pides a AppUpdateManager el AppUpdateInfo actual, que te dice si hay una actualización disponible y qué sabe Google sobre ella:
val updateManager = AppUpdateManagerFactory.create(context)
updateManager.appUpdateInfo.addOnSuccessListener { info ->
if (info.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) {
// decide which flow to start based on info.updatePriority()
// and info.clientVersionStalenessDays()
}
}
Ese objeto AppUpdateInfo es prácticamente toda la API — el resto consiste en decidir qué hacer con él.
Flexible vs. inmediata: dos trabajos distintos
Flexible descarga la actualización en segundo plano mientras el usuario sigue usando la app, y luego muestra un pequeño snackbar de “reiniciar para actualizar” cuando está lista. El usuario no queda bloqueado y puede descartar el aviso. Este es el valor por defecto correcto para todo lo que no sea urgente: un pulido de interfaz, una nueva función, una corrección de errores que no rompe nada.
if (info.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
updateManager.startUpdateFlowForResult(
info, activityResultLauncher, AppUpdateOptions.newBuilder(AppUpdateType.FLEXIBLE).build()
)
}
Cuando termina la descarga, InstallStateUpdatedListener reporta InstallStatus.DOWNLOADED, y tú completas la instalación explícitamente:
updateManager.registerListener { state ->
if (state.installStatus() == InstallStatus.DOWNLOADED) {
updateManager.completeUpdate() // shows the snackbar's restart action
}
}
Inmediata es lo contrario: un flujo bloqueante, a pantalla completa, que impide que el usuario use la app hasta que la actualización se instale. Es disruptiva por diseño, lo que la reserva para exactamente una situación: la versión actual está rota de una manera que realmente importa. Un parche de seguridad, una migración de esquema de Room que un cliente antiguo corrompería, un cambio de API en el backend que hace que la build antigua falle en cada solicitud. Si no puedes explicar por qué dejar que alguien siga usando la versión antigua un día más es realmente perjudicial, no es una actualización inmediata, es una flexible.
if (info.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
updateManager.startUpdateFlowForResult(
info, activityResultLauncher, AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build()
)
}
Solo la he usado una vez, al publicar una versión de Granyn que cambiaba cómo se almacenaban las transacciones recurrentes: un cliente antiguo escribiendo en el nuevo esquema habría calculado mal los saldos en silencio. Eso merece un bloqueo. Un botón renombrado, no.
Decidir la prioridad (y la obsolescencia)
AppUpdateInfo te da dos señales en lugar de dejar la elección flexible/inmediata a la suerte:
updatePriority()— un entero de 0 a 5 que tú mismo defines, por versión, en la Play Console al momento del lanzamiento. No se calcula a partir del diff; eres tú quien le dice a Google (y a tu propio código cliente) cuán urgente es esta versión.clientVersionStalenessDays()— cuántos días lleva disponible la actualización para este usuario específico, que es distinto de cuándo la publicaste tú. Un lanzamiento escalonado hace que este número varíe en tu base de instalaciones incluso para la misma versión.
Un patrón que me ha funcionado bien: tratar la prioridad 4-5 como elegible para inmediata, y combinar prioridad con obsolescencia para todo lo demás: una versión de prioridad 3 se vuelve inmediata solo después de llevar sin reclamarse una o dos semanas, dando a los usuarios un periodo de gracia antes de interrumpirlos.
val shouldForce = info.updatePriority() >= 4 ||
(info.updatePriority() == 3 && (info.clientVersionStalenessDays() ?: 0) > 14)
La comprobación que todos olvidan: reanudar una actualización estancada
Una actualización inmediata puede interrumpirse: el usuario pone la app en segundo plano, el proceso muere, entra una llamada. Cuando eso pasa, Play Core no la reanuda en silencio por ti; tienes que comprobar si hay una actualización estancada cada vez que la pantalla relevante se reanuda, no solo una vez al iniciar:
override fun onResume() {
super.onResume()
updateManager.appUpdateInfo.addOnSuccessListener { info ->
if (info.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
updateManager.startUpdateFlowForResult(
info, activityResultLauncher, AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build()
)
}
}
}
Sáltate esto y obtienes el modo de fallo que le da a esta API su mala reputación: un usuario es interrumpido a mitad de la actualización, pone la app en segundo plano para responder un mensaje, vuelve, y la app… simplemente funciona de nuevo, a medio actualizar, en un estado que nunca probaste. La comprobación en onResume son tres líneas y es la diferencia entre una actualización inmediata robusta y un reporte de error intermitente que no puedes reproducir.
Probarlo sin esperar un lanzamiento real
No puedes activar una actualización real desde tu propio dispositivo contra tu propia build instalada actualmente: Play Core necesita una diferencia de versión real y visible para tu cuenta. La configuración práctica es una pista de pruebas interna: instala desde ahí un código de versión antiguo, luego publica uno nuevo y deja que la API vea la diferencia real. Google también ofrece una API de pruebas de actualizaciones en la app para simular localmente las respuestas de AppUpdateInfo; vale la pena conectarla a un menú de depuración si tocas este flujo con frecuencia — es mucho más rápido que un ida y vuelta real por la pista para iterar sobre la lógica de prioridad/obsolescencia en sí.
Trata toda esta función como una red de seguridad, no como un patrón de UX en el que apoyarte. La mejor versión de esta API es la que tus usuarios nunca ven, porque la actualización automática ya hizo su trabajo; flexible e inmediata existen para los días en que no lo hizo.
// Lecturas relacionadas
Más del diario
androidx.startup en 2026: ordenar la inicialización de librerías sin una pila de ContentProviders
Una guía práctica de la librería App Startup de Android — consolidar initializers en un único ContentProvider, declarar dependencias, y los casos de init perezosa que no puede sustituir.
CameraX en 2026: enlazar Preview e ImageAnalysis sin dejar escapar una sesión de cámara
Una guía práctica de CameraX en Android: enlazar Preview e ImageAnalysis al ciclo de vida, la estrategia de backpressure correcta y el fallo que causa la rotación.
La API Photo Picker de Android en 2026: adjuntar una foto sin dar acceso a toda la galería
Una guía práctica de la API Photo Picker de Android: selección única y múltiple, filtrado por MIME, el respaldo para versiones previas a Android 13 y por qué supera a READ_MEDIA_IMAGES.