Hello, we have a gps location service that we cre...
# coroutines
p
Hello, we have a gps location service that we create using dagger and we are getting an exception that
_location
channel was closed and we are not sure how can this happen since
_location
is a private member and there is no call to
_location.close()
. What are we missing? Dagger
Copy code
@Provides
    @Singleton
    fun gpsLocationService(): GpsLocationService = GpsLocationService(
        context = context,
        coroutineContext = Dispatchers.Default
    )
Service
Copy code
class GpsLocationService(
    private val context: Context,
    override val coroutineContext: CoroutineContext
) : CoroutineScope {

    private val logger = Logger.get(this::class)

    private val _location = BroadcastChannel<Location>(1)

    private val interval: Millis = GpsUpdateInterval.High.interval

    private val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)

    private val locationCallback: LocationCallback = object : LocationCallback() {
        override fun onLocationResult(locationResult: LocationResult) {
            if (GpsHelper.isGpsEnabled(context)) {
                logger.trace("Location acquired (${locationResult.lastLocation}).")
                _location.sendBlocking(locationResult.lastLocation)
            } else {
                logger.trace("GPS is disabled, ignoring location change.")
            }
        }
    }

    suspend fun requestLocation(): Location? = _location.openSubscription().consume {
        val location = withTimeoutOrNull(15.seconds) { receiveOrNull() }

        location
    }
}
b
Using
_location.openSubscription().consume {}
will invoke
cancel()
after consuming. You should use
consumeEach
instead.
You should consider changing
requestLocation()
to return
Flow<Location>
BroadcastChannel
has an
asFlow
extension AFAIK
p
I need one value and that's why I use consume (consumeEach uses consume as well just uses a loop to retrieve next values). Consume does not close the broadcast channel
Copy code
@Test
    fun test() {
        val a = BroadcastChannel<Unit>(1)

        runBlocking {
            a.openSubscription().consume {

            }
        }

        kotlin.test.assertEquals(a.isClosedForSend, false)
    }
b
But why then use a
BroadcastChannel
?
p
Copy code
fun requestUpdates(): ReceiveChannel<Location> = _location.openSubscription()
I have this method in the class as well just did not copy it into the example.
@elizarov are there any other ways besides
.close()
that a broadcast channel could be closed? I can not figure that out from the stack trace since there is no information about who closed the channel.
Copy code
Fatal Exception: kotlinx.coroutines.channels.ClosedSendChannelException: Channel was closed
       at kotlinx.coroutines.channels.Closed.getSendException(AbstractChannel.kt:1048)
       at kotlinx.coroutines.channels.AbstractSendChannel.offer(AbstractChannel.kt:166)
       at kotlinx.coroutines.channels.AbstractSendChannel.send(AbstractChannel.kt:146)
       at kotlinx.coroutines.channels.BroadcastCoroutine.send$suspendImpl(Broadcast.kt)
       at kotlinx.coroutines.channels.BroadcastCoroutine.send(Broadcast.kt)
       at kotlinx.coroutines.channels.BroadcastKt$broadcast$1.invokeSuspend(Broadcast.kt:30)
       at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
       at kotlinx.coroutines.ResumeModeKt.resumeMode(ResumeMode.kt:67)
       at kotlinx.coroutines.DispatchedKt.resume(Dispatched.kt:319)
       at kotlinx.coroutines.DispatchedKt.resumeUnconfined(Dispatched.kt:49)
       at kotlinx.coroutines.DispatchedKt.dispatch(Dispatched.kt:298)
       at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:250)
       at kotlinx.coroutines.CancellableContinuationImpl.completeResume(CancellableContinuationImpl.kt:327)
       at kotlinx.coroutines.channels.AbstractChannel$ReceiveHasNext.completeResumeReceive(AbstractChannel.kt:907)
       at kotlinx.coroutines.channels.ArrayBroadcastChannel$Subscriber.checkOffer(ArrayBroadcastChannel.kt:256)
       at kotlinx.coroutines.channels.ArrayBroadcastChannel.checkSubOffers(ArrayBroadcastChannel.kt:127)
       at kotlinx.coroutines.channels.ArrayBroadcastChannel.offerInternal(ArrayBroadcastChannel.kt:96)
       at kotlinx.coroutines.channels.AbstractSendChannel.offer(AbstractChannel.kt:160)
       at kotlinx.coroutines.channels.AbstractSendChannel.send(AbstractChannel.kt:146)
       at com.eld.android.gps.GpsLocationService$locationCallback$1$onLocationResult$1.invokeSuspend(GpsLocationService.kt:34)
       at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
       at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:241)
       at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:270)
       at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:79)
       at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:54)
       at kotlinx.coroutines.BuildersKt.runBlocking(:1)
       at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:36)
       at kotlinx.coroutines.BuildersKt.runBlocking$default(:1)
       at com.eld.android.gps.GpsLocationService$locationCallback$1.onLocationResult(GpsLocationService.kt:34)
       at com.google.android.gms.internal.location.zzau.notifyListener()
       at com.google.android.gms.common.api.internal.ListenerHolder.notifyListenerInternal(:17)
       at com.google.android.gms.common.api.internal.ListenerHolder$zaa.handleMessage(:5)
       at android.os.Handler.dispatchMessage(Handler.java:111)
       at com.google.android.gms.internal.base.zap.dispatchMessage(:8)
       at android.os.Looper.loop(Looper.java:207)
       at android.os.HandlerThread.run(HandlerThread.java:61)
b
Its caused by
consume
. Just sayin'. Check docs on
consume
and
cancel
. You probably call
requestLocation()
somewhere before you invoke your
locationCallback
.
p
Then why does my given test case not close the broadcast channel?