The slides from my presentation from Droidcon Transylvania 2018
Abstract:
RxJava was the hype in the last couple of years, when it came to asynchronous operations in an Android app. I felt it, I adopted it, as I’m sure a lot of people did.
But with the advent of Kotlin, is RxJava still the most robust and efficient solution, or should we, as Android developers feel safe enough to rely on Coroutines?
In this talk, I will take attendees on a deep dive through the magical world of Coroutines, whilst trying to offer a comparison to everyone’s favorite reactive programming library.
18. Async tasks
• Labeled as “Painless Threading”
• Async Tasks don’t work well with orientations changes or might have
problems with the Lifecycle
• Limited to 138 concurrent tasks
19. Loaders
• Never really caught on
• Trying to solve the problem of running asynchronous operations within the
Activity or Fragments
24. Reactive Extensions
• simplifies the way you do async/threaded work
• simple to compose streams of data
• avoid callback hell
• a downside is that you shift from the normal programming model
26. What are coroutines?
• basically lightweight threads
• provide the same way of writing code both for async and sync
• exceptions handling, loops, etc. work like in synchronous code
27. What are coroutines?
suspend fun updateAccount(updatedUser: User) {
val token = refreshToken()
postUpdate(token, updatedUser)
}
28. How do coroutines work?
suspend fun refreshToken(): Token {
...
}
Object refreshToken(Continuation<Token> cont) {
...
}
Callback
interface Continuation<in T> {
val context: CoroutineContext
fun resume(value: T)
fun resumeWithException(exception: Throwable)
}
29. Is that really it?
suspend fun updateAccount(updatedUser: User) {
0 val token = refreshToken()
1 postUpdate(token, updatedUser)
2 }
class StateMachine(val updatedUser: User,
val updateAccountCallback: Continuation<Unit>)
: Continuation<Any?> {
var label: Int = 0
var token: String? = null
}
30. Deeper
suspend fun updateAccount(updatedUser: User) {
0 val token = refreshToken()
1 postUpdate(token, updatedUser)
2 }
when (sm.label) {
0 -> { sm.label = 1; refreshToken(sm) }
1 -> { sm.label = 2;
sm.token = value as String;
postUpdate(sm.token!!, updatedUser, sm)
}
2 -> { sm.updateAccountCallback.resume(Unit) }
}
Label is set to 0
31. Deeper
suspend fun updateAccount(updatedUser: User) {
0 val token = refreshToken()
1 postUpdate(token, updatedUser)
2 }
when (sm.label) {
0 -> { sm.label = 1; refreshToken(sm) }
1 -> { sm.label = 2;
sm.token = value as String;
postUpdate(sm.token!!, updatedUser, sm)
}
2 -> { sm.updateAccountCallback.resume(Unit) }
}
Label is set to 1
32. Deeper
suspend fun updateAccount(updatedUser: User) {
0 val token = refreshToken()
1 postUpdate(token, updatedUser)
2 }
when (sm.label) {
0 -> { sm.label = 1; refreshToken(sm) }
1 -> { sm.label = 2;
sm.token = value as String;
postUpdate(sm.token!!, updatedUser, sm)
}
2 -> { sm.updateAccountCallback.resume(Unit) }
}
Label is set to 2
33. Coroutine builders
suspend fun updateAccount(updatedUser: User) {
val token = refreshToken()
postUpdate(token, updatedUser)
}
38. Can they play well with others?
• Short answer: YES
• Long answer: with most or all libraries that use some sort of Future or
expose some kind of callback
• basically you need to create and/or apply a await extension function
42. How?
suspend fun <T> Call<T>.await(): T {
enqueue(object : Callback<T> {
override fun onFailure(call: Call<T>, t: Throwable) {
TODO("not implemented")
}
override fun onResponse(call: Call<T>, response: Response<T>) {
TODO("not implemented")
}
})
}
43. How?
public suspend inline fun <T> suspendCoroutine(crossinline block: (Continuation<T>) -> Unit): T =
suspendCoroutineOrReturn { c: Continuation<T> ->
val safe = SafeContinuation(c)
block(safe)
safe.getResult()
}
44. How?
suspend fun <T> Call<T>.await(): T = suspendCoroutine { continuation ->
enqueue(object : Callback<T> {
override fun onFailure(call: Call<T>, t: Throwable) {
continuation.resumeWithException(t)
}
override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.isSuccessful) {
continuation.resume(response.body()!!)
} else {
continuation.resumeWithException(Error(response.errorBody()!!))
}
}
})
}
45. How? - Sidenote
• JetBrains provides some out of the box integrations
46. How does it stack up against RxJava?
• Does it replace Rx?
• Is it better than Rx?
47. Why shouldn’t you use Rx?
• fluent API, but it leads to hard to read code and changing it might be
complex
• creating modular code that can be composed and still be readable is a
pain
• it’s overkill, most of the times
48. Why shouldn’t you use coroutines?
• if you’ve already learned Rx and the codebase is heavily relying on it
• sometimes composing and pub/sub-ing is easier with Rx
• if you’re codebase is written in Java
• community of developers using Rx is a tad larger (more questions on SO)
52. Epilogue
• Direct Style and Continuation Passing Style ✅
• Deep dive into the inner workings of coroutines ✅
• Integration with other libraries ✅
• Short comparison to RxJava ✅