I want to know if it is possible in Kotlin to create a "Context" from which objects can get data from. Something like the following:
MyScope(myGlobalObject) {
val objectA = MyClassA()
objectA.doSomething()
}
ClassA() {
fun doSomething() {
val resultA = ...
myGlobalObject.add(resultA)
val objectB = ClassB()
objectB.doSomethingElse()
}
}
ClassB() {
fun doSomethingElse() {
val resultB = ...
myGlobalObject.add(resultB)
}
}
I wouldn't want MyScope to be a singleton because this is multi-threaded environment (server request) where I wouldn't want other threads messing up with context within local instances of MyScope
Code above is in the spirit of avoiding passing around myGlobalObject
several levels down as follows:
val objectA = MyClassA(myGlobalObject)
objectA.doSomething()
ClassA constructor(val myGlobalObject) {
fun doSomething() {
val resultA = ...
myGlobalObject.add(resultA)
val objectB = ClassB(myGlobalObject)
objectB.doSomethingElse()
}
}
ClassB constructor(val myGlobalObject) {
fun doSomethingElse() {
val resultB = ...
myGlobalObject.add(resultB)
}
}
In other words, similar to other frameworks like React with ReactContexts. Been searching the internet and seems possible, JetPack compose has the feel of it but not sure if it possible to pass down data like above, or if there is a way to "reach" into a scope from any part of the code... I know it might return null or non-initialized but that is something I would be perfectly fine with
I believe you are looking for the experimental feature of Context Receivers. You can enable this by passing the -Xcontext-receivers
compiler option.
Suppose you want your functions to access such an object:
// as per your requirements, not a singleton
class SomeGlobalObject {
fun foo() {
println("foo")
}
fun bar() {
println("bar")
}
}
You can prefix your function declaration with context(SomeGlobalObject)
:
context(SomeGlobalObject)
fun thisNeedsAGlobalObject() {
// here you can use members of SomeGlobalObject without qualification
foo()
bar()
// if there is ambiguity, you can qualify it with 'this@SomeGlobalObject'
this@SomeGlobalObject.foo()
}
Functions that need a SomeGlobalObject
can call other functions that need a SomeGlobalObject
:
context(SomeGlobalObject)
fun somethingElseThatNeedsAGlobalObject() {
thisNeedsAGlobalObject()
}
You can use with
to start a scope where SomeGlobalObject
exists:
fun main() {
with(SomeGlobalObject()) {
thisNeedsAGlobalObject()
}
}
Adapting this to your ClassA
and ClassB
example, it would look like this:
fun main() {
val globalObject = SomeGlobalObject()
with(globalObject) {
val objectA = ClassA()
// globalObject is implicitly passed to doSomething here
objectA.doSomething()
}
println(globalObject.list)
}
class ClassA {
context(SomeGlobalObject)
fun doSomething() {
val resultA = "A..."
// 'this@SomeGlobalObject' is redundant here, but just to be clear
this@SomeGlobalObject.add(resultA)
val objectB = ClassB()
// globalObject is implicitly passed to doSomething here
objectB.doSomethingElse()
}
}
class ClassB {
context(SomeGlobalObject)
fun doSomethingElse() {
val resultB = "B..."
// 'this@SomeGlobalObject' is redundant here, but just to be clear
this@SomeGlobalObject.add(resultB)
}
}
class SomeGlobalObject {
val list = mutableListOf<Any>()
fun add(x: Any) {
println("Adding $x...")
list.add(x)
}
}
You can also make a function require multiple context receivers of different types. See the KEEP proposal for more features.