web-dev-qa-db-fra.com

Rappel existant à 3 fonctions de Kotlin Coroutines

J'ai une question générale avec un exemple spécifique: je voudrais utiliser la magie de la coroutine Kotlin au lieu de l'enfer de rappel dans Android lorsque vous prenez une photo.

manager.openCamera(cameraId, object : CameraDevice.StateCallback() {
    override fun onOpened(openedCameraDevice: CameraDevice) {
        println("Camera onOpened")
        // even more callbacks with openedCameraDevice.createCaptureRequest()....
    }

    override fun onDisconnected(cameraDevice: CameraDevice) {
        println("Camera onDisconnected")
        cameraDevice.close()
    }
    ...

Comment pourrais-je convertir ça en ... euh ... quelque chose de moins moche? Est-il possible de prendre un rappel moyen avec environ trois fonctions et de le transformer en chaîne de promesses en désignant le flux principal comme chemin de résultat de promesse? Et si oui, dois-je/dois-je utiliser des coroutines pour le rendre asynchrone?

J'adorerais quelque chose avec async et .await qui se traduirait par

manager.open(cameraId).await().createCaptureRequest()

J'essaie de le faire à travers quelque chose comme ce qui suit, mais ... Je ne pense pas que j'utilise CompletableDeferred à droite!

suspend fun CameraManager.open(cameraId:String): CameraDevice {
    val response = CompletableDeferred<CameraDevice>()
    this.openCamera(cameraId, object : CameraDevice.StateCallback() {
        override fun onOpened(cameraDevice: CameraDevice) {
            println("camera onOpened $cameraDevice")
            response.complete(cameraDevice)
        }

        override fun onDisconnected(cameraDevice: CameraDevice) {
            response.completeExceptionally(Exception("Camera onDisconnected $cameraDevice"))
            cameraDevice.close()
        }

        override fun onError(cameraDevice: CameraDevice, error: Int) {
            response.completeExceptionally(Exception("Camera onError $cameraDevice $error"))
            cameraDevice.close()
        }
    }, Handler())
    return response.await()
}
18
Benjamin H

Dans ce cas particulier, vous pouvez utiliser une approche générale pour convertir une API basée sur le rappel en une fonction de suspension via la fonction suspendCoroutine:

suspend fun CameraManager.openCamera(cameraId: String): CameraDevice? =
    suspendCoroutine { cont ->
        val callback = object : CameraDevice.StateCallback() {
            override fun onOpened(camera: CameraDevice) {
                cont.resume(camera)
            }

            override fun onDisconnected(camera: CameraDevice) {
                cont.resume(null)
            }

            override fun onError(camera: CameraDevice, error: Int) {
                // assuming that we don't care about the error in this example
                cont.resume(null) 
            }
        }
        openCamera(cameraId, callback, null)
    }

Maintenant, dans votre code d'application, vous pouvez simplement faire manager.openCamera(cameraId) et obtenir une référence à CameraDevice si elle a été ouverte avec succès ou null si elle ne l'a pas été.

44
Roman Elizarov

J'ai utilisé 2 solutions pour ce type de chose.

1: encapsuler l'interface dans une extension

CameraDevice.openCamera(cameraId: Integer, 
                onOpenedCallback: (CameraDevice) -> (), 
          onDisconnectedCallback: (CameraDevice) ->()) {

    manager.openCamera(cameraId, object : CameraDevice.StateCallback() {
        override fun onOpened(openedCameraDevice: CameraDevice) {
            onOpenedCallback(openedCameraDevice)
        }

        override fun onDisconnected(cameraDevice: CameraDevice) {
            onDisconnectedCallback(cameraDevice)
        }
   })
}

2: Créez une classe de conteneur simple avec une interface plus fonctionnelle:

class StateCallbackWrapper(val onOpened: (CameraDevice) -> (), val onClosed: (CameraDevice) ->()): CameraDevice.StateCallback() {
    override fun onOpened(openedCameraDevice: CameraDevice) {
        onOpened(openedCameraDevice)
    }

    override fun onDisconnected(cameraDevice: CameraDevice) {
        onClosed(cameraDevice)
    }
}

Personnellement, je commencerais par quelque chose comme ça, puis je construirais les différences de filetage en plus.

2
PeejWeej