I can't use "by lazy" because the callbacks require suspendCoroutine
, which borks in android if it blocks the main thread, so I have to use the following "cache the result" pattern over and over. Is there a way to wrap it in a funButUseCachedResultsIfTheyAlreadyExist
pattern to encapsulate the xCached object?
private var cameraDeviceCached: CameraDevice? = null
private suspend fun cameraDevice(): CameraDevice {
cameraDeviceCached?.also { return it }
return suspendCoroutine { cont: Continuation<CameraDevice> ->
... deep callbacks with cont.resume(camera) ...
}.also {
cameraDeviceCached = it
}
}
When what I'd really like to write is
private suspend fun cameraDevice(): CameraDevice = theMagicFunction { cont ->
... deep callbacks with cont.resume(camera) ...
}
You can build a generalized solution by wrapping an async
call as follows:
import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineStart.LAZY
class LazySuspendFun<out T>(
scope: CoroutineScope,
private val block: suspend () -> T
) {
private val deferred = scope.async(Dispatchers.Unconfined, LAZY) { block() }
suspend operator fun invoke() = deferred.await()
}
fun <T> CoroutineScope.lazySuspendFun(block: suspend () -> T) =
LazySuspendFun(this, block)
This is a simple example of how you can use it. Note that we are able to compose them so that we use a lazy-inited value as a dependency to getting another one:
val fetchToken = lazySuspendFun<String> {
suspendCoroutine { continuation ->
Thread {
info { "Fetching token" }
sleep(3000)
info { "Got token" }
continuation.resume("hodda_")
}.start()
}
}
val fetchPosts = lazySuspendFun<List<String>> {
val token = fetchToken()
suspendCoroutine { continuation ->
Thread {
info { "Fetching posts" }
sleep(3000)
info { "Got posts" }
continuation.resume(listOf("${token}post1", "${token}post2"))
}
}
}
On the calling side you must be inside some coroutine context so you can call the suspending functions:
myScope.launch {
val posts = fetchPosts()
...
}
This solution is robust enough that you can concurrently request the value several times and the initializer will run only once.
I'll write this as an answer, since it's not possible to post much code in comments.
What you're looking for is something like this:
private suspend fun cameraDevice() = theMagicFunction {
CameraDevice()
}()
suspend fun theMagicFunction(block: ()->CameraDevice): () -> CameraDevice {
var cameraDeviceCached: CameraDevice? = null
return fun(): CameraDevice {
cameraDeviceCached?.also { return it }
return suspendCoroutine { cont: Continuation<CameraDevice> ->
cont.resume(block())
}.also {
cameraDeviceCached = it
}
}
}
Unfortunately, this will not compile, since closures cannot be suspendable, and neither are local functions.
Best I can suggest, unless I miss a solution there, is to encapsulate this in a class, if this variable bothers you too much.