I'm working on an Android application where users can select multiple photos from their device's gallery and upload them to a backend server. To achieve this, I'm using PickMultipleVisualMedia()
to get URIs of the selected images and then passing them to a worker for uploading.
Here's how I've implemented the photo picker and the worker:
Photo Picker Implementation:
private fun launchNewPhotoPicker(){
newPicker.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
}
val newPicker = registerForActivityResult(ActivityResultContracts.PickMultipleVisualMedia(5)) { uris ->
if (uris != null) {
Log.d("PhotoPicker", "Selected URIs: ${uris}")
runUploadImageWorker(uris)
} else {
Log.d("PhotoPicker", "No media selected")
}
}
private fun runUploadImageWorker(uris: List<Uri>) {
val strUris = uris.map { it.toString() }.toTypedArray()
val data = Data.Builder()
.putStringArray("uris", strUris)
.build()
val uploadImageRequest = OneTimeWorkRequestBuilder<UploadImageWorker>()
.setInputData(data)
.build()
WorkManager.getInstance(requireContext()).enqueue(uploadImageRequest)
}
Worker Implementation:
class UploadImageWorker(appContext: Context, workerParams: WorkerParameters) :
Worker(appContext, workerParams) {
override fun doWork(): Result {
val uris = inputData.getStringArray("uris") ?: return Result.failure()
val client = okhttp3.OkHttpClient()
for (uriString in uris) {
val uri = Uri.parse(uriString)
val inputStream = applicationContext.contentResolver.openInputStream(uri)
val requestBody = inputStream?.readBytes()?.toRequestBody("image/*".toMediaTypeOrNull())
val body = requestBody?.let {
MultipartBody.Part.createFormData("picture", UUID.randomUUID().toString(), it)
} ?: return Result.failure()
val multipartBody = MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addPart(body)
.build()
val request = okhttp3.Request.Builder()
.url("https://mywonderfullfakeurl.com/api/pictures")
.post(multipartBody)
.build()
val response = client.newCall(request).execute()
if (!response.isSuccessful) {
val errorBody = response.body?.string() ?: "Unknown error"
Log.d("error", errorBody)
return Result.failure()
}
}
return Result.success()
}
}
However, I've noticed that the images uploaded to my backend server are missing EXIF data. How can I ensure that the EXIF data is preserved when uploading images from an Android app using Kotlin?
To preserve EXIF data when uploading images from an Android app using Kotlin, you can use the following technique, inspired by @Maveňツ, along with using a temporary file to avoid ENOENT
errors.
Additionally, as @blackapps pointed out, using OpenMultipleDocuments()
instead of PickMultipleVisualMedia()
is crucial to prevent null EXIF data when reading from the URI.
Photo Picker Implementation:
private fun launchNewPhotoPicker(){
newPiker.launch(arrayOf("image/jpeg"))
}
private val newPiker=registerForActivityResult(ActivityResultContracts.OpenMultipleDocuments()) { uris ->
if (uris != null) {
Log.d("PhotoPicker", "Selected URI: ${uris}")
//launch the worker to upload the images
runUploadImageWorker(uris)
} else {
Log.d("PhotoPicker", "No media selected")
}
}
Worker Implementation:
override fun doWork(): Result {
val uris = inputData.getStringArray("uris") ?: return Result.failure()
val client = okhttp3.OkHttpClient()
for (uriString in uris) {
val uri = Uri.parse(uriString)
// create temp file
val uuid = UUID.randomUUID().toString()
val file = File(applicationContext.cacheDir, uuid)
// copy selected file to temp file
uri?.let { applicationContext.contentResolver.openInputStream(it) }.use { input ->
file.outputStream().use { output ->
input?.copyTo(output)
}
}
val inputStream = file.inputStream()
val requestBody = inputStream.readBytes().toRequestBody("image/*".toMediaTypeOrNull())
val body = requestBody.let {
MultipartBody.Part.createFormData("picture", UUID.randomUUID().toString(), it)
}
val multipartBodyBuilder = MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addPart(body)
// Preserving EXIF data
val exifInterface = ExifInterface(file)
val tagsToCheck = arrayOf(
ExifInterface.TAG_DATETIME,
ExifInterface.TAG_GPS_LATITUDE,
ExifInterface.TAG_GPS_LONGITUDE,
ExifInterface.TAG_GPS_ALTITUDE
)
for (tag in tagsToCheck) {
exifInterface.getAttribute(tag)?.let {
multipartBodyBuilder.addFormDataPart("exif_$tag", it)
}
}
val multipartBody = multipartBodyBuilder.build()
// don't forget to delete the temporary file
file.delete()
val request = okhttp3.Request.Builder()
.url("https://mywonderfullfakeurl.com/api/pictures")
.post(multipartBody)
.build()
val response = client.newCall(request).execute()
if (!response.isSuccessful) {
val errorBody = response.body?.string() ?: "Unknown error"
Log.d("error", errorBody)
return Result.failure()
}
}
return Result.success()
}
In this revised version:
OpenMultipleDocuments()
instead of PickMultipleVisualMedia()
to have EXIF dataExifInterface
.MultipartBody
using addFormDataPart
.This ensures that the uploaded images preserve their selected EXIF data when sent to the backend server.