Search code examples
androidkotlinkotlin-coroutinesextension-functionkotlin-context-receivers

How to create an extension function with multiple receivers in Kotlin?


I want my extension function to have a couple of receivers. For example, I want function handle to be able to call methods of both CoroutineScope and Iterable instances:

fun handle() {
    // I want to call CoroutineScope.launch() and Iterable.map() functions here
    map {
        launch { /* ... */ }
    }
}

I thought this might work:

fun <T> (Iterable<T>, CoroutineScope).handle() {}

But it gives me an error:

Function declaration must have a name

I know that I can create the function with parameters, but

Is it possible to have multiple receivers for a single function and how to do that without parameters?


Solution

  • In the Kotlin version 1.6.20 there is a new feature called Context receivers. This is a first prototype of context receivers. This feature allows to make functions, properties and classes context-dependent by adding context receivers to their declaration. There is a new syntax for that. In front of the function declaration we can specify a list of contextual types that would be required to invoke this function. A contextual declaration does the following:

    • It requires all declared context receivers to be present in a caller's scope as implicit receivers.
    • It brings declared context receivers into the body scope of implicit receivers.

    The solution with context receivers looks like the following:

    context(CoroutineScope)
    fun <T> Iterable<T>.handle() {
        map {
            launch { /* ... */ }
        }
    }
    
    someCoroutineScope.launch {
        val students = listOf(...)
        students.handle()
    }
    

    In the context(CoroutineScope) we can declare multiple types, e.g context(CoroutineScope, LogInterface).

    Since context receivers feature is a prototype, to enable it add -Xcontext-receivers compiler option in the app's build.gradle file:

    apply plugin: 'kotlin-android'
    android {
        //...
        kotlinOptions {
            jvmTarget = "11"
            freeCompilerArgs += [
                    "-Xcontext-receivers"
            ]
        }
    }