Aller au contenu
Tous les articles

L'API Photo Picker d'Android en 2026 : joindre une photo sans donner accès à toute la galerie

Un guide pratique de l'API Photo Picker d'Android — sélection simple et multiple, filtrage MIME, la rétrocompatibilité pré-Android 13, et pourquoi elle vaut mieux que READ_MEDIA_IMAGES.

MFKAPPS 5 min de lecture

Demander à un utilisateur la permission de voir toute sa photothèque pour qu’il puisse joindre un ticket de caisse à une dépense, c’est demander bien plus que ce dont la fonctionnalité a besoin. READ_MEDIA_IMAGES donne à votre application un accès permanent à chaque photo de l’appareil — photos de vacances, captures d’écran de messages d’autres personnes, tout ce qui s’y trouve — pour une fonctionnalité qui n’a jamais eu besoin que de la seule image choisie par l’utilisateur. C’est exactement cet écart que le Photo Picker d’Android a été conçu pour combler.

Je l’ai ajouté à Granyn pour permettre à un utilisateur de joindre la photo d’un ticket de caisse à une dépense enregistrée. Voici comment l’API fonctionne réellement, et pourquoi elle a fait disparaître la demande de permission.

L’ancienne méthode, et pourquoi elle pose problème

Avant le Photo Picker, « laisser l’utilisateur choisir une photo » signifiait l’une de deux voies : demander READ_MEDIA_IMAGES (ou READ_EXTERNAL_STORAGE avant Android 13) et interroger MediaStore vous-même, ou lancer ACTION_GET_CONTENT et espérer que le sélecteur de documents du système se comporte de façon cohérente selon les surcouches des fabricants. La voie des permissions fonctionne, mais elle a un coût :

  • Une demande de permission au moment de l’exécution pour une fonctionnalité facile à refuser par habitude, ce qui tue silencieusement la fonctionnalité pour une partie des utilisateurs.
  • Le flux de déclaration des permissions sensibles de la Play Console — un accès média étendu attire davantage l’attention lors de la revue.
  • Un accès permanent que vous devez justifier dans votre politique de confidentialité, même si votre application ne touche jamais qu’à la seule photo sélectionnée par l’utilisateur.

Rien de tout cela n’apporte quoi que ce soit à l’utilisateur. La fonctionnalité n’a besoin que d’un accès en lecture à une seule image, une seule fois.

Comment le Photo Picker fonctionne réellement

Le Photo Picker est une interface appartenant au système — il s’exécute dans un processus séparé et de confiance, et l’image sur laquelle l’utilisateur appuie est transmise à votre application sous forme d’URI content://. Votre application n’obtient jamais d’autorisation d’accès à la galerie ; elle reçoit exactement cette seule URI, limitée à cette photo, aussi longtemps que nécessaire.

La configuration ne nécessite aucune permission dans le manifeste pour une sélection simple :

class AddExpenseFragment : Fragment() {

    private val pickImage = registerForActivityResult(
        ActivityResultContracts.PickVisualMedia(),
    ) { uri: Uri? ->
        if (uri != null) attachReceiptPhoto(uri)
    }

    private fun launchPicker() {
        pickImage.launch(
            PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly),
        )
    }
}

ActivityResultContracts.PickVisualMedia.ImageOnly limite le sélecteur aux images ; VideoOnly et SingleMimeType("image/png") couvrent les autres cas courants. Aucune entrée <uses-permission>, aucune demande au moment de l’exécution, aucune boîte de dialogue de justification à rédiger.

Sélectionner plusieurs photos

Un ticket de caisse, c’est généralement une seule photo, mais le même contrat propose une variante à sélection multiple, plafonnée au nombre que vous choisissez :

private val pickImages = registerForActivityResult(
    ActivityResultContracts.PickMultipleVisualMedia(maxItems = 5),
) { uris: List<Uri> ->
    uris.forEach { attachReceiptPhoto(it) }
}

L’interface système applique elle-même la limite — elle désactive les appuis supplémentaires une fois le plafond atteint, sans que votre code ait à rejeter une liste trop longue après coup.

L’URI récupérée n’est pas permanente

C’est le détail qui piège les gens : l’URI content:// transmise à votre ActivityResultCallback n’est garantie lisible que pendant la durée de cet appel. Si vous stockez la chaîne d’URI dans votre base de données et tentez de l’ouvrir des jours plus tard, cela peut lever une SecurityException — l’autorisation ne survit pas à la session du sélecteur, contrairement à une URI Storage Access Framework persistée.

La solution consiste à copier immédiatement les données nécessaires dans un fichier appartenant à votre application :

private fun attachReceiptPhoto(sourceUri: Uri) {
    val destFile = File(requireContext().filesDir, "receipts/${UUID.randomUUID()}.jpg")
    destFile.parentFile?.mkdirs()

    requireContext().contentResolver.openInputStream(sourceUri)?.use { input ->
        destFile.outputStream().use { output -> input.copyTo(output) }
    }

    viewModel.setReceiptPath(destFile.absolutePath)
}

Effectuez la copie hors du thread principal — c’est une opération d’E/S sur fichier — mais faites-le avant que le rappel de résultat de l’Activity du sélecteur ne se termine, pas sur un écran ultérieur. Une fois les données copiées, l’autorisation transitoire content:// n’a plus d’importance ; votre application possède désormais un fichier privé qu’elle peut lire aussi longtemps que nécessaire.

La rétrocompatibilité pré-Android 13

Le Photo Picker est arrivé en tant que fonctionnalité système avec Android 13, mais il est rétroporté jusqu’à l’API 21+ via Google Play services — le même appel ActivityResultContracts.PickVisualMedia bascule automatiquement vers cette rétrocompatibilité sur les appareils plus anciens disposant de Play services. Il y a un point à vérifier explicitement si votre ensemble minimal de fonctionnalités exige vraiment le comportement du sélecteur natif (comme l’intégration d’un fournisseur de médias cloud par application) :

val isPhotoPickerAvailable = ActivityResultContracts.PickVisualMedia
    .isPhotoPickerAvailable(requireContext())

En pratique, vous n’avez pas besoin de créer une branche pour ce cas pour un simple flux « joindre une photo » — le contrat se dégrade de lui-même avec élégance. Cela compte surtout si vous décidez d’afficher des indices d’interface spécifiques au sélecteur qui n’ont de sens que sur la version native.

Ce qui compte vraiment

Le gain ici n’est pas un nouveau geste ou une interface de sélection plus jolie — c’est que le modèle de permissions correspond enfin à ce que fait réellement la fonctionnalité. Un utilisateur qui joint la photo d’un ticket de caisse n’aurait jamais dû donner un accès permanent à toute sa pellicule, et jusqu’à l’existence du Photo Picker, Android n’avait pas de moyen propre d’éviter cela. Remplacer READ_MEDIA_IMAGES par PickVisualMedia a supprimé une permission du manifeste, une demande au moment de l’exécution, et un paragraphe de ma politique de confidentialité — et la fonctionnalité fonctionne exactement de la même façon du point de vue de l’utilisateur, simplement sans demander plus que nécessaire.