Hello, I have a question about context parameters ...
# getting-started
h
Hello, I have a question about context parameters when used with a lambda. It's a little hard for me to describe, but I have a function that has a lambda that needs a context parameter as well as a receiver and a regular parameter. The function (cTest) itself provides the context to the lambda function, so that the caller of cTest doesn't need to. But how do I realize this? Currently I have
Copy code
fun cTest(
    block: context(String) (Int).(props: List<String>) -> Unit
) {
    with("Context") {
        block(1, listOf("a", "b", "c"))
    }
}

val PTest = cTest { props ->
    println("Context: ??")
    println("Receiver: $this")
    println("Parameters: $props")
}
I could do this:
Copy code
context(str: String)
val PTest get() = cTest { props ->
    println("Context: $str")
    println("Receiver: $this")
    println("Parameters: $props")
}
But then the caller of
PTest
needs to provide context, which
cTest
already provides. I tried searching for this and also asked AI, but came up short.
y
contextOf<String>()
h
Hey, thank you very much for that super fast answer. I am a little lost on where to but this though. Like this?
Copy code
val PTest = cTest { props ->
    
    println("Context: ${contextOf<String>()}")
    println("Receiver: $this")
    println("Parameters: $props")
}
➕ 1
Thank you very much!
y
Exactly! It's a function defined in the stdlib like so:
Copy code
context(ctx: C)
fun <C> contextOf(): C = ctx
so it just uses the context and returns it
h
What confuses me a little, then why doesn't this work?
Copy code
context(str: String)
fun String.lambdaTest() { println("Context: $str")}

val PTest = cTest { props ->
    println(lambdaTest())
    println("Receiver: $this")
    println("Parameters: $props")
}
It shows
lambdaTest()
as unresolved context.
Ah nevermind I had a brain fart
I added another
String
receiver by accident.
Sorry, I have a little followup question. If I wanted to use the context directly inside the lambda, but don't want to do a
val str = contextOf<String>()
in the first row of each one, how would I do that if possible? So where would I put the
context(str: String)
then? Or is there no way to solve this with the
context
syntax?
Copy code
val Int.PTest get() = <not here> cTest <also not here> { props ->
    println("Context: $str")
    println("Receiver: $this")
    println("Parameters: $props")
}
y
pretty sure there isn't, sadly. The point of context receivers is that you're likely calling other functions that themselves use that context, and so you don't need to materialize the actual value itself that often (especially in lambdas)
h
Alright, thank you for your insights. I use it with KotlinJS and KotlinWrappers that provides a
FC<Props> { }
for Components. With context receivers I was able to just make my own FCs with much used stuff like Themes or Internationalization already inside so that we won't need
Copy code
useTheme()
useIntl()
usePermissions()
useNavigation()
in every top level component. Kinda sad to see that go it made them much more compact and readable. Only alternative would be to have them as additional parameters like props already is.
y
I'm not very familiar with the library in question, but in general, with context parameters, you want to make your own "bridge functions" that call into functions of your library. For instance, if you have some interface like:
Copy code
interface Foo {
  fun foo()
  fun bar(baz: Baz)
}
then you want to make functions like so:
Copy code
context(f: Foo)
fun foo() = f.foo()

context(f: Foo)
fun bar(baz: Baz) = f.bar(baz)
so, try to consider when exactly you need to call
contextOf<Blah>()
, and instead, make contextual bridge functions that use
Blah
as a context and call the functions you want instead
h
Yeah, I already implemented the bridge functions for the migration from 2.1.20 to 2.3.20 like
Copy code
context(theme: Theme)
val spacing get() = theme.spacing

context(theme: Theme)
val shadows get() = theme.shadows

context(theme: Theme)
val palette get() = theme.palette
It may depend on how often we need new fields/functions from these contexts. For now we don't use that many, but it could also turn out to be a hassle. In the backend the switch to context parameters was an absolute non-issue.