Kotlin is a language that is rapidly gaining popularity, among others thanks to cooperation with Java. On the other hand, RxJava has brought us many solutions to problems related to asynchronous code. If everything is so cool, do we need anything else in the Kotlin world? Is Kotlin Coroutine a competition for RxJava?
2. Agenda:
1. Pre-Kotlin world
2. What are coroutines and why we need it
3. Theoretical knowledge
4. Basic tutorial (some code)
5. Extra examples (more code)
6. Answer for the title question
7. Where to find more information
3. What have we had in pre-Kotlin world?
● AsyncTasks
● Threads
Painful points:
● Android Lifecycle
● Awful API
● Callbacks
● Easy to make a bug
● Hard to debug
● Concurrency is hard
4. What have we had in pre-Kotlin world? - RxJava!
http://reactivex.io/
6. What are Coroutines for a developer?
Coroutines allows us to write asynchronous code in
sequential way.
fun loadData() {
var data = api.getData()
showData(data)
}
sequential code
(not coroutine)
8. What problem does it solve? Callbacks!
https://www.twilio.com/blog/2017/03/promises-in-swift-writing-cleaner-asynchronous-code-using-promisekit.html
13. Coroutines - deep dive
● conceptually very light-weight threads
● one thread = ∞ coroutines
● compiled to state machine with shared state (and callbacks)
● 100K Threads = 💔, 100K Coroutines = 💚
● less context switch overhead
● less memory overhead
● works everywhere where Kotlin works
● stable since Kotlin 1.3
14. Suspend function
● suspend and executed later
● without blocking thread
● without changing context
● almost free
suspend fun doWorld() {
println("World!")
}
15. Coroutine context = Job + Dispatcher
Job (Rx ~ CompositeDisposable)
● a cancellable thing
● can’t be reused after cancel
● parent-child hierarchies
● SupervisorJob - child don’t cancel parent
Dispatchers (Rx ~ Schedulers)
● Default - thread pool = CPU cores
● Main - run on main thread (main UI thread on Android)
● IO - designed for IO tasks, share with Default
16. ● launch() - fire and forget
○ return Job object - allows to cancel coroutine
○ uncaught exceptions = crash
● async() - promise that it will return object
○ return Deferred<out T> : Job on which we call await() to wait for result
○ without await() call it will “swallow” exception
● runBlocking() - locks current thread until end of execution
● withContext() - run internal block on specified context
Coroutine builder
19. Coroutines - first
val job = GlobalScope.launch(Dispatchers.Main){
println("We are in coroutine")
}
20. Coroutines - cancel
val job = GlobalScope.launch(Dispatchers.Main){
println("We are in coroutine")
}
job.cancel()
------------------------------------------------------------------
RxJava disposable.dispose()
21. Coroutines - cancel
val job = launch (Dispatchers.Main){
while (true){
println("We are in coroutine")
}
}
job.cancel()
22. Coroutines - cancel
val job = launch (Dispatchers.Main){
while (isActive){
println("We are in coroutine")
}
}
job.cancel()
------------------------------------------------------------------
RxJava isDisposed()
23. CoroutineScope
public interface CoroutineScope {
public val coroutineContext: CoroutineContext
}
● every coroutine needs a scope (coroutine is extension method)
● scope is a lifecycle for a coroutine
● scopes creates “scope tree”
● you don’t want to use GlobalScope
24. CoroutineScope - Activty
class MyActivity : AppCompatActivity(), CoroutineScope {
lateinit var parentJob: SupervisorJob
override val coroutineContext: CoroutineContext = Dispatchers.Main + parentJob
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
parentJob = SupervisorJob()
}
override fun onDestroy() {
super.onDestroy()
parentJob.cancel()
}
}
25. CoroutineScope - Activty
class MyActivity : AppCompatActivity(), CoroutineScope {
override val coroutineContext: CoroutineContext = Dispatchers.Main + parentJob
fun buildCoroutineTree(){
launch { // MyActivity scope
async { // launch scope }
}
launch { // MyActivity scope }
}
override fun onDestroy() {
parentJob.cancel() // Cancel all coroutines in scope
}
}
26. CoroutineScope - ViewModel
class MyActivity : ViewModel(), CoroutineScope {
val parentJob = SupervisorJob()
override val coroutineContext: CoroutineContext = Dispatchers.Main + parentJob
override fun onCleared() {
super.onCleared()
parentJob.cancel()
}
fun doSomeStuff() {
launch{ // launch scope }
}
}
28. Coroutines - change context (thread)
launch (Dispatchers.Main){
println("Coroutine in main thread")
withContext(Dispatchers.IO){
println("Coroutine in background thread")
}
println("Coroutine in main thread")
}
------------------------------------------------------------------
RxJava
.observeOn()
.subscribeOn()
.unsubscribeOn()
29. Coroutines - sequentially
fun loadDataSequentially() {
launch(Dispatchers.Main) {
val response1 = withContext(Dispatchers.IO) { loadData1() } // 1
val response2 = withContext(Dispatchers.IO) { loadData2() } // 2
val result = response1 + response2 // 3
}
}
30. Coroutines - sequentially
fun loadDataSequentially() {
launch(Dispatchers.Main) {
val response1 = withContext(Dispatchers.IO) { loadData1() } // 1
val response2 = withContext(Dispatchers.IO) { loadData2() } // 2
val result = response1 + response2 // 3
}
}
31. Coroutines - asynchronous
launch(Dispatchers.Main) {
val response = async(Dispatchers.IO) { // Deferred<Response>
println("We are in async coroutine")
// doing stuff in background thread
loadData()
}
// doing stuff main thread
val value = response.await() // await for response
}
32. Coroutines - parallel
fun loadDataParallel() {
launch(Dispatchers.Main) {
val response1 = async(Dispatchers.IO) { loadData1() }
val response2 = async(Dispatchers.IO) { loadData2() }
// doing stuff main thread
val result = response1.await() + response1.await() //await for response
}
}
33. Coroutines - parallel
fun loadDataParallel() {
launch(Dispatchers.Main) {
val response1 = async(Dispatchers.IO) { loadData1() }
val response2 = async(Dispatchers.IO) { loadData2() }
// doing stuff main thread
val result = response1.await() + response1.await() //await for response
}
}
36. Operators?
Everything from Kotlin Collections (map, filter, etc.) and more:
fun loadDataWithTimeout() {
launch(Dispatchers.Main) {
val response = async(Dispatchers.IO) { loadData() }
val result = withTimeoutOrNull(2, TimeUnit.SECONDS) { response.await() }
}
37. Retry
suspend fun <T> retry(block: suspend (Int) -> T): T {
for (i in 1..5) { // try 5 times
try {
return withTimeout(500) { // with timeout
block(i)
}
} catch (e: TimeoutCancellationException) { /* retry */ }
}
return block(0) // last time just invoke without timeout
}
38. Retry
suspend fun <T> retry(block: suspend (Int) -> T): T {
for (i in 1..5) { // try 5 times
try {
return withTimeout(500) { // with timeout
block(i)
}
} catch (e: TimeoutCancellationException) { /* retry */ }
}
return block(0) // last time just invoke without timeout
}
39. Retry
suspend fun <T> retry(block: suspend (Int) -> T): T {
for (i in 1..5) { // try 5 times
try {
return withTimeout(500) { // with timeout
block(i)
}
} catch (e: TimeoutCancellationException) { /* retry */ }
}
return block(0) // last time just invoke without timeout
}
40. Retry
suspend fun <T> retry(block: suspend (Int) -> T): T {
for (i in 1..5) { // try 5 times
try {
return withTimeout(500) { // with timeout
block(i)
}
} catch (e: TimeoutCancellationException) { /* retry */ }
}
return block(0) // last time just invoke without timeout
}
41. Retry
suspend fun <T> retry(block: suspend (Int) -> T): T {
for (i in 1..5) { // try 5 times
try {
return withTimeout(500) { // with timeout
block(i)
}
} catch (e: TimeoutCancellationException) { /* retry */ }
}
return block(0) // last time just invoke without timeout
}
42. Retry
suspend fun <T> retry(block: suspend (Int) -> T): T {
for (i in 1..5) { // try 5 times
try {
return withTimeout(500) { // with timeout
block(i)
}
} catch (e: TimeoutCancellationException) { /* retry */ }
}
return block(0) // last time just invoke without timeout
}
43. Retrofit
interface RemoteDataSource{
@GET("api/news")
fun getNews() : Single<NewsResponse>
}
------------------------------------------------------------------
interface RemoteDataSource {
@GET("api/news")
fun getNews(): Deferred<NewsResponse>
}
------------------------------------------------------------------
interface RemoteDataSource {
@GET("api/news")
suspend fun getNews(): NewsResponse
}
RxJava
Adapter by
JakeWharton
Retrofit 2.5.1
44. Channels
● experimental
● hot observables in Rx world
● have buffer (default is 1)
● cold observables -
top issue
● cold observables ~
Sequences
val channel = Channel<Int>(1) // capacity 1
//Execution order
fun channelSend() = launch {
channel.send(1) //1
channel.send(1) //3
}
fun channelReceive() = launch {
val value1 = channel.receive() //2
val value2 = channel.receive() //4
}
45. Channels
● experimental
● hot observables in Rx world
● have buffer (default is 1)
● cold observables -
top issue
● cold observables ~
Sequences
val channel = Channel<Int>(1) // capacity 1
//Execution order
fun channelSend() = launch {
channel.send(1) //1
channel.send(1) //3
}
fun channelReceive() = launch {
val value1 = channel.receive() //2
val value2 = channel.receive() //4
}
46. Channels
● experimental
● hot observables in Rx world
● have buffer (default is 1)
● cold observables -
top issue
● cold observables ~
Sequences
val channel = Channel<Int>(1) // capacity 1
//Execution order
fun channelSend() = launch {
channel.send(1) //1
channel.send(1) //3
}
fun channelReceive() = launch {
val value1 = channel.receive() //2
val value2 = channel.receive() //4
}
47. Channels
● experimental
● hot observables in Rx world
● have buffer (default is 1)
● cold observables -
top issue
● cold observables ~
Sequences
val channel = Channel<Int>(1) // capacity 1
//Execution order
fun channelSend() = launch {
channel.send(1) //1
channel.send(1) //3
}
fun channelReceive() = launch {
val value1 = channel.receive() //2
val value2 = channel.receive() //4
}
48. Channels
● experimental
● hot observables in Rx world
● have buffer (default is 1)
● cold observables -
top issue
● cold observables ~
Sequences
val channel = Channel<Int>(1) // capacity 1
//Execution order
fun channelSend() = launch {
channel.send(1) //1
channel.send(1) //3
}
fun channelReceive() = launch {
val value1 = channel.receive() //2
val value2 = channel.receive() //4
}
49. Channels
● experimental
● hot observables in Rx world
● have buffer (default is 1)
● cold observables -
top issue
● cold observables ~
Sequences
val channel = Channel<Int>(1) // capacity 1
//Execution order
fun channelSend() = launch {
channel.send(1) //1
channel.send(1) //3
}
fun channelReceive() = launch {
val value1 = channel.receive() //2
val value2 = channel.receive() //4
}
50. Produce
val producer = produce{
send(1) //1
send(1) //3
}
fun receive() {
launch {
val value1 = producer.receive() //2
val value2 = producer.receive() //4
}
}
51. Actor
val subscriber = actor<Int> {
for(i in channel) {
//wait for elements in channel
}
}
fun send() {
launch {
subscriber.send(1)
subscriber.send(2)
}
}
52. Will it be the next step for RxJava
developer?
53. The next step for RxJava developer?
● Coroutines = low-level API for asynchronous calls
● Rx = observable pattern, "functional reactive programming"
● sequential vs streams, next tool in toolset
● Coroutines are faster and more memory efficient
● Perfectly replacement for Single, Completable, Maybe
● Easier to learn, lower entry threshold
● Can pass null values
● Channels can’t replace Observables
● Rx wins when dealing with real streams
● Coroutine wins with Kotlin/Native
● Rx API with Coroutines?
54. Should we switch to coroutines? IMHO
● You already have RxJava - stay with RxJava
● You are in love with RxJava - good
● You work with streams - only Rx
● You work with Java - Rx
● CRUD - Coroutines
● Simple app - Coroutines
● Don’t want Rx - only Coroutines
55. Where/What to learn more?
● https://github.com/Kotlin/kotlin-coroutines/ - core coroutine in language
● https://github.com/Kotlin/kotlinx.coroutines - base support library and other:
○ Dispatchers(“threads”) for Dispatchers.Main - Android, Spring, JavaFX
○ support for Reactive libraries - RxJava1, RxJava2, etc…
○ support for future-based libraries - JDK8, Guava, etc…
○ Kotlin/JS
○ Kotlin/Native
● KotlinConf talks about Coroutines by Roman Elizarov (2017, 2018)