https://kotlinlang.org logo
#coroutines
Title
# coroutines
p

Paul Woitaschek

09/14/2019, 7:16 AM
Is it somehow possible to await a continuation?
a

Allan Wang

09/14/2019, 7:18 AM
You can convert it to a deferred. But what’s your use case? Suspended functions operate sequentially unless otherwise specified
o

octylFractal

09/14/2019, 7:18 AM
You could
async { cont.resume() }
I suppose, but it doesn't return a value, so you probably want to use
launch
&
join
. Seems like an XY problem though.
p

Paul Woitaschek

09/14/2019, 7:19 AM
How can I convert it to a deferred @Allan Wang?
I use coroutines to request android permissions and store the continuations in a map for each permission request code. Now I found a bug resulting in the fact that you can't request 2 permissions at the same time, so when someone requests a permission, I need to await for the old continuations to complete first.
o

octylFractal

09/14/2019, 7:24 AM
I think you might have terms mixed up here -- a continuation doesn't have a strict sense of completion, that's all tied to
Job
, which you can already
join
on. A better model for your case might be to use an
actor
for each code, so that all permission requests are automatically processed sequentially. Then you don't have to wait for anything.
p

Paul Woitaschek

09/14/2019, 7:26 AM
Why would that be better?
suspendCancellableCoroutine
gives me a continuation and that's exactly what I need
e

elizarov

09/14/2019, 7:33 AM
@Paul Woitaschek For your use-case a
CompletableDeferred
whould be a much better fit. Use and store them instead of continuations, and you'll have no problem waiting for the same one multiple times.
p

Paul Woitaschek

09/14/2019, 7:35 AM
@elizarov How would that look like? In the end the signature would need to look like this:
suspend fun request(permission: String): PermissionResult
So I need to store what kind of permission I was requesting, right?
e

elizarov

09/14/2019, 7:37 AM
Something like this:
Copy code
suspend fun request(permission: String) = 
    mapPermissionToDeferred.getOrPut(permsision) { 
        requestPermission(permission)
    }.await()
Or even better:
Copy code
uspend fun request(permission: String) = 
    mapPermissionToDeferred.getOrPut(permsision) { 
        appScope.async { requestPermission(permission) }
    }.await()
where:
Copy code
mapPermissionToDeferred: MutableMap<String, Deferred<PermissionResult>>
suspend fun requestPermission: (permission: String): PermissionResult
(no need to explicitly use
CompletableDeferred
with the last approach -- just use
async
with a proper application-level scope)
p

Paul Woitaschek

09/14/2019, 7:41 AM
And how does that solve the problem of concurrent requests?
o

octylFractal

09/14/2019, 7:41 AM
hmm, looking into the android permission request model, I think
CompletableDeferred
is on the right track. Basically, since (If I'm interpreting your words correctly) you can only call
requestPermissions
one at a time, you can store the
requestCode
& a
CompletableDeferred
, then in your
suspend fun request
, if it's non-null,
await
it first. Then set the request code, a new
CompletableDeferred
, and call
requestPermission
. Then await your new deferred. You might need to use a
Mutex
here to ensure it's concurrently-safe, or box the requests in a data object and use
actor
to automatically sequential-ize* it. In
onRequestPermissionsResult
, you can check the code against the field, then complete the deferred. No Map needed.
e

elizarov

09/14/2019, 7:43 AM
Assyming that you only call
suspend fun request
from your main thread (you can wrap its body into
withContext(Dispatchers.Main) { ... }
to be sure, you'll never get two concurrent requests to the same permission, since the first one will get stored in the map via
getOrPut
p

Paul Woitaschek

09/14/2019, 7:43 AM
But I don't want to store the permission result permanently because the result might change
o

octylFractal

09/14/2019, 7:43 AM
there's also that option
you can always
null
out the field before setting the result. that's perfectly fine GC-wise
p

Paul Woitaschek

09/14/2019, 7:44 AM
to be sure, you'll never get two concurrent requests to the same permission
No, I must be sure to never get two concurrent requests ever, unregarding of the permission type
e

elizarov

09/14/2019, 7:45 AM
@Paul Woitaschek In this case it would be prudent to create a global actor that makes request. It would check incoming requsts, complete, then, then remove deferred from the map, go to the next request:
p

Paul Woitaschek

09/14/2019, 7:47 AM
And what is the benefit with the
CompletableDeferred
?
This already looks pretty straight forward to me
e

elizarov

09/14/2019, 7:48 AM
Copy code
suspend fun requestPermission: (permission: String, result: CompletableDeferred<PermissionResult>): PermissionResult = permission.send(permission to PermissionResult)
where
Copy code
permissionActor = actor<Pair<String, PermissionResult>> {
    for ((permission, result) in channel) {
        val permission = getIt(...)
        result.complete(permission)
        mapPermissionToDeferred.remove(permission)
        // ^^ this way future requests will request it again
    }
}
If it already works for you, then you can just change
CancellableContinuation
with
CompletableDeferred
and instead of
resume
call
complete
.
Instead of
suspendCancellableCoroutine<Unit> { cont -> ... }
you can do:
Copy code
val d = CompletableDeferred<Unit> /// why unit? 
...
d.await()
I don't see how you make sure there are no concurrent requests in your code, though.
o

octylFractal

09/14/2019, 7:56 AM
My idea translates roughly like this: https://gist.github.com/kenzierocks/bc80dead52b71448477ea2ff21cf0ff4 you may not need the mutex depending on the threading model
and of course, you can set
requestPermissionsDeferred = null
in
onRequestPermissionsResult
, if you wish to clean up
p

Paul Woitaschek

09/14/2019, 8:04 AM
And calling
actor.send
waits till the actor is empty? How do I get a result from it?
o

octylFractal

09/14/2019, 8:05 AM
send a
CompletableDeferred
. have the actor complete it
l

louiscad

09/14/2019, 1:22 PM
@Paul Woitaschek Did you try Splitties permissions? It should handle the situation where you request multiple permissions concurrently by spawning another Activity. Here's the link: https://github.com/LouisCAD/Splitties/tree/master/modules/permissions
2 Views