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

bj0

12/26/2017, 5:41 PM
i'm getting some inconsistent crashes when I try to use
launch {}
in a button click handler:
java.lang.NullPointerException: Attempt to invoke interface method 'kotlin.coroutines.experimental.CoroutineContext$Element kotlin.coroutines.experimental.CoroutineContext.get(kotlin.coroutines.experimental.CoroutineContext$Key)' on a null object reference
d

dave08

12/26/2017, 7:04 PM
Maybe the UI context is null when the activity is destroyed, and the coroutine is still running or something else having to do with the coroutine context passed...? You followed the ui guide?
b

bj0

12/26/2017, 7:45 PM
it happens on a button click
d

dave08

12/26/2017, 8:35 PM
@bj0 Could you post that code?
b

bj0

12/26/2017, 8:46 PM
it's part of a large project, i'll see if i can isolate it
i simplified it down to just this:
Copy code
view.data_button.setOnClickListener {
            if (datCon.isStarted)
                datCon.stop()
            else {
                warn { "launching" }
                launch {
                    warn { "launch" }
                    delay(3000)
//                    try {
//                        datCon.start()
//                    } catch (e: Exception) {
//                        err { ":${e.message}" }
//                    }
                }
            }
        }
called in
onCreateView
from a
Fragment
it doesn't happen every time, but sometimes when i start he program and press the button it throws the exception, sometimes it doesn't
d

dave08

12/26/2017, 9:18 PM
launch
tries retreiving a
Job
from the
CoroutineContext
that you pass to the
launch(context)
, it looks like that context is null sometimes.... maybe try a breakpoint or log there to check its value... and when the coroutine actually gets started.
Also, according to your sample, you can't modify any ui elements in
launch
since it runs by default on
CommonPool
context and not
UI
Also, if you leave the fragment (and it gets garbage collected), and go back in, your original
launch
will still be running, and a new one will get created at every click.. you might get a leak there...
b

bj0

12/26/2017, 9:29 PM
I'm not specified the context, so it's using the default
CommonPool
, and I'm not touching any UI elements. I'm not worried about leaks because this fragment is embedded and doesn't get cleaned up until the app is closed
but I literally just open the app and click the button, sometimes it crashes and sometimes it works as expected, it's gotta be a race condition somewhere
d

dave08

12/26/2017, 9:37 PM
Maybe replace the
launch
with an
async { }.await()
there might be a hiddwn exception. Or if it's a race, maybe try a conflated actor like in the ui guide:
Copy code
fun Node.onClick(action: suspend (MouseEvent) -> Unit) {
// launch one actor to handle all events on this node
val eventActor = actor<MouseEvent>(UI, capacity = Channel.CONFLATED) { // <--- Changed here
for (event in channel) action(event) // pass event to action
}
// install a listener to offer events to this actor
onMouseClicked = EventHandler { event ->
eventActor.offer(event)
}
}
Or try declaring a
val parentJob = Job()
in the Fragment, and running launch(CommonPool + parentJob) and cancel it when it makes sense...
What happens if you click a few times.. dataCon.start() will be run a few times each time on a different thread...!
It could be before the isStarted is even set... since theres the delay...
b

bj0

12/26/2017, 9:47 PM
that's commented out, it crashes with literally just
launch { delay() }
but only the "launching" makes it to the ADB log, the "launch" doesn't (when it crashes). when it works the first time, it keeps working if I keep mashing the buttonf
d

dave08

12/26/2017, 9:58 PM
Its just that the error is funny, why should he coroutine context be null? Maybe this is a bug? But the actor might just be the workaround (maybe together with the parentJob cancellation), since that assures the dataCon.start() will only get called once and that it will set isStarted before the next press in considered...
b

bj0

12/26/2017, 10:05 PM
yea I understand the comments you're making, they make sense but aren't related to the bug. I just did a quick
launch{}
because
.start()
is a
suspend
function, and I just need to fire it off. I plan on cleaning up that structure later, but I'm currently working on dealing with BLE details atm so I just needed a quick
launch
since after I comment out the actual code and just
launch { delay() }
crashes, I expect it's a bug in the coroutine library
I'm curious if it's been seen before, though my underlying setup is a bit unique
7 Views