androidkotlinhttpkotlin-coroutinescoroutinescope

How to wait for khttp (kotlin) response in Android


I've been trying to use khttp to send an .jpg file in an android activity but haven't been able to make it work.

fun sendImage(view: View) {

    try {
        var bmp = (imageView?.drawable as BitmapDrawable).bitmap
        var bos = ByteArrayOutputStream()
        bmp.compress(Bitmap.CompressFormat.JPEG, 0, bos)
        var response: Response? = null
        findViewById<TextView>(R.id.image_desc).text = "Connecting to " + SERVER_URL;

        try {
            val job=GlobalScope.launch {
                response = post(SERVER_URL, files = listOf(File(path).fileLike(name = "Image.jpg")))
            }

            findViewById<TextView>(R.id.image_desc).text = "Image contains: ${response?.text}"
        } catch (e: Exception) {
            findViewById<TextView>(R.id.image_desc).text = "Connection failed - please check fields are valid"
            findViewById<TextView>(R.id.image_desc).text = e.toString()
        }

    } catch (e: UnknownHostException) {
        findViewById<TextView>(R.id.image_desc).text = "Unknown host :("
        e.printStackTrace()
    } catch (e: IOException) {
        findViewById<TextView>(R.id.image_desc).text = "IO exceptiion :("
        e.printStackTrace()
    } catch (e: Exception) {
        findViewById<TextView>(R.id.image_desc).text = "Other exception :("
        e.printStackTrace()
    }
}

As soon as i send the image, image_desc textView's text change to Image contains: null. I'm sure the server isn't the problem, since when I test it with this python code:

import requests

url=...
files = {'file': open('./test/cat.jpg', 'rb')}
r=requests.post(url,files=files)
print (r.text)

I get the desired response after a short delay. I've tried turning sendImage to a suspend func and writing job.join() but that crashes the app. How should fix this?


Solution

  • Try next code:

    val job = GlobalScope.launch(Dispatchers.Main) {
        val postOperation = async(Dispatchers.IO) { // <- extension on launch scope, launched in IO dispatcher
            // blocking I/O operation
            post(SERVER_URL, files = listOf(File(path).fileLike(name = "Image.jpg")))
        }
        response = postOperation.await() // wait for result of I/O operation without blocking the main thread
        findViewById<TextView>(R.id.image_desc).text = "Image contains: ${response?.text}"
    }
    

    Also add next line to app's build.gradle dependency:

    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.1'
    

    Note that GlobalScope is discouraged to use, to launch a coroutine use an instance of CoroutineScope, or existing instance like viewModelScope or lifecycleScope.


    UPDATE:

    The correct approach would be to use lifecycleScope in Activity:

    lifecycleScope.launch { // uses Dispatchers.Main context
        val response = withContext(Dispatchers.IO) { // change context to background thread
            // blocking I/O operation
            post(SERVER_URL, files = listOf(File(path).fileLike(name = "Image.jpg")))
        }
        findViewById<TextView>(R.id.image_desc).text = "Image contains: ${response?.text}"
    }