Kind of a design question that is enabled by corou...
# coroutines
p
Kind of a design question that is enabled by coroutines semantics:
Copy code
/**
   * This function returns once a shake was detected
   */
  suspend fun detect() {
    val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager?
      ?: awaitCancellation()
    val shakeDetected = CompletableDeferred<Unit>()
    val listener = SeismicShakeDetector.Listener {
      shakeDetected.complete(Unit)
    }
    val shakeDetector = SeismicShakeDetector(listener)
    try {
      shakeDetector.start(sensorManager)
      shakeDetected.await()
    } finally {
      shakeDetector.stop()
    }
  }
I’ve written this function that suspend until a shake was detected. Do you think such a usage of coroutines makes sense? It feels kind of unintuitive in terms of ergonomics of a regular function
m
I think it makes sense, but I would use a
suspendCancellableCoroutine
Correct solution doesn't handle cancelation correctly, and
suspendCancellableCoroutine
already will handle the completable for you so it is a bit easier to use.
a
This looks like an almost ideal usage of coroutines: wait without blocking for something to happen, with cancellation and cleanup provided by structured concurrency. +1 for the direct use of
suspendCancellableCoroutine
, which is the perfect tool for adapting a callback like this.
p
Yep, that didn’t came to my mind because I don’t really have a return value here
Copy code
suspend fun detect() {
    suspendCancellableCoroutine<Unit> { cont ->
      val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager?
        ?: return@suspendCancellableCoroutine
      val listener = SeismicShakeDetector.Listener {
        cont.resume(Unit)
      }
      val shakeDetector = SeismicShakeDetector(listener)
      shakeDetector.start(sensorManager)
      cont.invokeOnCancellation {
        shakeDetector.stop()
      }
    }
  }
Made the code even easier 🙂
Hmm, but if I remember correctly it throws if you resume twice, so that might be an issue here
m
I would hope that the stop would get called before the next call to the listener because everything is happening on the same thread, but I doubt it is guaranteed. So might need to check if it is already resumed. The behavior also changed a little. It used to suspend forever if the sensor wasn't found. Now it returns immediately.
p
Is that so? Why would it return immediately?
m
I'm wrong.
p
I’m wondering why the docs of that function don’t state more broadly that you can only resume once
n
Because the docs advise you to use an entirely different method for this use-case instead:
A typical use of this function is to suspend a coroutine while waiting for a result from a single-shot callback API and to return the result to the caller. For multi-shot callback APIs see callbackFlow.
The
callbackFlow
implementation is almost identical. If you just want the next shake, use
Flow.first()
.
1
p
Yeah that was my previous implementation
Maybe I should have just moved it to the internals of the function instead of it's api
l
I've been doing such things for a few years now, all my new sensor related code involves coroutines at some point, which integrates nicely with other controlling code.
👍 1