Save file with Kotlin Coroutines in Android shows inappropiate blocking method call

Viewed 1350

I created a function for saving an image to the internal files directory. I execute this function in the lifecycleScope provided by Android like following:

override fun onCreate(savedInstanceState: Bundle?) {
    lifecycleScope.launch {
        saveImage(id, proxyEntity)
    }
}

This is my function where I want to save the image.

    private suspend fun saveImage(
            id: Long,
            proxyEntity: ProxyEntity,
        ) {
            val entitiesDirectory = File(filesDir, "local/entities")
            if (false == entitiesDirectory.isDirectory) {
                entitiesDirectory.mkdirs()
            }
            if (null != selectedImage) {
                withContext(Dispatchers.IO) {
                    val entityFile = File(entitiesDirectory, "$id")
                    val fileOutputStream = FileOutputStream(entityFile)
                    //selectedImage is of type Bitmap
                    fileOutputStream.write(ImageUtil.convertBitmapToByteArray(selectedImage!!))
                    fileOutputStream.close()
                    proxyEntity.imagePath = "local/entities/${id}"
                }
            }
        }

The code itself is working, but my question is, why Android-Studio still shows me "Inappropriate blocking method call" and if I am doing anything wrong.

EDIT: The message is appearing on FileOutputStream(entityFile) and the write and close function.

1 Answers

UPDATE: After reading Tenfour04's comment

At first I was hesitant, because in theory, suspend doesn't imply anything about the context. But like most things in life, after thinking more about it (and sleeping), I think it makes sense in a practical way to make some controlled and consistent choices. Among them, to ensure:

  1. That you can replace the Dispatcher in a Unit Test (this means: don't hardcode, instead, inject it so you can replace it).

E.g.: instead of:

...launch(Dispatchers.IO) { ... }

Do:

class XYZ(private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default) { 
 

suspend fun xyz() = withContext(defaultDispatcher) { ... }

You get the idea.

  1. Suspend functions should be safe to call from main thread

This is an interesting choice but I understand why. And I will start doing this in all my code where I can. Source: Suspend functions should be safe to call from the main thread.

I suggest you read the Coroutines Best Practices recently updated/published by Google. We may all have our disagreements and use a different patterns here and there, but overall, I'd do what Google suggests; in the end, it's their platform and the closer you are to their code, the easier will be to deal with the incessant changes and deprecation that happen in modern development these days.

UPDATE: I haven't had coffee.

Ok, after re-reading your code, I noticed you do specify the Coroutine Context.

That being said, I'd make the context assignment the way I did it (in the viewmodel launch's call).

You don't want your code to "care" which context is used on, instead let the viewmodel decide what's best.

Original Response

The I/O operations on the main thread are a bad idea (if not forbidden by some APIs), so you need to change the scheduler

Your code:

lifecycleScope.launch {
        saveImage(id, proxyEntity)
}

Is running on the Default scheduler (likely Main Thread).

Try instead to use the I/O one:

lifecyleScope.launch(Dispatchers.IO) {
   saveImage(id, proxyEntity)
}
Related