Lazy evaluated Coroutines in Kotlin
Async Workflows with Kotlin Pt III
This is a further extension of my work on Kotlin Coroutines and Promises. If you haven’t seen them, or just aren’t as up to date as you’d like, have a squiz:
- Part I : Async/Await in Android and Java with Kotlin Coroutines
- Part II: Promise-Like Syntax for Android and Java with Kotlin Coroutines
- Part III: You’re here! :)
So in the past topics, we got async/await functioning, and then built a happy little Promise wrapper around it. Today we’ll show how to lazy-evaluate a piece of asynchronous work, so that we can set up a worker without starting the work during initialisation-time, and later invoke the work during runtime.
Let me give an example, where fetchData
is some call that returns either a Deferred<T>
or a Promise<T>
depending on whether you’re using my optional PromiseLike syntax from Part II of this series.
class MyActivity { val intentArg by lazy { intent.getExtra(...) }
val dataPromise by lazyDeferred { fetchData(intentArg) } ... fun onCreate() {
setContentView(R.layout.content_view) showLoadingSpinner() // Using async/await syntax from Pt I
myView.bind(dataPromise.await()) // Using Promise-syntax from Pt II
dataPromise.then { response -> myView.bind(response) }
}
}
The requirement here is that fetchData
not execute until it is invoked during onCreate
. This is important because it depends upon a lazy value in intentArg
which will not be present until after the Android system has populated it during its create
lifecycle stage.
lazyDeferred as a Delegated Property
To achieve the lazyDeferred
syntax demonstrated above, we’ll use Kotlins’s Delegated Properties. To keep it simple though, we’ll just extend from the existing lazy
Delegated Property that comes out-of-the-box. This will let us set up a unit of async work ahead of time, but not actually invoke it until we interact with its result later on.
So to do this, there are two things to do. One, is to ensure the Promise
only executes its work when it has to, by setting the Coroutine start property to lazy. The second, is to wrap the object in a lazy-initialiser itself.
fun <T> lazyPromise(block: suspend CoroutineScope.() -> T): Lazy<Deferred<T>> {
return lazy {
async(start = CoroutineStart.LAZY) {
block.invoke(this)
}
}
}
And that’s it. Now it’s easier than ever coordinate your asynchronous workloads without hassle, either with Promise-Like syntax or by writing synchronous-looking code.