What is the lifetime kotlinx.coroutines.coroutineScope in Kotlin? - kotlin

The following code are from the project architecture samples at https://github.com/android/architecture-samples
What is the lifetime kotlinx.coroutines.coroutineScope in Kotlin? Will this function saveTask return as soon as the given block and all its children coroutines are completed?
If I pass a ViewModel.viewModelScope to DefaultTasksRepository instead of kotlinx.coroutines.coroutineScope, what are differents ?
BTW, it seems that the Code A don't pass any object of CoroutineScope, why?
Code A
import kotlinx.coroutines.coroutineScope
...
class DefaultTasksRepository(
private val tasksRemoteDataSource: TasksDataSource,
private val tasksLocalDataSource: TasksDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : TasksRepository {
...
override suspend fun saveTask(task: Task) {
coroutineScope {
launch { tasksRemoteDataSource.saveTask(task) }
launch { tasksLocalDataSource.saveTask(task) }
}
}
...
}
Code B
object ServiceLocator {
private val lock = Any()
private var database: ToDoDatabase? = null
...
private fun createTasksRepository(context: Context): TasksRepository {
val newRepo = DefaultTasksRepository(FakeTasksRemoteDataSource, createTaskLocalDataSource(context))
tasksRepository = newRepo
return newRepo
}
...
}
Added content
To Animesh Sahu: Thanks!
Are you sure that "A coroutineScope is a factory function that creates a CoroutineScope" , the following code is source code, it seems that the return value is not the object of CoroutineScope.
Source Code
public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R =
suspendCoroutineUninterceptedOrReturn { uCont ->
val coroutine = ScopeCoroutine(uCont.context, uCont)
coroutine.startUndispatchedOrReturn(coroutine, block)
}

A coroutineScope is a factory function that creates a CoroutineScope with the same context as it was called with but overriding the Job of that context.
CoroutineScope has lifetime until it is cancelled by calling cancel() on it or calling cancel() on CoroutineScope.coroutineContext or explicitly calling on the attached job coroutineContext[Job].cancel().
a coroutineScope is just a wrapper that creates immediate CoroutineScope that cancels itself up after executing its childrens.
PS: coroutineScope function is used for parallel decomposition of tasks with a new Job instance for control over its children

Related

avoid Error Suspension functions can be called only within coroutine body Kotlin [duplicate]

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
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:
In ViewModel we can use viewModelScope to launch a coroutine:
viewModelScope.launch { ... }
It attached to the lifecycle of Activity/Fragment and cancels launched coroutines when they destroyed.
Similar in Activity/Fragment we can use the following extension properties to launch a coroutine:
lifecycleScope.launch {}, lifecycle.coroutineScope.launch {}, viewLifecycleOwner.lifecycleScope.launch {}(applicable in Fragments).
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"
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

I have no way to get a return value of suspend function directly in Kotlin?

I use Room in my Android Studio project.
I hope to get the ID of added record quickly, but the following Code A can't work, how can I fix it? or I have to use these code just like Code B?
I have no way to get a return value of suspend function directly in Kotlin ?
Code A
#Dao
interface RecordDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun addRecord(aRecordEntity: RecordEntity): Long
}
class RecordRepository #Inject constructor(private val mRecordDao:RecordDao): IRecordRepository {
override suspend fun addRecord(aMRecord: MRecord): Long = withContext(Dispatchers.Default) {
mRecordDao.addRecord(ModelMapper.modelToEntity(aMRecord))
}
}
#HiltViewModel
class SoundViewModel #Inject constructor(
private val aRecordRepository: IRecordRepository
): ViewModel()
{
fun addRecord(aMRecord: MRecord):Long= viewModelScope.async {
aSoundMeter.addRecord(aMRecord)
}.await()
}
Code B
//The same
#HiltViewModel
class SoundViewModel #Inject constructor(
private val aRecordRepository: IRecordRepository
): ViewModel()
{
var id = 0L;
fun addRecord(aMRecord: MRecord) {
viewModelScope.launch {
id = aSoundMeter.addRecord(aMRecord)
}
}
}
You can only return the value of a suspend function from another suspend function. So make your ViewModel's function a suspend function:
#HiltViewModel
class SoundViewModel #Inject constructor(
private val aRecordRepository: IRecordRepository
): ViewModel()
{
suspend fun addRecord(aMRecord: MRecord): Long =
aSoundMeter.addRecord(aMRecord)
}
And launch a coroutine to call it and work with the results in your Fragment:
viewLifecycleOwner.lifecycleScope.launch {
// ...
val id = viewModel.addRecord(record)
// do something with id inside same launched coroutine
}
Note, if you're doing something critical to repository state, you should be using the results of addRecord inside a coroutine launched in the ViewModel instead. In that case, the Fragment should just "fire and forget" by calling some regular function in the ViewModel that has no return value.
Also, there are two issues with your code that I think show a misunderstanding:
Your repo code wraps the call to the DAO's suspend function using withContext. There is no reason to wrap a suspend function call in withContext() because the DAO suspend function already internally handles calling things on the right threads or dispatchers. It would be incorrect for a suspend function to ever block such that a specific IO or Default Dispatcher would be needed to call it. withContext(Dispatchers.IO) is for when you are calling blocking IO functions, like directly working with InputStreams and OutputStreams.
Never use async { }.await(). That's pointless and no different than directly calling the functions that you are wrapping in async since you are waiting for them immediately anyway.

