vidul_mahendru
vidul_mahendru

Reputation: 1

kotlin consumeEach() vs receive()

I have some code of the format:

 class MyProducerClass {
        fun CoroutineScope.myProducerFunction(): ReceiveChannel<someClass> = produce {
            // send some stuff to a channel
        }
    }

    class MyConsumerClass {
        val producerObject = MyProducerClass()

        fun CoroutineScope.myConsumerFunction(channel: ReceiveChannel<someClass>) = launch {
            // Consume stuff from the channel
        }
        
        fun functionToBeCalledByApplication() = runBlocking {
            MyProducerClass().myConsumerFunction(myProducerFunction())
        }
    }

But it seems like myProducerFunction can't be accessed from within MyConsumerClass. Can someone explain why this is? I am very new to kotlin and getting some intuition on why coroutines don't allow for this type of pattern would be very helpful

Moved all the coroutine related functions into a single class and it worked. Still not sure why the cross class coroutine function calls don't work

Upvotes: 0

Views: 481

Answers (1)

Tenfour04
Tenfour04

Reputation: 93639

myProducerFunction() is an extension function of CoroutineScope, so you can only call it on a CoroutineScope.

This leads to an awkward situation: when you define an extension function in one class, it becomes difficult to call from outside that class. To be able to call the other function, you must bring its owning class into scope as a receiver, which can be done with the run { } scope function like this:

fun functionToBeCalledByApplication(scope: CoroutineScope) = runBlocking {
    MyProducerClass().run { scope.myConsumerFunction(myProducerFunction()) }
}

Above, I made a CoroutineScope into a function parameter. You could also make this an extension function instead, but then you're passing along the problem to the application class that wants to call this:

fun CoroutineScope.functionToBeCalledByApplication() = runBlocking {
    MyProducerClass().run { myConsumerFunction(myProducerFunction()) }
}

For these reasons, you generally want to avoid defining a public extension function inside a class. In this case, depending on how these classes are used, I would either provide the CoroutineScope as a member property of the associated class, or make it a regular function argument in these functions.


Soon, this problem is going to be fixed. There's an experimental feature in Kotlin called "context receivers" that I think is planned to become stable in either Kotlin 1.8 or 1.9. You'll be able to define a context receiver for a function, so that receiver needs to be in scope to call a function, but you don't have to call the function on that object. With that upcoming feature, you'd define your functions like this:

class MyProducerClass {
    context(CoroutineScope)
    fun myProducerFunction(): ReceiveChannel<someClass> = produce {
            // send some stuff to a channel
    }
}

class MyConsumerClass {
    val producerObject = MyProducerClass()

    context(CoroutineScope)
    fun myConsumerFunction(channel: ReceiveChannel<someClass>) = launch {
        // Consume stuff from the channel
    }

    context(CoroutineScope)
    fun functionToBeCalledByApplication() = runBlocking {
        MyProducerClass().myConsumerFunction(myProducerFunction())
    }
}

Now the CorouineScope only has to be in scope as a receiver to be able to call these functions, instead of having to be the object you call the function on. So in your Application class, if you call the function in a coroutine function like launch, it will work fine because a CoroutineScope is a receiver.


Side note, you will almost never see runBlocking in production code, since it defeats the purpose of using coroutines. It is only for bridging between non-coroutine-based code and coroutines, like when you're using a library that handles its own multi-threading but need to be able call a suspend function from a background thread from that library. It would be more appropriate to make your last function a suspend function instead. The application code that calls it needs to provide a CoroutineScope anyway, it might as well do it from within its own coroutine.

Upvotes: 3

Related Questions