Coroutines
fun main() {
GlobalScope.launch {
delay(1000L)
println("World!")
}
println("Hello,")
Thread.sleep(2000L)
}
• Launch new coroutine in background and continue
• Returns a job
Slide 21
Slide 21 text
Coroutines
fun main() {
GlobalScope.launch {
delay(1000L)
println("World!")
}
println("Hello,")
Thread.sleep(2000L)
}
CoroutineScope with application lifetime
Structured Concurrency
suspend fun downloadAPKs(packageName: List) = coroutineScope {
packageName.forEach {
val url = findUrl(it)
launch {
download(url)
//...
}
}
}
Crash
Cancels
Waits for all previous “launch” to complete
No Job leak anymore
Slide 39
Slide 39 text
Structured Concurrency
fun main() = runBlocking {
launch {
delay(200L)
println("Task from runBlocking")
}
coroutineScope {
launch {
delay(500L)
println("Task from nested launch")
}
delay(100L)
println("Task from coroutine scope")
}
println("Coroutine scope is over")
}
Task from coroutine scope
Task from runBlocking
Task from nested launch
Coroutine scope is over
Slide 40
Slide 40 text
Structured Concurrency
fun main() = runBlocking {
launch {
delay(200L)
println("Task from runBlocking")
}
coroutineScope {
launch {
delay(500L)
println("Task from nested launch")
}
delay(100L)
println("Task from coroutine scope")
}
println("Coroutine scope is over")
}
launch new coroutine in the scope of runBlocking
Slide 41
Slide 41 text
Structured Concurrency
fun main() = runBlocking {
launch {
delay(200L)
println("Task from runBlocking")
}
coroutineScope {
launch {
delay(500L)
println("Task from nested launch")
}
delay(100L)
println("Task from coroutine scope")
}
println("Coroutine scope is over")
}
- Creates a new coroutine scope
- Does not block the current thread while
waiting for all children to complete.
Slide 42
Slide 42 text
Structured Concurrency
fun main() = runBlocking {
launch {
delay(200L)
println("Task from runBlocking")
}
coroutineScope {
launch {
delay(500L)
println("Task from nested launch")
}
delay(100L)
println("Task from coroutine scope")
}
println("Coroutine scope is over")
}
Waits nested launch completes
Slide 43
Slide 43 text
Structured Concurrency
fun main() = runBlocking {
launch {
delay(200L)
println("Task from runBlocking")
}
coroutineScope {
launch {
delay(500L)
println("Task from nested launch")
}
delay(100L)
println("Task from coroutine scope")
}
println("Coroutine scope is over")
}
Slide 44
Slide 44 text
Structured Concurrency
fun main() = runBlocking {
launch {
delay(200L)
println("Task from runBlocking")
}
coroutineScope {
launch {
delay(500L)
println("Task from nested launch")
}
delay(100L)
println("Task from coroutine scope")
}
println("Coroutine scope is over")
}
suspend fun doWork() {
}
Slide 45
Slide 45 text
Structured Concurrency
fun main() = runBlocking {
launch {
delay(200L)
println("Task from runBlocking")
}
doWork()
println("Coroutine scope is over")
}
suspend fun doWork() = coroutineScope {
launch {
delay(500L)
println("Task from nested launch")
}
delay(100L)
println("Task from coroutine scope")
}
Slide 46
Slide 46 text
Cancellation
Slide 47
Slide 47 text
Cancellation
fun main(args: Array) = runBlocking {
val job = launch {
doWork()
}
delay(1000)
job.cancelAndJoin()
}
private suspend fun doWork() {
try {
delay(2000L)
} catch (e: Exception) {
println(e)
}
}
kotlinx.coroutines.JobCancellationException: Job was cancelled;
job=StandaloneCoroutine{Cancelling}
Slide 48
Slide 48 text
Dispatchers
• Like RxJava’s Schedulers
• IO
• Main (Android, JavaFX, Swing)
• Default
• Unconfined
Slide 49
Slide 49 text
Async/Await
Slide 50
Slide 50 text
Async/Await
suspend fun calculateOne(): Int {
delay(1000L) return 11
}
suspend fun calculateTwo(): Int {
delay(1000L) return 22
}
Concurrent Execution !?
Slide 51
Slide 51 text
Async/Await
fun main() = runBlocking {
val time = measureTimeMillis {
val one = async { calculateOne() }
val two = async { calculateTwo() }
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
}
~ 1 sec
Like launch but return Deferred
Slide 52
Slide 52 text
Async/Await
fun main() = runBlocking {
val time = measureTimeMillis {
val one = async(start = CoroutineStart.LAZY) { calculateOne() }
val two = async(start = CoroutineStart.LAZY) { calculateTwo() }
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
} ~ 2 sec
Slide 53
Slide 53 text
Async/Await
fun main() = runBlocking {
val time = measureTimeMillis {
val one = async(start = CoroutineStart.LAZY) { calculateOne() }
val two = async(start = CoroutineStart.LAZY) { calculateTwo() }
one.start()
two.start()
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
}
~ 1 sec
Slide 54
Slide 54 text
Experimental Features
• Channels
• Select Expression
Slide 55
Slide 55 text
Coroutines + Android
Slide 56
Slide 56 text
Back to our App
Slide 57
Slide 57 text
Domain
interface TVShowRepository {
fun getAll(): List
}
Slide 58
Slide 58 text
Domain Layer
interface TVShowRepository {
suspend fun getAll(): List
}
Slide 59
Slide 59 text
Domain Layer
interface TVShowRepository {
suspend fun getAll(): List
}
What about exceptions?
- Try/Catch
- Either
Slide 60
Slide 60 text
Either
https://arrow-kt.io/docs/datatypes/either/
sealed class Either : EitherOf
Domain Layer
interface TVShowRepository {
suspend fun getAll(): List
}
Slide 64
Slide 64 text
Domain Layer
interface TVShowRepository {
suspend fun getAll(): Either>
}
sealed class Error(val msg: String) {
class NetworkConnection(msg: String) : Error(msg)
//..
sealed class Feature(msg: String) : Error(msg) {
class UnreachableTVShow : Feature("UnReachable")
//..
}
}
Slide 65
Slide 65 text
Domain Layer
when (error) {
is NetworkConnection -> { /*..*/}
is UnreachableTVShow -> { /*..*/}
else -> { /*..*/}
}
Looks better?
Slide 66
Slide 66 text
Data Layer
override suspend fun getAll(api: api) {
try {
val users = api.getAll().await()
return Either.right(users)
} catch (e: Exception) {
return Either.left(mapToError(e))
}
}
https://github.com/gildor/kotlin-coroutines-retrofit
Slide 67
Slide 67 text
What happened?
https://github.com/gildor/kotlin-coroutines-retrofit
• All non-UI layers are now implemented with suspend
functions
• Sequential programming
• More readable code
Slide 68
Slide 68 text
But…
• Structured concurrency on UI layer
Slide 69
Slide 69 text
ViewModel
abstract class BaseViewModel : ViewModel()
Slide 70
Slide 70 text
ViewModel
abstract class BaseViewModel : ViewModel(), CoroutineScope
Slide 71
Slide 71 text
ViewModel
abstract class BaseViewModel : ViewModel(), CoroutineScope {
protected val job: Job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
override fun onCleared() {
super.onCleared()
job.cancel()
}
}
Slide 72
Slide 72 text
ViewModel
abstract class BaseViewModel : ViewModel(), CoroutineScope {
protected val job: Job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
override fun onCleared() {
super.onCleared()
job.cancel()
}
}
Plus operator
Launch on main thread
Job will reference to all launches
Slide 73
Slide 73 text
ViewModel
class TVShowViewModel(val repository: TVShowRepository) : BaseViewModel() {
fun load() = launch {
val result = async(Dispatchers.IO) { repository.getAll() }.await()
//do whatever you want with result
}
}
Slide 74
Slide 74 text
Clean Architecture
in Action
twitter.com/s_masoumi
github.com/SaeedMasoumi