https://kotlinlang.org logo
Title
t

The Monster

09/08/2021, 7:52 PM
return webservice.getAllFacts()
    .also {
    coroutineScope {
        launch {
            catFactDao.insertAllFacts(it)
        }
    }
}
Code compiles, but is it a bad idea to use scope function on the return value (does it block even with the coroutine?)
Original code was like this but it's long
val list: List<CatFact> = webservice.getAllFacts()

  coroutineScope {
      launch {
          catFactDao.insertAllFacts(list)
      }
  }

return list
oh nvm looks like its a bad idea All coroutines that are started inside a
suspend
function must be stopped when that function returns
https://developer.android.com/kotlin/coroutines/coroutines-adv#parallel
j

Joffrey

09/08/2021, 10:49 PM
Note that
coroutineScope { launch { suspendCall() }}
is equivalent to just
suspendCall()
, because
coroutineScope
waits for its child coroutines to finish before returning
👀 1
What are you trying to accomplish here?
t

The Monster

09/08/2021, 11:26 PM
I am trying to return the
list
while leaving the IO task (
insert the list into DB
) running by itself. The coroutine techniques I tried, like Dispatchers.IO for example, are still blocking the method from returning. the only way that worked was to create a new Thread.
suspend fun getAllFacts(): List<CatFact> { //TODO return Flow<List<CatFact>> instead
    if (allFactsLoaded) {
        return catFactDao.selectAllFacts()
    } else {
        val list: List<CatFact> = webservice.getAllFacts()

        //thread(start = true) {
        //    catFactDao.insertAllFacts(list)
        //}

        catFactDao.insertAllFacts(list)

        return list
    }
}
I am still reading the coroutines docs to see if there is anything else I can try.
j

Joffrey

09/08/2021, 11:32 PM
The fact that launched coroutines shouldn't outlive a suspend function call is just a convention. It's a nice convention but if you're starting a thread you're already kinda breaking it anyway. If the behaviour you're looking for is really to have something concurrent that keeps going after the suspend function returns, then launching seems the appropriate thing to do. In order to do so, you'll need a coroutine scope that lives longer than your function call. It has to be provided in some way. For instance, it can be passed to
getAllFacts
either as argument or more conventionally as receiver (although we're already breaking conventions anyway 😄 ). So your function could look like this:
suspend fun CoroutineScope.getAllFacts(): List<CatFact> {
    if (allFactsLoaded) {
        return catFactDao.selectAllFacts()
    } else {
        val list: List<CatFact> = webservice.getAllFacts()
        launch {
            catFactDao.insertAllFacts(list)
        }
        return list
    }
}
You could also just have the scope somewhere in the class that declares this function, and simply use it to launch the coroutine:
suspend fun getAllFacts(): List<CatFact> {
    if (allFactsLoaded) {
        return catFactDao.selectAllFacts()
    } else {
        val list: List<CatFact> = webservice.getAllFacts()
        someScope.launch {
            catFactDao.insertAllFacts(list)
        }
        return list
    }
}
That just pushes the problem for now, because you still have to provide the scope. In the first case, the caller needs to have access to one. Most likely an existing scope tied to the lifecycle of some high level component would do. In the second case, having the scope in the class means your class should have some sort of lifecycle so it is able to cancel the scope when that lifecycle ends.
t

The Monster

09/09/2021, 12:13 AM
https://kotlinlang.slack.com/archives/C1CFAFJSK/p1631141393312100?thread_ts=1631130741.311600&amp;cid=C1CFAFJSK Oh so the
coroutineScope { launch { suspendCall() }}
is useless because there is no non-returning code outside of the
coroutineScope
AND there is only one
launch
call? I swear I saw code written like this in the docs, but now I read it again, the call outside of the
coroutineScope
is a
println()
lol.
j

Joffrey

09/09/2021, 12:19 AM
Basically
coroutineScope
suspends until all child coroutines are done, but if there is just one and no code running concurrently with it (inside
coroutineScope
), there is no point. It's like doing
async { suspendCall() }.await()
- starting a coroutine and immediately waiting for it
1
👀 1
So:
coroutineScope {
    launch { suspendCall() }
    somethingElse()
}
is ok because
suspendCall
and
somethingElse
will run concurrently
1
👀 1
t

The Monster

09/09/2021, 12:21 AM
I also tested your theory of using a scope outside of the method, and it works. Yes I am using Android, and this method is inside a Repository class, so maybe there is some pre-made scope that I can access, but I will have to look that up. I created a local coroutine scope with this.
private val scope: CoroutineScope = CoroutineScope(EmptyCoroutineContext)
I am glad that you said using the outer coroutine breaks convention, which aligns with the Android docs from Google. I will take some more time to process your statements because I am quite a beginner in coroutines.
Again, thanks so much for your help!!
🤝 1
j

Joffrey

09/09/2021, 12:29 AM
When you declare your own scope like this, it's important to think about its actual "scope" - its lifetime. Coroutine scopes help you avoid coroutine leaks, so you should try to cancel the scope when your component (Repository in this case) is not used anymore. This is probably more obvious with things like Activities or fragments because they have a clear lifecycle. I don't know much about Android so I don't know about Repository.
👍 2