Dispatchers used with CoroutineScope() factory function

I'm playing with coroutines and I've tried to run the following code:
private val myScope = CoroutineScope(CoroutineName("my own coroutine"))
fun destroy() {
myScope.cancel()
}
fun main() {
myScope.launch {
println("I'm working in thread ${Thread.currentThread().name}")
}
destroy()
}
And surprisingly for me, the output that I've got was the following:
I'm working in thread DefaultDispatcher-worker-1 #my own coroutine#1
I'm wondering where did DefaultDispatcher come from... Because I didn't specify Dispatcher explicitly.
If you dig into the source you'll find out the following:
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
val combined = coroutineContext + context
val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined
return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)
debug + Dispatchers.Default else debug
}
Since you didn't pass a CoroutineDispatcher to the scope itself coroutineContext doesn't have it and you did not pass any CoroutineDispatcher to the launch, context is equal to EmptyCoroutineContext. If you're not debugging, debug equals to combined and combined is not holding the reference to Dispatchers.Default and combined[ContinuationInterceptor] is null, since CoroutineDispatcher inherits ContinuationInterceptor and none is provided, therefore the final returned context is equal to debug (CoroutineName + Job() (provided by CoroutineScope() method, since you didn't provide it yourself) + EmptyCoroutineContext] + Dispatchers.Default.

Kotlin: Apply a suspend function on a list "in parallel"?

If I have a List<A> and a function suspend (A) -> B, how can I apply this function on the list in parallel?
coroutineScope {
list.map {
async {
process(it)
}
} // List<Deferred<B>>
.awaitAll() // List<B>
}
suspend fun process(a: A): B {
...
}
This assumes you are already in a suspend context. Otherwise, you need to launch a new coroutine on the appropriate scope instead of using the coroutineScope scoping function.
You can create an extension function on CoroutineScope, go through each element of the list and launch a coroutine for each element. In this way elements of the list will be processed in parallel. Some code snippet:
fun CoroutineScope.processListInParallel(list: List<A>): List<Deferred<B>> = list.map {
async { // launch a coroutine
processA(it)
}
}
GlobalScope.launch {
val list = listOf(A("name1"), A("name2"), A("name3"))
val deferredList = processListInParallel(list)
val results: List<B> = deferredList.awaitAll() // wait for all items to be processed
}
suspend fun processA(a: A): B {
delay(1000) // emulate suspension
return B("Result ${a.name}")
}
data class A(val name: String) {}
data class B(val name: String) {}
Note: GlobalScope is used here as an example, using it is highly discouraged, application code usually should use an application-defined CoroutineScope.

How to launch a Kotlin coroutine in a `suspend fun` that uses the current parent Scope?

How can I launch a coroutine from a suspend function and have it use the current Scope? (so that the Scope doesn't end until the launched coroutine also ends)
I'd like to write something like the following –
import kotlinx.coroutines.*
fun main() = runBlocking { // this: CoroutineScope
go()
}
suspend fun go() {
launch {
println("go!")
}
}
But this has a syntax error: "Unresolved Reference: launch". It seems launch must be run in one of the following ways –
GlobalScope.launch {
println("Go!")
}
Or
runBlocking {
launch {
println("Go!")
}
}
Or
withContext(Dispatchers.Default) {
launch {
println("Go!")
}
}
Or
coroutineScope {
launch {
println("Go!")
}
}
None of these alternatives does what I need. Either the code "blocks" instead of "spawning", or it spawns but the parent scope won't wait for its completion before the parent scope itself ends.
I need it to "spawn" (launch) in the current parent coroutine scope, and that parent scope should wait for the spawned coroutine to finish before it ends itself.
I expected that a simple launch inside a suspend fun would be valid and use its parent scope.
I'm using Kotlin 1.3 and cotlinx-coroutines-core:1.0.1.
You should make the function go an extension function of CoroutineScope:
fun main() = runBlocking {
go()
go()
go()
println("End")
}
fun CoroutineScope.go() = launch {
println("go!")
}
Read this article to understand why it is not a good idea to start in a suspend functions other coroutines without creating a new coroutineScope{}.
The convention is: In a suspend functions call other suspend functions and create a new CoroutineScope, if you need to start parallel coroutines. The result is, that the coroutine will only return, when all newly started coroutines have finished (structured concurrency).
On the other side, if you need to start new coroutines without knowing the scope, You create an extensions function of CoroutineScope, which itself it not suspendable. Now the caller can decide which scope should be used.
I believe I found a solution, which is with(CoroutineScope(coroutineContext). The following example illustrates this –
import kotlinx.coroutines.*
fun main() = runBlocking {
go()
go()
go()
println("End")
}
suspend fun go() {
// GlobalScope.launch { // spawns, but doesn't use parent scope
// runBlocking { // blocks
// withContext(Dispatchers.Default) { // blocks
// coroutineScope { // blocks
with(CoroutineScope(coroutineContext)) { // spawns and uses parent scope!
launch {
delay(2000L)
println("Go!")
}
}
}
However, Rene posted a much better solution above.
Say you are dealing with some RxJava Observable and it isn't the time to refactor them, you can now get a hold of a suspend function's CoroutineScope this way:
suspend fun yourExtraordinarySuspendFunction() = coroutineScope {
val innerScope = this // i.e. coroutineScope
legacyRxJavaUggh.subscribe { somePayloadFromRxJava ->
innerScope.launch {
// TODO your extraordinary work
}
}
}