app/build.gradle
: on aura besoin de la lib de base et et de l’extension spécifique pour Jetpack ComposeImageView
qui affichera l’avatar de l’utilisateur dans le layout de la liste (à coté de votre TextView
par ex)onResume
, récupérez une référence à cette vue puis utilisez Coil pour afficher une image en passant une URL de votre choix, par exemple:imageView.load("https://goo.gl/gEgYUd")
user
UserActivity
et ajoutez la dans le manifestCompose
:setContent {
var bitmap: Bitmap? by remember { mutableStateOf(null) }
var uri: Uri? by remember { mutableStateOf(null) }
Column {
AsyncImage(
modifier = Modifier.fillMaxHeight(.2f),
model = bitmap ?: uri,
contentDescription = null
)
Button(
onClick = {},
content = { Text("Take picture") }
)
Button(
onClick = {},
content = { Text("Pick photo") }
)
}
}
Activity
quand on clique sur l’ImageView
du premier écran (pas besoin de launcher ici car on attends pas de résultat: startActivity(intent)
)TakePicturePreview
qui retourne un Bitmap
(c’est à dire une image stockée dans une variable, pas dans un fichier, donc en qualité limitée):val takePicture = rememberLauncherForActivityResult(TakePicturePreview()) {
bitmap = it
}
UserWebService
, ajouter une nouvelle méthode (cette route n’est pas documentée à ma connaissance donc c’est cadeau):@Multipart
@POST("sync/v9/update_avatar")
suspend fun updateAvatar(@Part avatar: MultipartBody.Part): Response<User>
MultipartBody.Part
qui permet d’envoyer des fichiers en HTTP:private fun Bitmap.toRequestBody(): MultipartBody.Part {
val tmpFile = File.createTempFile("avatar", "jpg")
tmpFile.outputStream().use { // *use*: open et close automatiquement
this.compress(Bitmap.CompressFormat.JPEG, 100, it) // *this* est le bitmap ici
}
return MultipartBody.Part.createFormData(
name = "avatar",
filename = "avatar.jpg",
body = tmpFile.readBytes().toRequestBody()
)
}
takePicture
, envoyez l’image au serveur avec updateAvatar
, en convertissant bitmap
avec toRequestBody
avantonResume
de TaskListFragment
, afficher l’avatar renvoyé depuis le serveur afin de le voir sur l’écran principal:imageView.load(user.avatar) {
error(R.drawable.ic_launcher_background) // image par défaut en cas d'erreur
}
On va maintenant permettre à l’utilisateur d’uploader une image enregistrée sur son téléphone
Pour simplifier on utilisera PhotoPicker
private fun Uri.toRequestBody(): MultipartBody.Part {
val fileInputStream = contentResolver.openInputStream(this)!!
val fileBody = fileInputStream.readBytes().toRequestBody()
return MultipartBody.Part.createFormData(
name = "avatar",
filename = "avatar.jpg",
body = fileBody
)
}
Jusqu’ici, vous avez probablement utilisé un Android en version 10 ou plus récente: la gestion de l’accès aux fichiers est simplifiée tant qu’on utilise les dossiers partagés (Images, Videos, etc)
Mais pour gérer les versions plus anciennes, il faut demander la permission READ_EXTERNAL_STORAGE
avant d’accéder au fichiers:
Manifest.xml
:<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
launcher
avec le contrat RequestPermission()
Manifest.permission.READ_EXTERNAL_STORAGE
) et dans sa callback, utilisez le launcher précédentActuellement, la qualité d’image récupérée de l’appareil photo est très faible (car passée en Bitmap
dans la RAM), pour changer cela il faut utiliser le contrat TakePicture
qui écrit dans un fichier passé au launcher par une Uri
// propriété: une URI dans le dossier partagé "Images"
private val captureUri by lazy {
contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, ContentValues())
}
// launcher
val takePicture = rememberLauncherForActivityResult(TakePicture()) { success ->
if (success) uri = capturedUri
}
// utilisation
takePicture.launch(captureUri)
UserViewModel
UserActivity
, permettre d’afficher et éditer le nom d’utilisateurUserWebService
:@PATCH("sync/v9/sync")
suspend fun update(@Body userUpdate: UserUpdate): Response<Unit>
User
: il faut créer un objet UserUpdate
private fun pickPhotoWithPermission() {
val storagePermission = Manifest.permission.READ_EXTERNAL_STORAGE
val permissionStatus = checkSelfPermission(storagePermission)
val isAlreadyAccepted = permissionStatus == PackageManager.PERMISSION_GRANTED
val isExplanationNeeded = shouldShowRequestPermissionRationale(storagePermission)
when {
isAlreadyAccepted -> // lancer l'action souhaitée
isExplanationNeeded -> // afficher une explication
else -> // lancer la demande de permission et afficher une explication en cas de refus
}
}
private fun showMessage(message: String) {
Snackbar.make(findViewById(android.R.id.content), message, Snackbar.LENGTH_LONG).show()
}
Pour faire encore mieux, vous pouvez aussi afficher un message avec AlertDialog en Compose et continuer le flow en fonction de la réponse de l’utilisateur