Aller au contenu
Tous les articles

L'API In-App Update d'Android en 2026 : flexible vs immédiate, et quand forcer une mise à jour

Un guide pratique de l'API In-App Update de Google — flux flexible vs immédiat, priorité de mise à jour, jours d'obsolescence, et la vérification de reprise que tout le monde oublie.

MFKAPPS 5 min de lecture

La mise à jour automatique du Play Store couvre la plupart de vos utilisateurs, tôt ou tard — mais « tôt ou tard » fait beaucoup de travail dans cette phrase. Un réglage Wi-Fi uniquement, une pression sur le stockage, ou un utilisateur qui n’ouvre tout simplement jamais l’application Play Store peuvent laisser quelqu’un trois versions en retard — encore sur la build avec le bug que vous avez corrigé il y a deux semaines, ou pire, encore en train de heurter un contrat serveur que votre backend ne prend plus en charge. L’API In-App Update de Google existe pour combler cet écart : elle permet à votre application de vérifier si une nouvelle version existe et de proposer la mise à jour depuis l’intérieur même de l’app, sans envoyer l’utilisateur ailleurs.

L’API propose deux flux distincts, et choisir le mauvais est la façon la plus courante de mal livrer cette fonctionnalité.

Comment ça fonctionne réellement

L’API fait partie de Play Core (com.google.android.play:app-update-ktx). Vous demandez à AppUpdateManager l’AppUpdateInfo actuel, qui vous indique si une mise à jour est disponible et ce que Google en sait :

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()
    }
}

Cet objet AppUpdateInfo est essentiellement toute l’API — le reste consiste à décider quoi en faire.

Flexible vs immédiate : deux tâches différentes

Flexible télécharge la mise à jour en arrière-plan pendant que l’utilisateur continue d’utiliser l’app, puis affiche un petit snackbar « redémarrer pour mettre à jour » une fois prête. L’utilisateur n’est pas bloqué et peut ignorer l’invite. C’est le bon choix par défaut pour tout ce qui n’est pas urgent — un polish d’interface, une nouvelle fonctionnalité, une correction de bug non cassante.

if (info.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
    updateManager.startUpdateFlowForResult(
        info, activityResultLauncher, AppUpdateOptions.newBuilder(AppUpdateType.FLEXIBLE).build()
    )
}

Une fois le téléchargement terminé, InstallStateUpdatedListener signale InstallStatus.DOWNLOADED, et vous terminez l’installation explicitement :

updateManager.registerListener { state ->
    if (state.installStatus() == InstallStatus.DOWNLOADED) {
        updateManager.completeUpdate() // shows the snackbar's restart action
    }
}

Immédiate est l’inverse : un flux bloquant, plein écran, qui empêche l’utilisateur d’utiliser l’application jusqu’à ce que la mise à jour soit installée. Elle est perturbatrice par conception, ce qui la réserve à exactement une situation — la version actuelle est cassée d’une manière qui compte vraiment. Un correctif de sécurité, une migration de schéma Room qu’un ancien client corromprait, un changement d’API backend qui fait planter l’ancienne build à chaque requête. Si vous ne pouvez pas expliquer pourquoi laisser quelqu’un utiliser l’ancienne version un jour de plus est réellement nuisible, ce n’est pas une mise à jour immédiate, c’est une mise à jour flexible.

if (info.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
    updateManager.startUpdateFlowForResult(
        info, activityResultLauncher, AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build()
    )
}

Je n’y ai eu recours qu’une seule fois, en livrant une version de Granyn qui changeait la façon dont les transactions récurrentes étaient stockées — un ancien client écrivant dans le nouveau schéma aurait silencieusement faussé les soldes. Cela mérite un blocage. Un bouton renommé, non.

Décider de la priorité (et de l’obsolescence)

AppUpdateInfo vous donne deux signaux au lieu de laisser le choix flexible/immédiate au hasard :

  • updatePriority() — un entier de 0 à 5 que vous définissez vous-même, par version, dans la Play Console au moment du déploiement. Il n’est pas calculé à partir du diff ; c’est vous qui indiquez à Google (et à votre propre code client) à quel point cette version est urgente.
  • clientVersionStalenessDays() — depuis combien de jours la mise à jour est disponible pour cet utilisateur spécifique, ce qui diffère du moment où vous l’avez livrée. Un déploiement progressif signifie que ce chiffre varie selon votre base d’installations même pour la même version.

Un schéma qui a bien fonctionné pour moi : traiter les priorités 4-5 comme éligibles à l’immédiat, et combiner priorité et obsolescence pour tout le reste — une version de priorité 3 devient immédiate seulement après être restée non réclamée une semaine ou deux, laissant aux utilisateurs une période de grâce avant de les interrompre.

val shouldForce = info.updatePriority() >= 4 ||
    (info.updatePriority() == 3 && (info.clientVersionStalenessDays() ?: 0) > 14)

La vérification que tout le monde oublie : reprendre une mise à jour bloquée

Une mise à jour immédiate peut être interrompue — l’utilisateur met l’app en arrière-plan, le processus meurt, un appel arrive. Quand cela se produit, Play Core ne la reprend pas silencieusement pour vous ; vous devez vérifier une mise à jour bloquée à chaque fois que l’écran concerné reprend, pas seulement une fois au lancement :

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()
            )
        }
    }
}

Sautez cette étape et vous obtenez le mode d’échec qui donne à cette API sa mauvaise réputation : un utilisateur est interrompu en pleine mise à jour, met l’app en arrière-plan pour répondre à un texto, revient, et l’app… fonctionne à nouveau, à moitié mise à jour, dans un état que vous n’avez jamais testé. La vérification onResume fait trois lignes et c’est la différence entre une mise à jour immédiate robuste et un bug report intermittent que vous ne pouvez pas reproduire.

Tester sans attendre un vrai déploiement

Vous ne pouvez pas déclencher une vraie mise à jour depuis votre propre appareil contre votre propre build actuellement installée — Play Core a besoin d’un véritable écart de version visible par votre compte. La configuration pratique est une piste de test interne : installez-y un ancien code de version, puis publiez-en un nouveau et laissez l’API voir l’écart réel. Google propose aussi une API de test des mises à jour in-app pour simuler localement les réponses AppUpdateInfo ; cela vaut la peine de la relier à un menu de debug si vous touchez souvent à ce flux — bien plus rapide qu’un aller-retour sur une vraie piste pour itérer sur la logique priorité/obsolescence elle-même.

Traitez toute cette fonctionnalité comme un filet de sécurité, pas comme un schéma UX sur lequel s’appuyer. La meilleure version de cette API est celle que vos utilisateurs ne voient jamais, parce que la mise à jour automatique a déjà fait son travail — flexible et immédiate existent pour les jours où ce n’était pas le cas.