Coroutines on Activity

In my Android app, I want to use the Camera intent, which returns an onActivityResult

I coded the start Camera in a separate function, and calling it as:

// MainActivity.kt
        btnCamera.setOnClickListener {
            startCamera(this)
        }

And doing the same for the onActivityResult and calling it as:

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        onCameraActivityResult(this, requestCode, resultCode, data)
    }

In the onCameraActivityResult I’m calling another function that is using opencv, and call another function named detectFace:

fun onCameraActivityResult(context: Context, requestCode: Int, resultCode: Int, data: Intent?) {
    if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == Activity.RESULT_OK) {
...
        detectFace(context, bitmap, faces_value)
    }
}

The detectFace functionusing coroutine, so it is required to be asuspend` function as:

suspend fun detectFace(context: Context, image: Bitmap, facesValue: TextView) {
    val frame = AndroidFrameConverter().convert(image)
    val mat = OpenCVFrameConverter.ToMat().convert(frame)
    coroutineScope {
        launch {
          //  delay(1000)
          //  println("Kotlin Coroutines World!")
            val numberOfFaces = FaceDetection.detectFaces(mat).toString()
            (context as Activity).runOnUiThread {
                facesValue.text = numberOfFaces
            }
        }
       // println("Hello")
    }
}

As detectFace is required to be a suspend, then the one calling it onCameraActivityResult is required to be a suspend too, also the one calling this, which is onActivityResult is required to be a suspend too.
So, I ended having:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
       btnCamera.setOnClickListener {
            startCamera(this)
        }
    }

    override suspend fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        onCameraActivityResult(this, requestCode, resultCode, data)
    }
}

And here I ended with this error:

You need to launch your coroutine. Read the docs. This is geared towards Android: Kotlin coroutines on Android  |  Android Developers

Already did as shown in my example

override suspend fun onActivityResult
you can’t do it here
you need to create|override coroutineScope in activity and call “launch” of this scope inside of onActivityResult without suspend modifier on it

You are launching from within the coroutineScope lambda. The coroutineScope method is for grouping child coroutines and can only be called from within a coroutine.

You need to start a coroutine from a non-suspend method as is described in the link I posted (See “Start a coroutine” section)

You can create a MainScope or use one provided by the Android support libraries like viewModelScope or lifecycleScope

How, can you help with sample code!

Thanks for your references, but I still did not get it.

If I used:

class MainActivity : AppCompatActivity() {
    private val scope = MainScope()
}

Or

class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
}

And having my function as:

    private suspend fun detectFace(activity: Activity, image: Bitmap?): Job = coroutineScope {
        launch {
            val numberOfFaces = FaceDetection.detectFaces(activity, image).toString()
        }
    }

Whatever the way I’m calling it from the

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { }

Regardless as:

coroutineScope {
     launch {
              detectFace(this, bitmap)
      }
}

Or as:

coroutineScope {
         val deferredOne = async { detectFace(this, bitmap) }
          deferredOne.await()
}

In all cases, i still getting that:

Suspend function `coroutineScope` should be called from a coroutine or another suspend function

Which means, the onActivityResult should be either a suspend function or coroutineScope

Can you guide more and give me sample code about what shall I do!

You need to call launch from a non-suspend method:

fun myNonSuspendMethod() {
    myScope.launch { mySuspendMethod() }
}

You should never do this:

suspend fun mySuspendMethodThatLaunchesChildCoroutineWithoutWaitingForIt() {
    myScope.launch { mySyspendMethod() }
}

This is fine, but can only be used if already in a coroutine:

suspend fun mySuspendMethodThatWaitsForChildCoroutinesToFinish() {
    coroutineScope {
        launch { mySyspendMethod() }
}
1 Like

Thanks a lot for your time and efforts, now it is fine with me, I re-wrote the detectFace as:

class MainActivity : AppCompatActivity() {
    private val scope = MainScope()

    override fun onCreate(savedInstanceState: Bundle?) { }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 
              detectFace(this, image, textView)
    }
}

private fun detectFace(context: Context, image: Bitmap, facesValue: TextView) {
    val frame = AndroidFrameConverter().convert(image)
    val mat = OpenCVFrameConverter.ToMat().convert(frame)
    scope.launch {
            val numberOfFaces = FaceDetection.detectFaces(mat).toString()
            (context as Activity).runOnUiThread {
                facesValue.text = numberOfFaces
            }
        }
    }

MainScope runs on the UI thread, which is not what you want. Do this:

class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 
        detectFace(image, textView)
    }

    private fun detectFace(image: Bitmap, facesValue: TextView) = launch {
        // switch to a background dispatcher
        val numberOfFaces = withContext(Dispatchers.Default) {
            val frame = AndroidFrameConverter().convert(image)
            val mat = OpenCVFrameConverter.ToMat().convert(frame)
            FaceDetection.detectFaces(mat).toString()
        }
        facesValue.text = numberOfFaces
    }
}
class MyActivity : AppCompatActivity() {
    private val coroutineContext: CoroutineContext = newSingleThreadContext("name") // for example
    private val scope = CoroutineScope(coroutineContext)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my)
        scope.launch { 
            suspendFun()
        }
    }


    suspend fun suspendFun() { }
}

or

class MyActivity() : AppCompatActivity(), CoroutineScope {
    override val coroutineContext: CoroutineContext = newSingleThreadContext("name") // for example

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my)
        launch {
            suspendFun()
        }
    }


    suspend fun suspendFun() {}
}

In real life you should use your coroutineContext from presenter|viewmodel|any other mv* component
in case of activity|fragment dont forget to cancel job in onDestroy:

class MyActivity() : AppCompatActivity(), CoroutineScope {
    override val coroutineContext: CoroutineContext = newSingleThreadContext("name") // for example
    var job: Job? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my)
    
        job = launch {
            suspenFun()
        }
    }

    override fun onDestroy() {
        job?.cancel()
        super.onDestroy()
    }

    suspend fun suspenFun() {}
}

or just coroutineContext.cancel()

1 Like

To make your Activity tidy (cleaning all its coroutines when it’s destroyed), here is example:

class SomeActivity: Activity(), CoroutineScope by MainScope(){
   override fun onDestroy() {
        super.onDestroy()
        coroutineContext.cancel()
    }
}

This makes your Activity to shut down all lights when it leaves. In other words, you can create any amount of launch{ ... } in the activity (based on its CoroutineScope), but when it’s destroyed, all its life ends and there is no forgotten background work.

Of course, you can still start/cancel coroutines jobs in the activity by tracking individual jobs:

var taskJob: Job? = null

fun startTask(){
   taskJob = launch{
      ... work to do
   }
}

fun endTask(){
   taskJob?.cancel()
   taskJob = null
}

But also for this case, if you forget to call endTask, the task (taskJob) is canceled when activity gets destroyed, because it was created inside of activity’s CoroutineScope , which acts as supervisor parent job of all its child jobs.

And note that MainScope() makes all your jobs (the launch{}) work on main thread by default, if you don’t specify other coroutine context (dispatcher) as parameter to launch (or to other coroutine builder function).

1 Like

I would use

class MyActivity() : AppCompatActivity(), 
    CoroutineScope by CoroutineScope(newSingleThreadContext("name")) {
1 Like

Though now it looks like the recommended way to do it is with a scope property, instead of implementing the CoroutineScope interface.

class MyActivity : AppCompatActivity() {
    private val scope = CoroutineScope(newSingleThreadContext("name"))

    fun doSomething() {
        scope.launch { ... }
    }
}