Gabi
06/05/2022, 8:18 PMException in thread "DefaultDispatcher-worker-6 @track-session/cu#80" java.lang.NullPointerException: Cannot invoke "kotlinx.coroutines.flow.Flow.collect(kotlinx.coroutines.flow.FlowCollector, kotlin.coroutines.Continuation)" because "this.$this_unsafeTransform$inlined" is null
at kotlinx.coroutines.flow.FlowKt__TransformKt$onEach$$inlined$unsafeTransform$1.collect(SafeCollector.common.kt:113)
at kotlinx.coroutines.flow.FlowKt__CollectKt.collect(Collect.kt:30)
at kotlinx.coroutines.flow.FlowKt.collect(Unknown Source)
at gragas.play.TrackSession.playRegisteredTracks(TrackSession.kt:117)
at gragas.play.TrackSession.access$playRegisteredTracks(TrackSession.kt:38)
at gragas.play.TrackSession$3.invokeSuspend(TrackSession.kt:58)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:749)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664)
Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [CoroutineName(track-session/cu), CoroutineId(80), "track-session/cu#80":StandaloneCoroutine{Cancelling}@6465a67d, Dispatchers.Default]
This is the source of playRegisteredTracks
private suspend fun playRegisteredTracks() {
queue
.onEach { song ->
player.playTrack(song)
}
.collect()
}
And this is the full source of TrackSession.kt
https://gist.github.com/2cc063411159091c45c3a3e1d4cc8155Nick Allen
06/05/2022, 9:11 PMqueue
property so the coroutine calling playRegisteredTracks
is launched before queue
is assigned. Similarly, the init block is above the connection
property so if (!::connection.isInitialized) {
is will always be true.
Many view launching coroutines during construction as an anti-pattern and would instead suggest something more like:
class TrackSession private constructor(...) {
companion object {
operator fun invoke(..., scope: CoroutineScope): TrackSession =
TrackSession(...).also { //Constructor just creates the instance
launchBackgroundWork(scope)
}
}
(I've never really had a problem with launching coroutines during construction but seeing this error reminds me how easy it is to mix up ordering so maybe I'll start treating it as an anti-pattern)
It's also a widely recognized anti-pattern to implement an interface just to avoid typing in the implementation. You don't really plan on calling myTrackSession.launch { ... }
from other classes do you so why be a CoroutineScope
? Just have a scope property and call scope.launch
.
Finally, how do those coroutines end? You aren't passing in an external scope (so some other owner would cancel the coroutine) and you have no close
method to shut down the work. If this is a singleton (created only one ever and then alive for entire process) then it's fine, but otherwise, it's probably better to end them somehow. GC can sometimes save you from lingering coroutines but it's not something I'd recommend relying on.Nick Allen
06/05/2022, 9:23 PMThe init block is above theI wasn't really clear but I suspect this is your issue. Thatproperty so the coroutine callingqueue
is launched beforeplayRegisteredTracks
is assigned.queue
queue
is null because it hasn't been assigned yet.Gabi
06/05/2022, 9:26 PM