kable: Android - observer.collect throws NoSuchElementException

I’m trying to read some data of a UART Service that provides a “write without response” and a “notify” characteristic. I can write to it fine using cable but Kable but get the following error when using observer.collect { ... } to subscribe to notifications.

java.util.NoSuchElementException: Collection contains no element matching the predicate.

The characteristic works fine to read using other apps and sends out data about once a second. As probably will be apparent from my sample code below I’m fairly new to Kotlin but as far as I understand after reading up and digging through the source code I can’t see why this error is produced.

I looked at issue 38 but since I go through the steps to get the characteristic I get this error even if it is discovered. Or did i misunderstand the cause of that issue?

The characteristic and observer objects looks fine to me, what I can notice is that the

Sample code

bluetoothScope.launch {
            val peripheral: Peripheral? = getPeripheral() // Get Peripheral using Scanner()
            if (peripheral != null) {
                peripheral.connect()
                val characteristic = peripheral.services
                        ?.first { service -> service.serviceUuid == UUID.fromString(UART_SERVICE)}
                        ?.characteristics?.first { characteristic ->
                            characteristic.characteristicUuid == UUID.fromString(RX_CHARACTERISTIC_UUID)
                        }
                if (characteristic != null ) {
                    val observer = peripheral.observe(characteristic)
                                try {
                                    observer.collect { data ->
                                        Log.d(TAG, data)
                                    }
                                } catch (e: NoSuchElementException) {
                                    Log.d(TAG, "Error: $e")
                                }
                }
                peripheral.state?.collect { state ->
                    when (state) {
                        State.Connected -> {
                            Log.d(TAG, "Connected")
                        }
                        else -> Log.d(TAG, "Not Connected")
                    }
                }
            }

        }
    }

Scan result

DiscoveredService(serviceUuid=6e400001-b5a3-f393-e0a9-e50e24dcca9e, 
characteristics=[
    DiscoveredCharacteristic(
        serviceUuid=6e400001-b5a3-f393-e0a9-e50e24dcca9e,
        characteristicUuid=6e400003-b5a3-f393-e0a9-e50e24dcca9e, 
        descriptors=[]), 
    DiscoveredCharacteristic(
        serviceUuid=6e400001-b5a3-f393-e0a9-e50e24dcca9e, 
        characteristicUuid=6e400002-b5a3-f393-e0a9-e50e24dcca9e, 
        descriptors=[])]
)]

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 15 (9 by maintainers)

Most upvoted comments

Your issue is actually closely related to #38, in that I believe you’re still being hit by the internal characteristic lookup failure within write or read, despite having retrieved the characteristic object from the discovered services.

Definitely a shortcoming in how the write and read functions retrieve the platform characteristic object: The services property converts the list of services to List<DiscoveredService>, whereas DiscoveredService only holds UUID details about the service (the reference to the actual platform service object is not held, to ensure that library consumers don’t hold on to/leak the platform’s service/characteristic objects).

Unfortunately this means that a lookup (in Peripheral’s internal service cache) is still performed even when manually finding the service/characteristic yourself (as you have done).

For Android, in the future, we may consider holding a weak reference in the services/characteristics provided by services property, making the lookup only necessary if the weak reference is lost.

In the mean time, I’ll be sure and get #38 closed out soon and push a SNAPSHOT or get another version published. It will at least give you a clearer picture of what is going wrong. Additionally, I’ll brainstorm other possible causes of the failure you’re seeing.

I haven’t investigated further but I assumed that the error was caused here in acquire() in Observers?

I suspect it is actually here (as described above):

https://github.com/JuulLabs/kable/blob/c750de57fa6b0ea3dbd148dee3807b95e4ec1719/core/src/jsMain/kotlin/Peripheral.kt#L207-L212

Please let me know if I can help you troubleshoot in anyway.

Thanks for the help and issue report. I’ll keep you posted.

I have reproduced similar crash with SensorTag example when disconnect peripheral in during of connecting. You can reproduce this with my peripheral app attached here peripheral.apk.zip

java.util.NoSuchElementException: Collection contains no element matching the predicate. at com.juul.kable.PeripheralKt.bluetoothGattCharacteristicFrom(Peripheral.kt:269) at com.juul.kable.PeripheralKt.access$bluetoothGattCharacteristicFrom(Peripheral.kt:1) at com.juul.kable.AndroidPeripheral.startNotifications$core_debug(Peripheral.kt:193) at com.juul.kable.Observers$acquire$1.invokeSuspend(Observers.kt:45) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:342) at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith$default(DispatchedContinuation.kt:263) at kotlinx.coroutines.internal.ScopeCoroutine.afterCompletion(Scopes.kt:27) at kotlinx.coroutines.JobSupport.continueCompleting(JobSupport.kt:933) at kotlinx.coroutines.JobSupport.access$continueCompleting(JobSupport.kt:28) at kotlinx.coroutines.JobSupport$ChildCompletion.invoke(JobSupport.kt:1152) at kotlinx.coroutines.JobSupport.notifyCompletion(JobSupport.kt:1520) at kotlinx.coroutines.JobSupport.completeStateFinalization(JobSupport.kt:323) at kotlinx.coroutines.JobSupport.finalizeFinishingState(JobSupport.kt:240) at kotlinx.coroutines.JobSupport.tryMakeCompletingSlowPath(JobSupport.kt:903) at kotlinx.coroutines.JobSupport.tryMakeCompleting(JobSupport.kt:860) at kotlinx.coroutines.JobSupport.makeCompletingOnce$kotlinx_coroutines_core(JobSupport.kt:825) at kotlinx.coroutines.AbstractCoroutine.resumeWith(AbstractCoroutine.kt:111) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:226) at android.os.Handler.handleCallback(Handler.java:873) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:214) at android.app.ActivityThread.main(ActivityThread.java:7156) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:975)