67

I am calling suspended function from onCreate(...)

override fun onCreate(savedInstanceState: Bundle?) {
    ...
    ...
    callGetApi()
}

and the suspended function is:-

suspend fun callGetApi() {....}

But the error shows up Suspend function 'callGetApi' should be called only from a coroutine or another suspend function

Sergio
  • 27,326
  • 8
  • 128
  • 149
Santanu Sur
  • 10,997
  • 7
  • 33
  • 52

3 Answers3

76

Suspend function should be called only from a coroutine.

That means to call a suspend function you need to use a coroutine builder, e.g. launch, async or runBlocking(recommended to use only in unit tests). For example:

class Activity : AppCompatActivity(), CoroutineScope {
    private var job: Job = Job()

    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job

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

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        launch {
            val result =  callGetApi()
            onResult(result) // onResult is called on the main thread
        }
    }

    suspend fun callGetApi(): String {...}

    fun onResult(result: String) {...}
}

To use Dispatchers.Main in Android add dependency to the app's build.gradle file:

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

The MOST RECENT APPROACH would be to use extension properties in ViewModel and Activity/Fragment:

It attached to the lifecycle of Activity/Fragment and cancels launched coroutines when they destroyed.

Sergio
  • 27,326
  • 8
  • 128
  • 149
  • 1
    That's another example of parallel decompostion, but you wrote `async { call() }.await()` and that's an anti-pattern because: 1) there's no concurrency, 2) if `call()` fails, it will cause your top-level `launch`ed coroutine to be immediately cancelled (you get no chance to do regular exception handling), 3) it's more heavyweight than `withContext()`, which reuses the same coroutine. – Marko Topolnik Dec 26 '18 at 08:22
  • Agree, in this case `async-await` would be an abuse. – Sergio Dec 26 '18 at 08:42
  • 2
    @IgorGanapolsky please make sure you are implementing `CoroutineScope` interface : `class Activity : AppCompatActivity(), CoroutineScope` – Sergio Nov 21 '19 at 17:55
  • @Sergey We need `lifecycleScope` api for **launch** in Activity. And gradle dependency on **androidx.lifecycle:lifecycle-runtime-ktx:** – IgorGanapolsky Nov 21 '19 at 18:45
  • @IgorGanapolsky not necessary, you can just implement `CoroutineScope` and use `launch` without any errors. Did you implement `CoroutineScope` and do you still see the error? – Sergio Nov 21 '19 at 19:59
  • @Sergey Don't pollute Activity with `CoroutineScope` inheritance. Keep to **S.O.L.I.D.** principles – IgorGanapolsky Dec 03 '19 at 13:54
  • @IgorGanapolsky the question was about `callGetApi()` function in Activity. I answered according to that. SOLID principles and architecture patterns are a matter of another question. – Sergio Jun 03 '20 at 10:45
42

Looks like the most elegant way to do it as of July 2019, is the one described here:

import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch

class Activity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super...

        lifecycleScope.launch {
            val result =  callGetApi()
            onResult(result) 
        }
    }
}

Don't forget to add the correponding lib:

implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha02"
hannes ach
  • 16,247
  • 7
  • 61
  • 84
Kipr
  • 806
  • 10
  • 13
11

The above answer worked , but i solved it without inheriting CoroutineScope class by just using .... gradle.build

  dependencies {
      implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3'
  }

Activity.kt

  import kotlinx.coroutines.GlobalScope
  import kotlinx.coroutines.Dispatchers

  GlobalScope.launch (Dispatchers.Main) { callGetApi() }

Dispatchers.Main is important cause you cannot update the UI in any other thread than main.

But its recommended to inherit CoroutineScope to maintain the lifecycle of the activity and onDestroy of the activity to kill the job

Santanu Sur
  • 10,997
  • 7
  • 33
  • 52
  • 5
    Yes, this will seem work to for you at first glance, but it goes directly agains the [recommendations in the documentation](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/). Using this style on Android will make your application buggy and cause weird crashes. – Marko Topolnik Dec 26 '18 at 07:52
  • 1
    As per docs `GlobalScope.launch` is not recommended to use. The best practice to use local scope to have the possibility to cancel coroutines. – Sergio Dec 26 '18 at 07:52
  • Will it crash even if i mention the thread .. i.e , `Dispatchers.Main` instead of others like `Dispatchers.Default, Dispatchers.IO, Dispatchers.Unconfined` @MarkoTopolnik @Sergey – Santanu Sur Dec 26 '18 at 07:56
  • 1
    It's a different source of crash. Your activity may die, but the coroutine will live on. It will then complete later on and try to update the dead activity. `CoroutineScope` is about controlling the lifecycle of a coroutine and making sure it doesn't keep on executing in the background after the receiver of its results is gone. – Marko Topolnik Dec 26 '18 at 07:58
  • This isn't compatible with Kotlin 1.3.60 – IgorGanapolsky Nov 21 '19 at 17:50
  • And I thought Microrubbishes async/await was twaddle! Come back OpenVMS ... all is forgiven.. – Richard Hammond Dec 13 '22 at 13:01