https://kotlinlang.org logo
#coroutines
Title
# coroutines
g

gildor

06/07/2019, 10:31 AM
Never use this anti-pattern is the solution
👌 2
m

Mark

06/07/2019, 10:35 AM
Okay, thanks. Any pointer to some likely alternative?
g

gildor

06/07/2019, 10:35 AM
What is your use case for this?
m

Mark

06/07/2019, 10:36 AM
Various, but the most common one is needing a (application) context in a singleton.
w

wbertan

06/07/2019, 10:38 AM
Should not DI solve that?
m

Mark

06/07/2019, 10:39 AM
I’ve been holding off DI up until now, because the landscape seems to be changing a lot.
👀 1
🤔 1
I mean the decision of whether to use Dagger 2, Kodein, Koin etc
w

wbertan

06/07/2019, 10:44 AM
You already did some decisions, your architecture, your framework, your language, that is one of those decisions, where good or bad, will not be eternal (as some years back people choosing Java and now migrating to Kotlin).
Either way, as @gildor said,
Never use this anti-pattern
, but I believe this could achieve what you want (I think the
Asas2
is what you want)
Copy code
class TestAsas {
    object Asas : (String) -> String {
        override fun invoke(p1: String): String = "Welcome $p1!"
    }

    @Test
    fun asas() {
        val result = Asas("Me")

        Assert.assertEquals("Welcome Me!", result)
    }

    object Asas2 : (String) -> () -> String {
        lateinit var INSTANCE: () -> String

        override fun invoke(p1: String): () -> String {
            if(!::INSTANCE.isInitialized){
                { "Welcome $p1!" }.also { INSTANCE = it }
            }
            return INSTANCE
        }
    }

    @Test
    fun asas2() {
        Asas2("Me")

        val result1 = Asas2.INSTANCE()
        Assert.assertEquals("Welcome Me!", result1)

        val result2 = Asas2.INSTANCE()
        Assert.assertEquals("Welcome Me!", result2)
    }
}
It also prevent other initialisation:
Copy code
@Test
    fun asas3() {
        Asas2("Me")

        val result1 = Asas2.INSTANCE()
        Assert.assertEquals("Welcome Me!", result1)

        Asas2("Other")

        val result2 = Asas2.INSTANCE()
        Assert.assertEquals("Welcome Me!", result2)
    }
l

louiscad

06/07/2019, 10:49 AM
If you need applicationContext, I already solved the problem with the little library Splitties App Context that gives you a top level
appCtx
property.
m

Mark

06/07/2019, 10:50 AM
Yes, I saw that thanks. Although sometimes, I need something other than the context.
g

gildor

06/07/2019, 10:53 AM
“Context” case and “something other” are completely different things
1
Get application context is easy, just use Louis’ code or just write top level property and set it on Application onCreate/onBaseContextAttaches
not perfect, but much better than use singleton with params
I need something other than the context.
Yes, most probably you need DI
1
d

Dico

06/07/2019, 10:56 AM
I think it's a misconception that you need library to follow DI pattern
☝️ 1
Just pass all dependencies to constructor
g

gildor

06/07/2019, 10:56 AM
even without using any framework, just create some dependencies graph and make it available globally, as just super simple solution
d

Dico

06/07/2019, 10:57 AM
In Kotlin it's ridiculously easy, since it removes a bunch of boilerplate
g

gildor

06/07/2019, 10:57 AM
or use common Android pattern with service locator based on retrieving dependencies from Application context
m

Mark

06/07/2019, 10:58 AM
yes, this more or less how I currently do it
g

gildor

06/07/2019, 10:58 AM
Just pass all dependencies to constructor
But because you cannot do this for Android components, instead you can do this by getting from Application context
m

Mark

06/07/2019, 12:20 PM
To better describe the problem: Suppose a
MyApplication
object (
Application
subclass) has a property
myImmutableConfig
and there is a singleton (by intention) class that can be instantiated by
suspend fun createMySingleton(myImmutableConfig): MySingleton
and I want to be able to access MySingleton from a suspending function. If accessed from a blocking function, then presumably it would potentially block the main thread.
My first thoughts are something like this, using lateinit, similar to @wbertan suggestion earlier. But this way isn’t thread safe by the looks of it:
Copy code
private lateinit var INSTANCE: MySingleton

suspend fun getInstance(): MySingleton {
    if(!::INSTANCE.isInitialized){
        INSTANCE = createMySingleton(myApplication.myImmutableConfig)
    }
    return INSTANCE
}

fun getInstanceBlocking(): MySingleton {
    if(!::INSTANCE.isInitialized) {
        runBlocking { 
            return@runBlocking getInstance()
        }
    }
    return INSTANCE
}
g

gildor

06/07/2019, 1:04 PM
No need to use all those isInitialized or singeltons, if we talking just about caching of value in property in a suspend way, just use async
Copy code
val myConfig: Deferred<MyConfig> = GlobalScope.async(start = Lazy) {
   createMyConfig(myApplication.myImmutableConfig)
}
And to use just
myConfig.await()
m

Mark

06/07/2019, 1:06 PM
Using GlobalScope?
ah ok
g

gildor

06/07/2019, 1:06 PM
Any scope what you want
depends on case
if this a property in a class, use scope of this class etc
but your example above looks more like some global property, so up to you
you even can get rid of Deferred by wrapping to suspend function:
Copy code
private val myConfig: Deferred<MyConfig> = ...

suspend fun myConfig(): MyConfig = myConfig.await()
But in general, because you use this on Application level GlobalScope is fine
m

Mark

06/07/2019, 1:13 PM
So much nicer, thanks!
Copy code
private val INSTANCE_DEFERRED = GlobalScope.async(start = CoroutineStart.LAZY) {
    createMySingleton(myApplication.myImmutableConfig)
}
suspend fun getInstance() = INSTANCE_DEFERRED.await()
g

gildor

06/07/2019, 1:16 PM
as you can see, there is no singletons with params, just some global/application state (which has own problems, but not more than for example common singleton)
also it’s not blocking
m

Mark

06/07/2019, 1:18 PM
I see, yes. Although the instance can only be accessed from a suspending function.
And then from non-suspending code:
Copy code
fun getInstanceBlocking() = if (INSTANCE_DEFERRED.isCompleted) {
	INSTANCE_DEFERRED.getCompleted()
} else {
    runBlocking {
        getInstance()
    }
}
g

gildor

06/07/2019, 1:36 PM
Of course, from suspending, because it's asyncronous
For blocking version of this function I wouldn't check for completed, just wrap to runBlockimg
m

Mark

06/07/2019, 1:37 PM
ok, thanks
g

gildor

06/07/2019, 1:38 PM
Also, I wouldn't introduce blocking version, just because it makes all this code not so safe and potentially you can accidentally block main thread
Blocking version maybe useful only for some very special use cases, but in this case I would just use runBlocking on call site
m

Mark

06/07/2019, 1:39 PM
Right, then it’s more explicit
5 Views