Reputation: 210
I need to fetch some images from the gallery, process them (resize, compress...) and save them to a certain path. However, i need to queue the calls because older devices won't be able to process multiple images at the same time.
I am using Glide, this is the code used for processing one image:
fun processImage(context: Context, sourcePath: String, destinationPath: String, quality: Int, width: Int, height: Int, deleteOriginal: Boolean, callback: ((success: Boolean) -> Unit)) {
val sourceFile = File(sourcePath)
val destinationFile = File(destinationPath)
GlideApp.with(context)
.asBitmap()
.load(sourceFile)
.into(object : SimpleTarget<Bitmap>(width, height) {
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
try {
destinationFile.writeBytes(ImageUtilities.imageToByteArray(resource, quality, Bitmap.CompressFormat.JPEG, false))
if (deleteOriginal) {
val originalFile = File(sourcePath)
originalFile.delete()
}
callback.invoke(true)
} catch (ex: Exception) {
callback.invoke(false)
}
}
})
}
Now i am queuing the calls manually by calling processNextImage
which calls itself recursively until all the images are processed:
private fun processImages(sourceImagePaths: List<String>) {
processNextImage(sourceImagePaths, 0)
}
private fun processNextImage(sourceImagePaths: List<String>, index: Int) {
val imagePath = sourceImagePaths[index]
val destination = FileUtilities.generateImagePath()
processImage(this, imagePath, destination, 90, 1000, 1000, false) {
processedImagePaths.add(destination)
if (index + 1 < sourceImagePaths.count())
processImage(sourceImagePaths, index + 1)
else
success()
}
}
However I don't think this is the best way to do it and I tried to look into Kotlin coroutines but all I found were examples when the queued code is already blocking, which doesn't fit my case because Glide already handles the resizing asynchronously and returns the result in a callback onResourceReady
Any ideas for a clean way to do this?
Upvotes: 3
Views: 2082
Reputation: 200138
As described in the official documentation, there is a simple pattern to follow if you want to turn a callback-based API into one based on suspendable functions. I'll paraphrase that description here.
Your key tool is the function from the standard library called suspendCoroutine()
. Assume that you have someLongComputation
function with a callback that receives a Result
object:
fun someLongComputation(params: Params, callback: (Result) -> Unit)
You can convert it into a suspending function with the following straightforward code:
suspend fun someLongComputation(params: Params): Result =
suspendCoroutine { cont ->
someLongComputation(params) { cont.resume(it) }
}
Note how the type of the object passed to the original callback became simply the return value of the suspendable function.
With this you can see the magic of coroutines happen right in front of you: even though it looks exactly like a blocking call, it isn't. The coroutine will get suspended behind the scenes and resume when the return value is ready — and how it will resume is totally under your control.
Upvotes: 1
Reputation: 210
I was able to solve the issue using suspendCoroutine
as suggested in Marko's comment, here is my code:
private fun processImages(sourceImagePaths: List<String>) {
async(UI) {
sourceImagePaths.forEach { path ->
processNextImage(path)?.let {
processedImagePaths.add(it)
}
}
if (processedImagePaths.isEmpty()) finishWithFailure() else finishWithSuccess()
}
}
private suspend fun processNextImage(sourceImagePath: String): String? = suspendCoroutine { cont ->
val destination = FileUtilities.generateImagePath()
processImage(this, sourceImagePath, destination, 90, 1000, 1000, false) { success ->
if (success)
cont.resume(destination)
else
cont.resume(null)
}
}
The method processImages
iterates over the list of paths, and calls processNextImage
for each path. Since processNextImage
contains a suspendCoroutine
, it will block the thread until cont.resume
is called, which guarantees that the next image will not be processed before the current one is done.
Upvotes: 0