sample-apps-for-matter-android icon indicating copy to clipboard operation
sample-apps-for-matter-android copied to clipboard

[Enhancement] Provide CHIP APIs coroutine extension functions

Open tetedoie opened this issue 1 year ago • 1 comments

The CHIP APIs are mostly Java callback based APIs but most developers will use Kotlin (and Kotlin coroutines).

In GHSAFM, we have 3 wrappers classes (ChipClient, ClustersHelper and SubscriptionHelper) which are often only adapting the CHIP Java callback based APIs into Kotlin coroutines. In order to avoid that every developer implement and maintain this code on their own, we could provide Kotlin extension functions inside an additional maven artifact.

For example, in ChipClient we have

suspend fun getConnectedDevicePointer(nodeId: Long): Long {
    return suspendCoroutine { continuation ->
      chipDeviceController.getConnectedDevicePointer(
          nodeId,
          object : GetConnectedDeviceCallback {
            override fun onDeviceConnected(devicePointer: Long) {
              Timber.d("Got connected device pointer")
              continuation.resume(devicePointer)
            }

            override fun onConnectionFailure(nodeId: Long, error: Exception) {
              val errorMessage = "Unable to get connected device with nodeId $nodeId."
              Timber.e(errorMessage, error)
              continuation.resumeWithException(IllegalStateException(errorMessage))
            }
          })
    }
  }

(We also have awaitGetConnectedDevicePointer which seams to be duplicate code)

So we could provide an extension function on ChipDeviceController like

suspend fun ChipDeviceController.getConnectedDevicePointer(
    nodeId: Long
): Long = suspendCoroutine {
    getConnectedDevicePointer(
        nodeId,
        object : GetConnectedDeviceCallback {
            override fun onDeviceConnected(devicePointer: Long) {
                it.resume(devicePointer)
            }

            override fun onConnectionFailure(nodeId: Long, error: Exception?) {
                it.resumeWithException(
                    IllegalStateException(
                        "Unable to get connected device with nodeId $nodeId.",
                        error
                    )
                )
            }
        }
    )
}

The same can be done for predefined clusters like for

    public static class DescriptorCluster extends BaseChipCluster {

        [...]

        public void readDeviceTypeListAttribute(DeviceTypeListAttributeCallback callback) {
            [...]
        }

        public void subscribeDeviceTypeListAttribute(DeviceTypeListAttributeCallback callback, int minInterval, int maxInterval) {
            [...]
        }
      
        [...]
  }

We could provide

suspend fun ChipClusters.DescriptorCluster.readDeviceTypeListAttribute(
): List<ChipStructs.DescriptorClusterDeviceTypeStruct> = suspendCoroutine {
    readDeviceTypeListAttribute(
        object : ChipClusters.DescriptorCluster.DeviceTypeListAttributeCallback {
            override fun onSuccess(
                values: List<ChipStructs.DescriptorClusterDeviceTypeStruct>
            ) {
                it.resume(values)
            }

            override fun onError(ex: Exception) {
                it.resumeWithException(ex)
            }
        }
    )
}

fun ChipClusters.DescriptorCluster.subscribeDeviceTypeListAttribute(
    minInterval: Int,
    maxInterval: Int
): Flow<List<ChipStructs.DescriptorClusterDeviceTypeStruct>> = callbackFlow {
    subscribeDeviceTypeListAttribute(
        object : ChipClusters.DescriptorCluster.DeviceTypeListAttributeCallback {
            override fun onSuccess(valueList: MutableList<ChipStructs.DescriptorClusterDeviceTypeStruct>) {
                trySend(valueList)
            }

            override fun onError(ex: java.lang.Exception?) {
                close(ex)
            }
        },
        minInterval,
        maxInterval
    )
}

As both use the same callback type, we could even share the callback creation inside a private function but it's just an implementation detail.

This is an enhancement proposal, the actual CHIP artifacts should be published first. Then it should be implemented in the CHIP repository instead of the GHSAFM repository.

tetedoie avatar Mar 27 '23 10:03 tetedoie

Thanks Paul-Marie. Makes sense. We should get resolution on #80 soon, and then we should tackle this as you propose.

pierredelisle avatar Apr 09 '23 00:04 pierredelisle