To continue the analogy, await() can be a suspending function (hence also callable from within an async {} block) that suspends a coroutine until some computation is done and returns its result:
We are using the delay() function thatโs like Thread.sleep(), but better: it doesnโt block a thread, but only suspends the coroutine itself. The thread is returned to the pool while the coroutine is waiting, and when the waiting is done, the coroutine resumes on a free thread in the pool.
await() can not be called outside a coroutine, because it needs to suspend until the computation finishes, and only coroutines can suspend in a non-blocking way
What does suspend function mean in Kotlin Coroutine ๐ค
Basically, coroutines are computations that can be suspended without blocking a thread
I heard people often say โsuspend functionโ. But I think it is the coroutine who gets suspended because it is waiting for the function to finished? โsuspendโ usually means โcease operationโ, in this case the coroutine is idle.
To continue the analogy, await() can be a suspending function (hence also callable from within an async {} block) that suspends a coroutine until some computation is done and returns its result:
1 2 3 4 5 6
async { // Here I call it the outer async coroutine ... // Here I call computation the inner coroutine val result = computation.await() ... }
๐ค It says โthat suspends a coroutine until some computation is doneโ, but coroutine is like a lightweight thread. So if the coroutine is suspended, how can the computation is done ?
We see await is called on computation, so it might be async that returns Deferred, which means it can start another coroutine
๐ค The quote say that suspends a coroutine. Does it mean suspend the outer async coroutine, or suspend the inner computation coroutine?
Does suspend mean that while outer async coroutine is waiting (await) for the inner computation coroutine to finish, it (the outer async coroutine) idles (hence the name suspend) and returns thread to the thread pool, and when the child computation coroutine finishes, it (the outer async coroutine) wakes up, takes another thread from the pool and continues?
Conceptually, async is just like launch. It starts a separate coroutine which is a light-weight thread that works concurrently with all the other coroutines. The difference is that launch returns a Job and does not carry any resulting value, while async returns a Deferred โ a light-weight non-blocking future that represents a promise to provide a result later. You can use .await() on a deferred value to get its eventual result, but Deferred is also a Job, so you can cancel it if needed.
1 2 3 4 5 6 7 8
funmain(args: Array<String>) = runBlocking<Unit> { val time = measureTimeMillis { val one = async { doSomethingUsefulOne() } val two = async { doSomethingUsefulTwo() } println("The answer is ${one.await() + two.await()}") } println("Completed in $time ms") }
There is a laziness option to async using an optional start parameter with a value of CoroutineStart.LAZY. It starts coroutine only when its result is needed by some await or if a start function is invoked. Run the following example that differs from the previous one only by this option:
1 2 3 4 5 6 7 8
funmain(args: Array<String>) = runBlocking<Unit> { val time = measureTimeMillis { val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() } val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() } println("The answer is ${one.await() + two.await()}") } println("Completed in $time ms") }
What is the difference between launch/join and async/await in Kotlin coroutines
So you can think of coroutine as something that manages thread in a very efficient way.
๐ค launch
1 2 3 4 5 6 7 8
funmain(args: Array<String>) { launch { // launch new coroutine in background and continue delay(1000L) // non-blocking delay for 1 second (default time unit is ms) println("World!") // print after delay } println("Hello,") // main thread continues while coroutine is delayed Thread.sleep(2000L) // block main thread for 2 seconds to keep JVM alive }
So launch starts a background thread, does something, and returns a token immediately as Job. You can call join on this Job to block until this launch thread completes
1 2 3 4 5 6 7 8
funmain(args: Array<String>) = runBlocking<Unit> { val job = launch { // launch new coroutine and keep a reference to its Job delay(1000L) println("World!") } println("Hello,") job.join() // wait until child coroutine completes }
๐ฆ async
Conceptually, async is just like launch. It starts a separate coroutine which is a light-weight thread that works concurrently with all the other coroutines. The difference is that launch returns a Job and does not carry any resulting value, while async returns a Deferred โ a light-weight non-blocking future that represents a promise to provide a result later.
So async starts a background thread, does something, and returns a token immediately as Deferred.
1 2 3 4 5 6 7 8
funmain(args: Array<String>) = runBlocking<Unit> { val time = measureTimeMillis { val one = async { doSomethingUsefulOne() } val two = async { doSomethingUsefulTwo() } println("The answer is ${one.await() + two.await()}") } println("Completed in $time ms") }
You can use .await() on a deferred value to get its eventual result, but Deferred is also a Job, so you can cancel it if needed.
There is a laziness option to async using an optional start parameter with a value of CoroutineStart.LAZY. It starts coroutine only when its result is needed by some await or if a start function is invoked.
Understand Kotlin Coroutines on Android (Google I/Oโ19)
People advise against storing keys inside build.gradles. We should store them on 1Password and populate our gradle.properties, so donโt track this file in git. Here is .gitignore file
1 2 3 4 5 6 7 8
*.iml
/build /gradle.properties /local.properties
.gradle .idea
There are several ways to help BuddyBuild know about our gradle.properties
1. Using Environment variables
But when configuring the project on BuddyBuild, it complains about key not found. The solution is to use Environment variables
This is because gradle does not know about environment variables. The System.getenv("MY_KEY") is for BuddyBuild, and the default MY_KEY is for gradle.properties.
Next is to remove this duplication. We can use Groovy Binding. build.gradle does the import import groovy.lang.Binding automatically for us
1 2 3 4 5 6 7 8
String environmentKey(variable) { for (Object var : binding.variables) { if (var.value == variable) { return System.getenv(var.key) ?: variable } } return"" }
BuddyBuild allows us to define Secured File, here we can upload our gradle.properties
And we can use Prebuild script to copy this secured file to our project. BuddyBuild suggests using buddybuild_prebuild.sh but then build fails in Build file '/tmp/sandbox/workspace/app/build.gradle'
So, create a script called buddybuild_postclone.sh
Thereโs always need for communication, right ๐ Suppose we have OnboardingActivity that has several OnboardingFragment. Each Fragment has a startButton telling that the onboarding flow has finished, and only the last Fragment shows this button.
Here are several ways you can do that
1. EventBus ๐
Nearly all articles I found propose this https://github.com/greenrobot/EventBus, but I personally donโt like this idea because components are loosely coupled, every component and broadcast can listen to event from a singleton, which makes it very hard to reason when the project scales
// Listen should return an Observable and not the publisher // Using ofType we filter only events that match that class type fun <T> listen(eventType: Class<T>): Observable<T> = publisher.ofType(eventType) }
This is advised here Communicating with Other Fragments. Basically you define an interface OnboardingFragmentDelegate that whoever conforms to that, can be informed by the Fragment of events. This is similar to Delegate pattern in iOS ๐
We can learn from Share data between fragments to to communication between Fragment and Activity, by using a shared ViewModel that is scoped to the activity. This is a bit overkill
1 2 3
classOnboardingSharedViewModel: ViewModel() { val finish = MutableLiveData<Unit>() }