SlideShare a Scribd company logo
1 of 261
Download to read offline
C O R O U T I N E S A N D R X J AVA
A N A S Y N C H R O N I C I T Y C O M PA R I S O N
M A N U E L V I C E N T E V I V O @ M A N U E LV I C N T
@manuelvicnt #DroidconBos
A G E N D A
• Coroutines Recap
• RxJava & Coroutines concepts
• Build an App
@manuelvicnt #DroidconBos
G O A L S
• Learn Coroutines with basic RxJava knowledge
• Compare both libraries on different topics
• Use what we learned to build the App
• Code available
@manuelvicnt #DroidconBos
T H E A P P
@manuelvicnt #DroidconBos
I N T E N D E D A U D I E N C E
• Able to read Kotlin code
• Basic RxJava experience
• Coroutines 101
• You want to learn differences and similarities between
RxJava and Coroutines
@manuelvicnt #DroidconBos
I H E A R D C O R O U T I N E S ?
• From the Kotlin documentation…
• Coroutines simplify asynchronous programming
• Code can be expressed sequentially and the library
handles the asynchronous code for us
• Computations can be suspended without blocking a
Thread
@manuelvicnt #DroidconBos
I H E A R D C O R O U T I N E S ?
• From the Kotlin documentation…
• Coroutines simplify asynchronous programming
• Code can be expressed sequentially and the library
handles the asynchronous code for us
• Computations can be suspended without blocking a
Thread
@manuelvicnt #DroidconBos
I H E A R D C O R O U T I N E S ?
• From the Kotlin documentation…
• Coroutines simplify asynchronous programming
• Code can be expressed sequentially and the library
handles the asynchronous code for us
• Computations can be suspended without blocking a
Thread
I WA N T T O
P L AY A G A M E
@manuelvicnt #DroidconBos
What is this?
launch(CommonPool) {
heavyComputation()
}
@manuelvicnt #DroidconBos
What is this?
launch(CommonPool) {
heavyComputation()
}
Coroutine
@manuelvicnt #DroidconBos
What is this?
launch(CommonPool) {
heavyComputation()
}
@manuelvicnt #DroidconBos
What is this?
launch(CommonPool) {
heavyComputation()
}
Coroutine Builder
@manuelvicnt #DroidconBos
What is this?
launch(CommonPool) {
heavyComputation()
}
@manuelvicnt #DroidconBos
What is this?
launch(CommonPool) {
heavyComputation()
}
Coroutine Context
@manuelvicnt #DroidconBos
What is this?
launch(CommonPool) {
heavyComputation()
}
@manuelvicnt #DroidconBos
What is this?
launch(CommonPool) {
heavyComputation()
}
Suspending Lambda
@manuelvicnt #DroidconBos
What is this?
launch(CommonPool) {
heavyComputation()
val data =
makeNetworkRequest()
updateDB(data)
}
Suspending Lambda
@manuelvicnt #DroidconBos
What is this?
launch(CommonPool) {
heavyComputation()
val data =
makeNetworkRequest()
updateDB(data)
}
Suspending Lambda
@manuelvicnt #DroidconBos
What is this?
launch(CommonPool) {
heavyComputation()
val data =
makeNetworkRequest()
updateDB(data)
}
Suspending Lambda
@manuelvicnt #DroidconBos
What is this?
launch(CommonPool) {
heavyComputation()
val data =
makeNetworkRequest()
updateDB(data)
}
Suspending Lambda
@manuelvicnt #DroidconBos
What is this?
launch(CommonPool) {
heavyComputation()
val data =
makeNetworkRequest()
updateDB(data)
}
Suspending Lambda
@manuelvicnt #DroidconBos
What is this?
launch(CommonPool) {
heavyComputation()
val data =
makeNetworkRequest()
updateDB(data)
}
Suspending Lambda
@manuelvicnt #DroidconBos
What is this?
launch(CommonPool) {
heavyComputation()
val data =
makeNetworkRequest()
updateDB(data)
}
Suspending Lambda
@manuelvicnt #DroidconBos
What is this?
launch(CommonPool) {
heavyComputation()
val data =
makeNetworkRequest()
updateDB(data)
}
Suspending Lambda
C O R O U T I N E S R E C A P
C O M P L E T E D
C O R O U T I N E S - R X J AVA
C O M PA R I S O N
C A N C E L E X E C U T I O N
@manuelvicnt #DroidconBos
With Disposables
How to cancel an Observable?
@manuelvicnt #DroidconBos
With Disposables
How to cancel an Observable?
val disposable: Disposable =
Observable.interval(1, TimeUnit.SECONDS)
.subscribe()
disposable.dispose()
@manuelvicnt #DroidconBos
With Disposables
How to cancel an Observable?
val disposable: Disposable =
Observable.interval(1, TimeUnit.SECONDS)
.subscribe()
disposable.dispose()
@manuelvicnt #DroidconBos
With the Coroutine Job (from the Coroutine Context)
How to cancel a Coroutine?
@manuelvicnt #DroidconBos
With the Coroutine Job (from the Coroutine Context)
How to cancel a Coroutine?
val job = launch(CommonPool) {
// my suspending block
}
job.cancel()
@manuelvicnt #DroidconBos
With the Coroutine Job (from the Coroutine Context)
How to cancel a Coroutine?
val parentJob = Job()
launch(parentJob + CommonPool) {
// my suspending block
}
parentJob.cancel()
@manuelvicnt #DroidconBos
With the Coroutine Job (from the Coroutine Context)
How to cancel a Coroutine?
val parentJob = Job()
launch(CommonPool, parent = parentJob) {
// my suspending block
}
parentJob.cancel()
C H A N N E L S
@manuelvicnt #DroidconBos
C H A N N E L S
• Transfer stream of values
• Similar to Reactive Streams Publisher or RxJava
Observable/Flowable
@manuelvicnt #DroidconBos
C H A N N E L S
• Transfer stream of values
• Similar to Reactive Streams Publisher or RxJava
Observable/Flowable
Observable.create<Int> { emitter ->
for (i in 1..5) {
emitter.onNext(i)
}
emitter.onComplete()
}
@manuelvicnt #DroidconBos
Observable.create<Int> { emitter ->
for (i in 1..5) {
emitter.onNext(i)
}
emitter.onComplete()
}
C H A N N E L S
@manuelvicnt #DroidconBos
Observable.create<Int> { emitter ->
for (i in 1..5) {
emitter.onNext(i)
}
emitter.onComplete()
}
C H A N N E L S
@manuelvicnt #DroidconBos
Observable.create<Int> { emitter ->
for (i in 1..5) {
emitter.onNext(i)
}
emitter.onComplete()
}
C H A N N E L S
@manuelvicnt #DroidconBos
Observable.create<Int> { emitter ->
for (i in 1..5) {
emitter.onNext(i)
}
emitter.onComplete()
}.subscribe()
C H A N N E L S
@manuelvicnt #DroidconBos
C H A N N E L S
Observable.create<Int> { emitter ->
for (i in 1..5) {
emitter.onNext(i)
}
emitter.onComplete()
}.subscribe()
@manuelvicnt #DroidconBos
C H A N N E L S
Observer Timeline
Observable.create<Int> { emitter ->
for (i in 1..5) {
emitter.onNext(i)
}
emitter.onComplete()
}.subscribe()
@manuelvicnt #DroidconBos
C H A N N E L S
Observer Timeline
1
Observable.create<Int> { emitter ->
for (i in 1..5) {
emitter.onNext(i)
}
emitter.onComplete()
}.subscribe()
@manuelvicnt #DroidconBos
C H A N N E L S
Observer Timeline
1 2
Observable.create<Int> { emitter ->
for (i in 1..5) {
emitter.onNext(i)
}
emitter.onComplete()
}.subscribe()
@manuelvicnt #DroidconBos
C H A N N E L S
Observer Timeline
1 2 3
Observable.create<Int> { emitter ->
for (i in 1..5) {
emitter.onNext(i)
}
emitter.onComplete()
}.subscribe()
@manuelvicnt #DroidconBos
C H A N N E L S
Observer Timeline
1 2 3 4
Observable.create<Int> { emitter ->
for (i in 1..5) {
emitter.onNext(i)
}
emitter.onComplete()
}.subscribe()
@manuelvicnt #DroidconBos
C H A N N E L S
Observer Timeline
1 2 3 4 5
Observable.create<Int> { emitter ->
for (i in 1..5) {
emitter.onNext(i)
}
emitter.onComplete()
}.subscribe()
@manuelvicnt #DroidconBos
C H A N N E L S
Observer Timeline
1 2 3 4 5
Observable.create<Int> { emitter ->
for (i in 1..5) {
emitter.onNext(i)
}
emitter.onComplete()
}.subscribe()
@manuelvicnt #DroidconBos
C H A N N E L S
Observer Timeline
1 2 3 4 5
Observable.create<Int> { emitter ->
for (i in 1..5) {
emitter.onNext(i)
}
emitter.onComplete()
}.subscribe()
@manuelvicnt #DroidconBos
C H A N N E L S
Observer Timeline
1 2 3 4 5
Observer 2 Timeline
Observable.create<Int> { emitter ->
for (i in 1..5) {
emitter.onNext(i)
}
emitter.onComplete()
}.subscribe()
@manuelvicnt #DroidconBos
C H A N N E L S
Observer Timeline
1 2 3 4 5
Observer 2 Timeline
1
Observable.create<Int> { emitter ->
for (i in 1..5) {
emitter.onNext(i)
}
emitter.onComplete()
}.subscribe()
@manuelvicnt #DroidconBos
C H A N N E L S
Observer Timeline
1 2 3 4 5
Observer 2 Timeline
1 2
Observable.create<Int> { emitter ->
for (i in 1..5) {
emitter.onNext(i)
}
emitter.onComplete()
}.subscribe()
@manuelvicnt #DroidconBos
C H A N N E L S
Observer Timeline
1 2 3 4 5
Observer 2 Timeline
1 2 3
Observable.create<Int> { emitter ->
for (i in 1..5) {
emitter.onNext(i)
}
emitter.onComplete()
}.subscribe()
@manuelvicnt #DroidconBos
C H A N N E L S
Observer Timeline
1 2 3 4 5
Observer 2 Timeline
1 2 3 4
Observable.create<Int> { emitter ->
for (i in 1..5) {
emitter.onNext(i)
}
emitter.onComplete()
}.subscribe()
@manuelvicnt #DroidconBos
C H A N N E L S
Observer Timeline
1 2 3 4 5
Observer 2 Timeline
1 2 3 4 5
Observable.create<Int> { emitter ->
for (i in 1..5) {
emitter.onNext(i)
}
emitter.onComplete()
}.subscribe()
@manuelvicnt #DroidconBos
C H A N N E L S
Observer Timeline
1 2 3 4 5
Observer 2 Timeline
1 2 3 4 5
Observable.create<Int> { emitter ->
for (i in 1..5) {
emitter.onNext(i)
}
emitter.onComplete()
}.subscribe()
@manuelvicnt #DroidconBos
C H A N N E L S
• Transfer stream of values
• Can be shared between different Coroutines
• By default, channel capacity == 1
@manuelvicnt #DroidconBos
C H A N N E L S
• Transfer stream of values
• Can be shared between different Coroutines
• By default, channel capacity == 1
interface Channel<E> : SendChannel<E>, ReceiveChannel<E>
@manuelvicnt #DroidconBos
interface Channel<E> : SendChannel<E>, ReceiveChannel<E>
C H A N N E L S
@manuelvicnt #DroidconBos
interface Channel<E> : SendChannel<E>, ReceiveChannel<E>
public interface SendChannel<in E> {
public suspend fun send(element: E)
public fun offer(element: E)
public fun close(cause: Throwable? = null): Boolean
}
public interface ReceiveChannel<out E> {
public suspend fun receive(): E
public fun close(cause: Throwable? = null): Boolean
}
C H A N N E L S
@manuelvicnt #DroidconBos
interface Channel<E> : SendChannel<E>, ReceiveChannel<E>
public interface SendChannel<in E> {
public suspend fun send(element: E)
public fun offer(element: E)
public fun close(cause: Throwable? = null): Boolean
}
public interface ReceiveChannel<out E> {
public suspend fun receive(): E
public fun close(cause: Throwable? = null): Boolean
}
C H A N N E L S
@manuelvicnt #DroidconBos
interface Channel<E> : SendChannel<E>, ReceiveChannel<E>
public interface SendChannel<in E> {
public suspend fun send(element: E)
public fun offer(element: E)
public fun close(cause: Throwable? = null): Boolean
}
public interface ReceiveChannel<out E> {
public suspend fun receive(): E
public fun close(cause: Throwable? = null): Boolean
}
C H A N N E L S
@manuelvicnt #DroidconBos
interface Channel<E> : SendChannel<E>, ReceiveChannel<E>
public interface SendChannel<in E> {
public suspend fun send(element: E)
public fun offer(element: E)
public fun close(cause: Throwable? = null): Boolean
}
public interface ReceiveChannel<out E> {
public suspend fun receive(): E
public fun close(cause: Throwable? = null): Boolean
}
C H A N N E L S
@manuelvicnt #DroidconBos
C H A N N E L S
val channel = Channel<Int>()
Channel
@manuelvicnt #DroidconBos
C H A N N E L S
val channel = Channel<Int>()
Channel
launch {
channel.send(1)
}
@manuelvicnt #DroidconBos
C H A N N E L S
val channel = Channel<Int>()
Channel
1
launch {
channel.send(1)
}
@manuelvicnt #DroidconBos
C H A N N E L S
val channel = Channel<Int>()
Channel
1
launch {
channel.send(1)
}
launch {
val value = channel.receive()
}
@manuelvicnt #DroidconBos
C H A N N E L S
val channel = Channel<Int>()
Channel
1
launch {
channel.send(1)
}
launch {
val value = channel.receive()
}
@manuelvicnt #DroidconBos
C H A N N E L S
val channel = Channel<Int>()
Channel CapacityChannel
1
@manuelvicnt #DroidconBos
C H A N N E L S
val channel = Channel<Int>()
Channel Capacity
launch {
channel.send(1)
channel.send(2)
}
Channel
1
@manuelvicnt #DroidconBos
C H A N N E L S
val channel = Channel<Int>()
Channel Capacity
launch {
channel.send(1)
channel.send(2)
}
Channel
1
@manuelvicnt #DroidconBos
C H A N N E L S
val channel = Channel<Int>()
Channel Capacity
1
launch {
channel.send(1)
channel.send(2)
}
Channel
0
@manuelvicnt #DroidconBos
C H A N N E L S
val channel = Channel<Int>()
Channel Capacity
1
launch {
channel.send(1)
channel.send(2)
}
Channel
0
@manuelvicnt #DroidconBos
C H A N N E L S
val channel = Channel<Int>()
Channel Capacity
1
launch {
channel.send(1)
channel.send(2)
}
Channel
0
// Suspended until the
// channel is NOT full
@manuelvicnt #DroidconBos
C H A N N E L S
val channel = Channel<Int>()
Channel Capacity
1
launch {
channel.send(1)
channel.send(2)
}
Channel
0
// Suspended until the
// channel is NOT full
launch {
val value = channel.receive()
}
@manuelvicnt #DroidconBos
C H A N N E L S
val channel = Channel<Int>()
Channel Capacity
launch {
channel.send(1)
channel.send(2)
}
Channel
1
// Suspended until the
// channel is NOT full
launch {
val value = channel.receive()
}
1
@manuelvicnt #DroidconBos
C H A N N E L S
val channel = Channel<Int>()
Channel Capacity
launch {
channel.send(1)
channel.send(2)
}
Channel
1
// Suspended until the
// channel is NOT full
launch {
val value = channel.receive()
}
1
@manuelvicnt #DroidconBos
C H A N N E L S
val channel = Channel<Int>()
Channel Capacity
launch {
channel.send(1)
channel.send(2)
}
Channel
launch {
val value = channel.receive()
}
11
@manuelvicnt #DroidconBos
C H A N N E L S
val channel = Channel<Int>()
Channel Capacity
launch {
channel.send(1)
channel.send(2)
}
Channel
launch {
val value = channel.receive()
}
11
@manuelvicnt #DroidconBos
C H A N N E L S
val channel = Channel<Int>()
Channel Capacity
launch {
channel.send(1)
channel.send(2)
}
Channel
0
launch {
val value = channel.receive()
}
21
@manuelvicnt #DroidconBos
C H A N N E L S
val channel = Channel<Int>()
launch {
for (x in 1..5) channel.send(x)
}
@manuelvicnt #DroidconBos
C H A N N E L S
val channel = Channel<Int>()
launch {
for (x in 1..5) channel.send(x)
}
launch {
for (value in channel) {
consumeValue(value)
}
}
Channel
@manuelvicnt #DroidconBos
C H A N N E L S
val channel = Channel<Int>()
launch {
for (x in 1..5) channel.send(x)
}
launch {
for (value in channel) {
consumeValue(value)
}
}
Channel
1
@manuelvicnt #DroidconBos
C H A N N E L S
val channel = Channel<Int>()
launch {
for (x in 1..5) channel.send(x)
}
launch {
for (value in channel) {
consumeValue(value)
}
}
Channel
1 2
@manuelvicnt #DroidconBos
C H A N N E L S
val channel = Channel<Int>()
launch {
for (x in 1..5) channel.send(x)
}
launch {
for (value in channel) {
consumeValue(value)
}
}
Channel
1 2 3
@manuelvicnt #DroidconBos
C H A N N E L S
val channel = Channel<Int>()
launch {
for (x in 1..5) channel.send(x)
}
launch {
for (value in channel) {
consumeValue(value)
}
}
Channel
1 2 3 4
@manuelvicnt #DroidconBos
C H A N N E L S
val channel = Channel<Int>()
launch {
for (x in 1..5) channel.send(x)
}
launch {
for (value in channel) {
consumeValue(value)
}
}
Channel
1 2 3 4 5
@manuelvicnt #DroidconBos
C H A N N E L S
val channel = Channel<Int>()
launch {
for (x in 1..5) channel.send(x)
}
launch {
for (value in channel) {
consumeValue(value)
}
}
Channel
1 2 3 4 5
@manuelvicnt #DroidconBos
C H A N N E L S
Channel
1 2 3 4 5
launch {
consumeValue(channel.receive())
}
@manuelvicnt #DroidconBos
C H A N N E L S
Channel
1 2 3 4 5
launch {
consumeValue(channel.receive())
}
@manuelvicnt #DroidconBos
C H A N N E L S
Channel
1 2 3 4 5
launch {
consumeValue(channel.receive())
}
@manuelvicnt #DroidconBos
C H A N N E L S
Channel
1 2 3 4 5
launch {
consumeValue(channel.receive())
}
// Suspended until the
// channel is NOT empty
@manuelvicnt #DroidconBos
B U F F E R E D C H A N N E L S
• Channels take an optional capacity parameter
• Allow senders to send multiple elements before
suspending
@manuelvicnt #DroidconBos
B U F F E R E D C H A N N E L S
• Channels take an optional capacity parameter
• Allow senders to send multiple elements before
suspending
val channel = Channel<Int>(3)
@manuelvicnt #DroidconBos
C H A N N E L S
• We can also use produce that implements the
ReceiveChannel interface
• Only the code inside produce can send elements to
the channel
• Useful to create custom operators
@manuelvicnt #DroidconBos
C H A N N E L S
• We can also use produce that implements the
ReceiveChannel interface
• Only the code inside produce can send elements to
the channel
• Useful to create custom operators
val publisher = produce(capacity = 2) {
for (x in 1..5) send(x)
}
@manuelvicnt #DroidconBos
C H A N N E L S
val publisher = produce(capacity = 2) {
for (x in 1..5) send(x)
}
@manuelvicnt #DroidconBos
C H A N N E L S
val publisher = produce(capacity = 2) {
for (x in 1..5) send(x)
}
launch {
publisher.consumeEach {
consumeValue(it)
}
}
@manuelvicnt #DroidconBos
R A C E C O N D I T I O N I N C H A N N E L S
val channel = Channel<Int>()
@manuelvicnt #DroidconBos
R A C E C O N D I T I O N I N C H A N N E L S
launch {
val value1 = channel.receive()
}
launch {
val value2 = channel.receive()
}
val channel = Channel<Int>()
@manuelvicnt #DroidconBos
R A C E C O N D I T I O N I N C H A N N E L S
launch {
val value1 = channel.receive()
}
launch {
val value2 = channel.receive()
}
val channel = Channel<Int>()
launch {
channel.send(1)
}
B R O A D C A S T
C H A N N E L
@manuelvicnt #DroidconBos
B R O A D C A S T C H A N N E L
• Similar to RxJava Subjects
• Rx Hot Observable behavior
@manuelvicnt #DroidconBos
B R O A D C A S T C H A N N E L
• Similar to RxJava Subjects
• Rx Hot Observable behavior
publishSubject.subscribe {
consumeValue(it)
}
publishSubject.subscribe {
println(it)
}
Observer 1 Timeline
Observer 2 Timeline
@manuelvicnt #DroidconBos
B R O A D C A S T C H A N N E L
• Similar to RxJava Subjects
• Rx Hot Observable behavior
publishSubject.subscribe {
consumeValue(it)
}
publishSubject.subscribe {
println(it)
}
publishSubject.onNext(3)
Observer 1 Timeline
Observer 2 Timeline
@manuelvicnt #DroidconBos
B R O A D C A S T C H A N N E L
• Similar to RxJava Subjects
• Rx Hot Observable behavior
publishSubject.subscribe {
consumeValue(it)
}
publishSubject.subscribe {
println(it)
}
publishSubject.onNext(3)
Observer 1 Timeline
Observer 2 Timeline
3
3
@manuelvicnt #DroidconBos
B R O A D C A S T C H A N N E L
• BroadcastChannel implements the SendChannel<E>
interface
• Emit the same item to multiple consumers that listen
for the elements using openSubscription()
val channel = BroadcastChannel<Int>(2)
@manuelvicnt #DroidconBos
B R O A D C A S T C H A N N E L
val channel = BroadcastChannel<Int>(2)
val observer1Job = launch {
channel.openSubscription().use { channel ->
for (value in channel) {
consumeValue(value)
}
}
}
@manuelvicnt #DroidconBos
B R O A D C A S T C H A N N E L
Observer 1 Timeline
val channel = BroadcastChannel<Int>(2)
val observer1Job = launch {
channel.openSubscription().use { channel ->
for (value in channel) {
consumeValue(value)
}
}
}
@manuelvicnt #DroidconBos
B R O A D C A S T C H A N N E L
val observer2Job = launch {
channel.consumeEach { value ->
consumeValue(value)
}
}
@manuelvicnt #DroidconBos
B R O A D C A S T C H A N N E L
val observer2Job = launch {
channel.consumeEach { value ->
consumeValue(value)
}
}
Observer 2 Timeline
@manuelvicnt #DroidconBos
B R O A D C A S T C H A N N E L
Observer 2 Timeline
Observer 1 Timeline
Channel Capacity
2
@manuelvicnt #DroidconBos
B R O A D C A S T C H A N N E L
Observer 2 Timeline
Observer 1 Timeline
Channel Capacity
2
@manuelvicnt #DroidconBos
B R O A D C A S T C H A N N E L
Observer 2 Timeline
Observer 1 Timeline
channel.send(4)
Channel Capacity
1
@manuelvicnt #DroidconBos
B R O A D C A S T C H A N N E L
Observer 2 Timeline
Observer 1 Timeline
channel.send(4)
4
Channel Capacity
1
@manuelvicnt #DroidconBos
B R O A D C A S T C H A N N E L
Observer 2 Timeline
Observer 1 Timeline
channel.send(4)
4
Channel Capacity
1
@manuelvicnt #DroidconBos
B R O A D C A S T C H A N N E L
Observer 2 Timeline
Observer 1 Timeline
channel.send(4)
4
4
Channel Capacity
2
@manuelvicnt #DroidconBos
B R O A D C A S T C H A N N E L
Observer 2 Timeline
Observer 1 Timeline
channel.send(4)
4
4
Channel Capacity
2
@manuelvicnt #DroidconBos
B R O A D C A S T C H A N N E L
Observer 2 Timeline
Observer 1 Timeline
channel.send(4)
4
4
channel.send(2)
Channel Capacity
1
@manuelvicnt #DroidconBos
B R O A D C A S T C H A N N E L
Observer 2 Timeline
Observer 1 Timeline
channel.send(4)
4
4
channel.send(2)
2
Channel Capacity
1
@manuelvicnt #DroidconBos
B R O A D C A S T C H A N N E L
Observer 2 Timeline
Observer 1 Timeline
channel.send(4)
4
4
channel.send(2)
2
Channel Capacity
1
@manuelvicnt #DroidconBos
B R O A D C A S T C H A N N E L
Observer 2 Timeline
Observer 1 Timeline
channel.send(4)
4
4
channel.send(2)
2
2
Channel Capacity
2
@manuelvicnt #DroidconBos
B R O A D C A S T C H A N N E L
• Special mention to ConflatedBroadcastChannel
• Conflated is a special type of capacity
• Behavior similar to Rx BehaviorSubject
@manuelvicnt #DroidconBos
C O N F L AT E D B R O A D C A S T C H A N N E L
Observer 1 Timeline
4 2
@manuelvicnt #DroidconBos
C O N F L AT E D B R O A D C A S T C H A N N E L
Observer 1 Timeline
4 2
// Closes Subscription
@manuelvicnt #DroidconBos
C O N F L AT E D B R O A D C A S T C H A N N E L
Observer 1 Timeline
4 2
// Closes Subscription
// Resubscribes to ConflatedBroadcastChannel
@manuelvicnt #DroidconBos
C O N F L AT E D B R O A D C A S T C H A N N E L
Observer 1 Timeline
4 2
// Closes Subscription
// Resubscribes to ConflatedBroadcastChannel
New Observer 1 Timeline
2
@manuelvicnt #DroidconBos
It’s supported by default
What about Rx back-pressure?
@manuelvicnt #DroidconBos
Observable Channel Subject
Broadcast
Channel
Cold Hot Hot Hot
Unicast Unicast Broadcast Broadcast
C O M PA R I S O N
@manuelvicnt #DroidconBos
C H A N N E L S
• If we want a “Cold Observable” behavior, we can use
publish
@manuelvicnt #DroidconBos
C H A N N E L S
• If we want a “Cold Observable” behavior, we can use
publish
val publisher = publish {
for (x in 1..5) send(x)
}
@manuelvicnt #DroidconBos
C H A N N E L S
• If we want a “Cold Observable” behavior, we can use
publish
val publisher = publish {
for (x in 1..5) send(x)
}
publisher.openSubscription().use { channel ->
for (value in channel) {
consumeValue(value)
}
}
I N T E R O P
org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlin_coroutines_version
@manuelvicnt #DroidconBos
R X J AVA - > C O R O U T I N E S
• OpenSubscription
Observable.interval(
1, TimeUnit.SECONDS
)
.openSubscription().use { channel ->
for (value in channel) {
consumeValue(value)
}
}
@manuelvicnt #DroidconBos
R X J AVA - > C O R O U T I N E S
• OpenSubscription
Observable.interval(
1, TimeUnit.SECONDS
)
.openSubscription().use { channel ->
for (value in channel) {
consumeValue(value)
}
}
@manuelvicnt #DroidconBos
R X J AVA - > C O R O U T I N E S
• OpenSubscription
Observable.interval(
1, TimeUnit.SECONDS
)
.openSubscription().use { channel ->
for (value in channel) {
consumeValue(value)
}
}
@manuelvicnt #DroidconBos
R X J AVA - > C O R O U T I N E S
• Await
@manuelvicnt #DroidconBos
R X J AVA - > C O R O U T I N E S
• Await
val value = Observable.interval(
1, TimeUnit.SECONDS
)
.awaitFirstOrDefault(-1)
@manuelvicnt #DroidconBos
R X J AVA - > C O R O U T I N E S
• Await
val value = Observable.interval(
1, TimeUnit.SECONDS
)
.awaitFirstOrDefault(-1)
@manuelvicnt #DroidconBos
C O R O U T I N E S - > R X J AVA
• Job.asCompletable
@manuelvicnt #DroidconBos
C O R O U T I N E S - > R X J AVA
• Job.asCompletable
val job = launch {
heavyComputation()
}
job.asCompletable(CommonPool).subscribe({
// Job completed
})
@manuelvicnt #DroidconBos
C O R O U T I N E S - > R X J AVA
• Job.asCompletable
val job = launch {
heavyComputation()
}
job.asCompletable(CommonPool).subscribe({
// Job completed
})
@manuelvicnt #DroidconBos
C O R O U T I N E S - > R X J AVA
• Job.asCompletable
val job = launch {
heavyComputation()
}
job.asCompletable(CommonPool).subscribe({
// Job completed
})
@manuelvicnt #DroidconBos
C O R O U T I N E S - > R X J AVA
• Deferred.asSingle
val deferred = async {
heavyComputation()
}
deferred.asSingle(CommonPool).subscribe({
// Job completed
}, {
// Error happened
})
@manuelvicnt #DroidconBos
C O R O U T I N E S - > R X J AVA
• Deferred.asSingle
val deferred = async {
heavyComputation()
}
deferred.asSingle(CommonPool).subscribe({
// Job completed
}, {
// Error happened
})
@manuelvicnt #DroidconBos
C O R O U T I N E S - > R X J AVA
• Deferred.asSingle
val deferred = async {
heavyComputation()
}
deferred.asSingle(CommonPool).subscribe({
// Job completed
}, {
// Error happened
})
@manuelvicnt #DroidconBos
C O R O U T I N E S - > R X J AVA
• CoroutineBuilders
• rxCompletable
• rxMaybe
• rxSingle
• rxObservable
• rxFlowable
@manuelvicnt #DroidconBos
C O R O U T I N E S - > R X J AVA
• CoroutineBuilders
• rxCompletable
• rxMaybe
• rxSingle
• rxObservable
• rxFlowable
rxCompletable {
// Suspending lambda
}.subscribe()
A C T O R S
@manuelvicnt #DroidconBos
A C T O R S
• Actor = Coroutine + Channel
• Opposite of produce
@manuelvicnt #DroidconBos
A C T O R S
• Actor = Coroutine + Channel
• Opposite of produce
val actor = actor<Int>() {
for (int in channel) {
// iterate over received Integers
}
}
@manuelvicnt #DroidconBos
A C T O R S
• Actor = Coroutine + Channel
• Opposite of produce
val actor = actor<Int>() {
for (int in channel) {
// iterate over received Integers
}
}
launch {
actor.send(2)
}
@manuelvicnt #DroidconBos
A C T O R S
val userActionActor = actor<MainUserAction>(CommonPool) {
for (msg in channel) { // iterate over incoming messages
when (msg) {
is MainUserAction.FirstAction -> {
// Do something
}
is MainUserAction.SecondAction -> {
// Do something
}
}
}
}
@manuelvicnt #DroidconBos
A C T O R S
val userActionActor = actor<MainUserAction>(CommonPool) {
for (msg in channel) { // iterate over incoming messages
when (msg) {
is MainUserAction.FirstAction -> {
// Do something
}
is MainUserAction.SecondAction -> {
// Do something
}
}
}
}
@manuelvicnt #DroidconBos
A C T O R S
val userActionActor = actor<MainUserAction>(CommonPool) {
for (msg in channel) { // iterate over incoming messages
when (msg) {
is MainUserAction.FirstAction -> {
// Do something
}
is MainUserAction.SecondAction -> {
// Do something
}
}
}
}
@manuelvicnt #DroidconBos
A C T O R S
val userActionActor = actor<MainUserAction>(CommonPool) {
for (msg in channel) {
when (msg) {
is MainUserAction.FirstAction -> {
// Do something
}
is MainUserAction.SecondAction -> {
// Do something
}
}
}
}
O P E R AT O R S
@manuelvicnt #DroidconBos
O P E R AT O R S
• Some operators are built into the language with Kotlin
Collections
RxJava Coroutines
map map
filter filter
skip drop
reduce reduce
@manuelvicnt #DroidconBos
O P E R AT O R S
• Some others are easy to implement
fun range(
context: CoroutineContext,
start: Int,
count: Int
) = publish(context) {
for (x in start until start + count) send(x)
}
@manuelvicnt #DroidconBos
O P E R AT O R S
• Some others require more work
• Completable.zip
suspend fun zip(block: () -> Unit, block2: () -> Unit) {
val deferred1 = async { block() }
val deferred2 = async { block2() }
deferred1.await()
deferred2.await()
}
@manuelvicnt #DroidconBos
C O M P L E X O P E R AT O R S
RxJava Coroutines
timeout withTimeoutOrNull
retry repeat(times)
debounce ReceiveChannel<T>.debounce()
groupBy groupBy
T H R E A D I N G
@manuelvicnt #DroidconBos
T H R E A D I N G I N R X
@manuelvicnt #DroidconBos
T H R E A D I N G I N R X
• Threading control Operators: observeOn and
subscribeOn
@manuelvicnt #DroidconBos
T H R E A D I N G I N R X
• Threading control Operators: observeOn and
subscribeOn
• Scheduler is a tool that schedules actions to be
performed
@manuelvicnt #DroidconBos
T H R E A D I N G I N R X
• Threading control Operators: observeOn and
subscribeOn
• Scheduler is a tool that schedules actions to be
performed
• You can create your own Scheduler
@manuelvicnt #DroidconBos
T H R E A D I N G
Single.zip(
Single.just(3),
Single.just(4),
BiFunction<Int, Int, Int> { n1, n2 -> n1 + n2 }
)
.subscribeOn(Schedulers.computation())
.observeOn(Schedulers.io())
.flatMap { n ->
Single.just(n * n)
}
.observeOn(Schedulers.computation())
.flatMap { n ->
Single.just(n - 1)
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
println("Finished with result $it")
}, {
println("Failed with error $it")
})
@manuelvicnt #DroidconBos
T H R E A D I N G
Single.zip(
Single.just(3),
Single.just(4),
BiFunction<Int, Int, Int> { n1, n2 -> n1 + n2 }
)
.subscribeOn(Schedulers.computation())
.observeOn(Schedulers.io())
.flatMap { n ->
Single.just(n * n)
}
.observeOn(Schedulers.computation())
.flatMap { n ->
Single.just(n - 1)
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
println("Finished with result $it")
}, {
println("Failed with error $it")
})
@manuelvicnt #DroidconBos
T H R E A D I N G
Single.zip(
Single.just(3),
Single.just(4),
BiFunction<Int, Int, Int> { n1, n2 -> n1 + n2 }
)
.subscribeOn(Schedulers.computation())
.observeOn(Schedulers.io())
.flatMap { n ->
Single.just(n * n)
}
.observeOn(Schedulers.computation())
.flatMap { n ->
Single.just(n - 1)
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
println("Finished with result $it")
}, {
println("Failed with error $it")
})
@manuelvicnt #DroidconBos
T H R E A D I N G
Single.zip(
Single.just(3),
Single.just(4),
BiFunction<Int, Int, Int> { n1, n2 -> n1 + n2 }
)
.subscribeOn(Schedulers.computation())
.observeOn(Schedulers.io())
.flatMap { n ->
Single.just(n * n)
}
.observeOn(Schedulers.computation())
.flatMap { n ->
Single.just(n - 1)
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
println("Finished with result $it")
}, {
println("Failed with error $it")
})
Computation
@manuelvicnt #DroidconBos
T H R E A D I N G
Single.zip(
Single.just(3),
Single.just(4),
BiFunction<Int, Int, Int> { n1, n2 -> n1 + n2 }
)
.subscribeOn(Schedulers.computation())
.observeOn(Schedulers.io())
.flatMap { n ->
Single.just(n * n)
}
.observeOn(Schedulers.computation())
.flatMap { n ->
Single.just(n - 1)
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
println("Finished with result $it")
}, {
println("Failed with error $it")
})
@manuelvicnt #DroidconBos
T H R E A D I N G
Single.zip(
Single.just(3),
Single.just(4),
BiFunction<Int, Int, Int> { n1, n2 -> n1 + n2 }
)
.subscribeOn(Schedulers.computation())
.observeOn(Schedulers.io())
.flatMap { n ->
Single.just(n * n)
}
.observeOn(Schedulers.computation())
.flatMap { n ->
Single.just(n - 1)
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
println("Finished with result $it")
}, {
println("Failed with error $it")
})
Computation
@manuelvicnt #DroidconBos
T H R E A D I N G
Single.zip(
Single.just(3),
Single.just(4),
BiFunction<Int, Int, Int> { n1, n2 -> n1 + n2 }
)
.subscribeOn(Schedulers.computation())
.observeOn(Schedulers.io())
.flatMap { n ->
Single.just(n * n)
}
.observeOn(Schedulers.computation())
.flatMap { n ->
Single.just(n - 1)
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
println("Finished with result $it")
}, {
println("Failed with error $it")
})
Computation
@manuelvicnt #DroidconBos
T H R E A D I N G
Single.zip(
Single.just(3),
Single.just(4),
BiFunction<Int, Int, Int> { n1, n2 -> n1 + n2 }
)
.subscribeOn(Schedulers.computation())
.observeOn(Schedulers.io())
.flatMap { n ->
Single.just(n * n)
}
.observeOn(Schedulers.computation())
.flatMap { n ->
Single.just(n - 1)
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
println("Finished with result $it")
}, {
println("Failed with error $it")
})
Computation
Io
@manuelvicnt #DroidconBos
T H R E A D I N G
Single.zip(
Single.just(3),
Single.just(4),
BiFunction<Int, Int, Int> { n1, n2 -> n1 + n2 }
)
.subscribeOn(Schedulers.computation())
.observeOn(Schedulers.io())
.flatMap { n ->
Single.just(n * n)
}
.observeOn(Schedulers.computation())
.flatMap { n ->
Single.just(n - 1)
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
println("Finished with result $it")
}, {
println("Failed with error $it")
})
Computation
Io
@manuelvicnt #DroidconBos
T H R E A D I N G
Single.zip(
Single.just(3),
Single.just(4),
BiFunction<Int, Int, Int> { n1, n2 -> n1 + n2 }
)
.subscribeOn(Schedulers.computation())
.observeOn(Schedulers.io())
.flatMap { n ->
Single.just(n * n)
}
.observeOn(Schedulers.computation())
.flatMap { n ->
Single.just(n - 1)
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
println("Finished with result $it")
}, {
println("Failed with error $it")
})
Computation
Io
@manuelvicnt #DroidconBos
T H R E A D I N G
Single.zip(
Single.just(3),
Single.just(4),
BiFunction<Int, Int, Int> { n1, n2 -> n1 + n2 }
)
.subscribeOn(Schedulers.computation())
.observeOn(Schedulers.io())
.flatMap { n ->
Single.just(n * n)
}
.observeOn(Schedulers.computation())
.flatMap { n ->
Single.just(n - 1)
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
println("Finished with result $it")
}, {
println("Failed with error $it")
})
Computation
Io
@manuelvicnt #DroidconBos
T H R E A D I N G
Single.zip(
Single.just(3),
Single.just(4),
BiFunction<Int, Int, Int> { n1, n2 -> n1 + n2 }
)
.subscribeOn(Schedulers.computation())
.observeOn(Schedulers.io())
.flatMap { n ->
Single.just(n * n)
}
.observeOn(Schedulers.computation())
.flatMap { n ->
Single.just(n - 1)
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
println("Finished with result $it")
}, {
println("Failed with error $it")
})
Computation
Io
Main Thread
@manuelvicnt #DroidconBos
T H R E A D I N G I N C O R O U T I N E S
@manuelvicnt #DroidconBos
T H R E A D I N G I N C O R O U T I N E S
• It is defined in the CoroutineContext with a value for
the key ContinuationInterceptor
@manuelvicnt #DroidconBos
T H R E A D I N G I N C O R O U T I N E S
• It is defined in the CoroutineContext with a value for
the key ContinuationInterceptor
• Specified with a CoroutineDispatcher
• Specific Thread
• Thread Pool
@manuelvicnt #DroidconBos
T H R E A D I N G I N C O R O U T I N E S
• Some values:
• CommonPool
• UI (Android)
• Unconfined
@manuelvicnt #DroidconBos
T H R E A D I N G I N C O R O U T I N E S
@manuelvicnt #DroidconBos
T H R E A D I N G I N C O R O U T I N E S
• Create your own ThreadPoolDispatcher
@manuelvicnt #DroidconBos
T H R E A D I N G I N C O R O U T I N E S
• Create your own ThreadPoolDispatcher
• NewSingleThreadContext
val coroutineDispatcher = newSingleThreadContext("Name")
@manuelvicnt #DroidconBos
T H R E A D I N G I N C O R O U T I N E S
• Create your own ThreadPoolDispatcher
• NewSingleThreadContext
• NewFixedThreadPoolContext
••val coroutineDispatcher = newFixedThreadPoolContext(4, "Name")
val coroutineDispatcher = newSingleThreadContext("Name")
@manuelvicnt #DroidconBos
T H R E A D I N G
launch(CommonPool) {
val deferred1 = async(coroutineContext) { 3 }
val deferred2 = async(coroutineContext) { 4 }
var result = deferred1.await() + deferred2.await()
launch(newSingleThreadContext("CustomThread")) {
result = result * result - 1
}.join()
withContext(UI) {
println("Finished with result $result")
}
}
@manuelvicnt #DroidconBos
T H R E A D I N G
launch(CommonPool) {
val deferred1 = async(coroutineContext) { 3 }
val deferred2 = async(coroutineContext) { 4 }
var result = deferred1.await() + deferred2.await()
launch(newSingleThreadContext("CustomThread")) {
result = result * result - 1
}.join()
withContext(UI) {
println("Finished with result $result")
}
}
@manuelvicnt #DroidconBos
T H R E A D I N G
launch(CommonPool) {
val deferred1 = async(coroutineContext) { 3 }
val deferred2 = async(coroutineContext) { 4 }
var result = deferred1.await() + deferred2.await()
launch(newSingleThreadContext("CustomThread")) {
result = result * result - 1
}.join()
withContext(UI) {
println("Finished with result $result")
}
}
@manuelvicnt #DroidconBos
T H R E A D I N G
launch(CommonPool) {
val deferred1 = async(coroutineContext) { 3 }
val deferred2 = async(coroutineContext) { 4 }
var result = deferred1.await() + deferred2.await()
launch(newSingleThreadContext("CustomThread")) {
result = result * result - 1
}.join()
withContext(UI) {
println("Finished with result $result")
}
}
CommonPool
@manuelvicnt #DroidconBos
T H R E A D I N G
launch(CommonPool) {
val deferred1 = async(coroutineContext) { 3 }
val deferred2 = async(coroutineContext) { 4 }
var result = deferred1.await() + deferred2.await()
launch(newSingleThreadContext("CustomThread")) {
result = result * result - 1
}.join()
withContext(UI) {
println("Finished with result $result")
}
}
CommonPool
@manuelvicnt #DroidconBos
T H R E A D I N G
launch(CommonPool) {
val deferred1 = async(coroutineContext) { 3 }
val deferred2 = async(coroutineContext) { 4 }
var result = deferred1.await() + deferred2.await()
launch(newSingleThreadContext("CustomThread")) {
result = result * result - 1
}.join()
withContext(UI) {
println("Finished with result $result")
}
}
CommonPool
CustomThread
@manuelvicnt #DroidconBos
T H R E A D I N G
launch(CommonPool) {
val deferred1 = async(coroutineContext) { 3 }
val deferred2 = async(coroutineContext) { 4 }
var result = deferred1.await() + deferred2.await()
launch(newSingleThreadContext("CustomThread")) {
result = result * result - 1
}.join()
withContext(UI) {
println("Finished with result $result")
}
}
CommonPool
CustomThread
@manuelvicnt #DroidconBos
T H R E A D I N G
launch(CommonPool) {
val deferred1 = async(coroutineContext) { 3 }
val deferred2 = async(coroutineContext) { 4 }
var result = deferred1.await() + deferred2.await()
launch(newSingleThreadContext("CustomThread")) {
result = result * result - 1
}.join()
withContext(UI) {
println("Finished with result $result")
}
}
CommonPool
CustomThread
UI
M AT H A P P
M V I A R C H I T E C T U R E
@manuelvicnt #DroidconBos
M AT H A P P
• Three Projects. Implemented with:
• Coroutines
• RxJava
• Coroutines/RxJava Interop
@manuelvicnt #DroidconBos
M AT H A P P
• MVI
• Architecture Components ViewModels
• Survives Configuration Changes
R E S T O R E S TAT E
@manuelvicnt #DroidconBos
M V I A R C H I T E C T U R E
V I E W
V I E W
M O D E L
1.
User Input
2.
User Action
3.
Process
Result4.
View State
5.
Render State
@manuelvicnt #DroidconBos
M V I A R C H I T E C T U R E
V I E W
V I E W
M O D E L
1.
User Input
2.
User Action
3.
Process
Result4.
View State
5.
Render State
?
@manuelvicnt #DroidconBos
M V I A R C H I T E C T U R E
V I E W
V I E W
M O D E L
1.
User Input
2.
User Action
3.
Process
Result4.
View State
5.
Render State
?
?
@manuelvicnt #DroidconBos
R X A R C H I T E C T U R E
V I E W
V I E W
M O D E L
1.
User Input
2.
User Action
S U B J E C T *
S U B J E C T *
3.
Process
Result4.
View State
5.
Render State
* Not optimal
@manuelvicnt #DroidconBos
R X A R C H I T E C T U R E
V I E W
V I E W
M O D E L
2.
User Action
R X R E L AY
O B S E R VA B L E
4.
View State Exposes an Observable
but implemented with a
Subject so View cannot
call onNext()
RxRelay to avoid
the View calling
onComplete()
@manuelvicnt #DroidconBos
C O R O U T I N E S A R C H I T E C T U R E
V I E W
V I E W
M O D E L
1.
User Input
2.
User Action
A C T O R
C B C *
* ConflatedBroadcastChannel
3.
Process
Result4.
View State
5.
Render State
@manuelvicnt #DroidconBos
C O M M O N I M P L E M E N TAT I O N
sealed class MainUserAction {
class Calculate(
val number: Long
) : MainUserAction()
class FunFactEnabled(
val enabled: Boolean
) : MainUserAction()
}
@manuelvicnt #DroidconBos
C O M M O N I M P L E M E N TAT I O N
sealed class MainViewState {
object Loading : MainViewState()
class Rendered(
val fibonacciNumber: Long,
val funFact: String
) : MainViewState()
object WrongInputError : MainViewState()
object RequestError : MainViewState()
}
@manuelvicnt #DroidconBos
V I E W M O D E L
• Extends from ViewModel in Architecture Components
• Receives User Actions
• Processes User Action
• Notifies View with the ViewState
@manuelvicnt #DroidconBos
R X V I E W M O D E L
@manuelvicnt #DroidconBos
R X V I E W M O D E L
• Receives User Actions
val userActionSubject: PublishSubject<MainUserAction>
@manuelvicnt #DroidconBos
R X V I E W M O D E L
• Receives User Actions
• Notifies View with the ViewState
val viewStateSubject: BehaviorSubject<MainViewState>
val userActionSubject: PublishSubject<MainUserAction>
@manuelvicnt #DroidconBos
R X V I E W M O D E L
init {
userActionSubject
.subscribeOn(Schedulers.computation())
.subscribe({
when (it) {
is MainUserAction.Calculate -> {
if (it.number <= 0) {
viewStateSubject.
onNext(MainViewState.WrongInputError)
} else {
viewStateSubject.onNext(MainViewState.Loading)
processCalculation(it)
}
}
is MainUserAction.FunFactEnabled -> {
askForFunFact = it.enabled
}
}
})
}
@manuelvicnt #DroidconBos
R X V I E W M O D E L
init {
userActionSubject
.subscribeOn(Schedulers.computation())
.subscribe({
when (it) {
is MainUserAction.Calculate -> {
if (it.number <= 0) {
viewStateSubject.
onNext(MainViewState.WrongInputError)
} else {
viewStateSubject.onNext(MainViewState.Loading)
processCalculation(it)
}
}
is MainUserAction.FunFactEnabled -> {
askForFunFact = it.enabled
}
}
})
}
@manuelvicnt #DroidconBos
R X V I E W M O D E L
init {
userActionSubject
.subscribeOn(Schedulers.computation())
.subscribe({
when (it) {
is MainUserAction.Calculate -> {
if (it.number <= 0) {
viewStateSubject.
onNext(MainViewState.WrongInputError)
} else {
viewStateSubject.onNext(MainViewState.Loading)
processCalculation(it)
}
}
is MainUserAction.FunFactEnabled -> {
askForFunFact = it.enabled
}
}
})
}
@manuelvicnt #DroidconBos
R X V I E W M O D E L
init {
userActionSubject
.subscribeOn(Schedulers.computation())
.subscribe({
when (it) {
is MainUserAction.Calculate -> {
if (it.number <= 0) {
viewStateSubject.
onNext(MainViewState.WrongInputError)
} else {
viewStateSubject.onNext(MainViewState.Loading)
processCalculation(it)
}
}
is MainUserAction.FunFactEnabled -> {
askForFunFact = it.enabled
}
}
})
}
@manuelvicnt #DroidconBos
R X V I E W M O D E L
init {
userActionSubject
.subscribeOn(Schedulers.computation())
.subscribe({
when (it) {
is MainUserAction.Calculate -> {
if (it.number <= 0) {
viewStateSubject.
onNext(MainViewState.WrongInputError)
} else {
viewStateSubject.onNext(MainViewState.Loading)
processCalculation(it)
}
}
is MainUserAction.FunFactEnabled -> {
askForFunFact = it.enabled
}
}
})
}
@manuelvicnt #DroidconBos
• Clean up when it’s not longer needed
R X V I E W M O D E L
override fun onCleared() {
userActionSubject.onComplete()
viewStateSubject.onComplete()
super.onCleared()
}
@manuelvicnt #DroidconBos
C O R O U T I N E S V I E W M O D E L
@manuelvicnt #DroidconBos
C O R O U T I N E S V I E W M O D E L
• Receives User Actions
val userActionActor = actor<MainUserAction>(CommonPool, parent = parentJob)
@manuelvicnt #DroidconBos
C O R O U T I N E S V I E W M O D E L
• Receives User Actions
• Notifies View with the ViewState
val viewStateChannel = ConflatedBroadcastChannel<MainViewState>()
val userActionActor = actor<MainUserAction>(CommonPool, parent = parentJob)
@manuelvicnt #DroidconBos
C O R O U T I N E S V I E W M O D E L
private val parentJob = Job()
val userActionActor = actor<MainUserAction>(
CommonPool,
parent = parentJob
) {
for (msg in channel) { // iterate over incoming messages
when (msg) {
is MainUserAction.Calculate -> {
if (msg.number <= 0) {
viewStateChannel.offer(MainViewState.WrongInputError)
} else {
viewStateChannel.offer(MainViewState.Loading)
processCalculation(msg)
}
}
is MainUserAction.FunFactEnabled -> {
askForFunFact = msg.enabled
}
}
}
}
@manuelvicnt #DroidconBos
C O R O U T I N E S V I E W M O D E L
private val parentJob = Job()
val userActionActor = actor<MainUserAction>(
CommonPool,
parent = parentJob
) {
for (msg in channel) { // iterate over incoming messages
when (msg) {
is MainUserAction.Calculate -> {
if (msg.number <= 0) {
viewStateChannel.offer(MainViewState.WrongInputError)
} else {
viewStateChannel.offer(MainViewState.Loading)
processCalculation(msg)
}
}
is MainUserAction.FunFactEnabled -> {
askForFunFact = msg.enabled
}
}
}
}
@manuelvicnt #DroidconBos
C O R O U T I N E S V I E W M O D E L
private val parentJob = Job()
val userActionActor = actor<MainUserAction>(
CommonPool,
parent = parentJob
) {
for (msg in channel) { // iterate over incoming messages
when (msg) {
is MainUserAction.Calculate -> {
if (msg.number <= 0) {
viewStateChannel.offer(MainViewState.WrongInputError)
} else {
viewStateChannel.offer(MainViewState.Loading)
processCalculation(msg)
}
}
is MainUserAction.FunFactEnabled -> {
askForFunFact = msg.enabled
}
}
}
}
@manuelvicnt #DroidconBos
C O R O U T I N E S V I E W M O D E L
private val parentJob = Job()
val userActionActor = actor<MainUserAction>(
CommonPool,
parent = parentJob
) {
for (msg in channel) { // iterate over incoming messages
when (msg) {
is MainUserAction.Calculate -> {
if (msg.number <= 0) {
viewStateChannel.offer(MainViewState.WrongInputError)
} else {
viewStateChannel.offer(MainViewState.Loading)
processCalculation(msg)
}
}
is MainUserAction.FunFactEnabled -> {
askForFunFact = msg.enabled
}
}
}
}
@manuelvicnt #DroidconBos
C O R O U T I N E S V I E W M O D E L
private val parentJob = Job()
val userActionActor = actor<MainUserAction>(
CommonPool,
parent = parentJob
) {
for (msg in channel) { // iterate over incoming messages
when (msg) {
is MainUserAction.Calculate -> {
if (msg.number <= 0) {
viewStateChannel.offer(MainViewState.WrongInputError)
} else {
viewStateChannel.offer(MainViewState.Loading)
processCalculation(msg)
}
}
is MainUserAction.FunFactEnabled -> {
askForFunFact = msg.enabled
}
}
}
}
@manuelvicnt #DroidconBos
• Clean up when it’s not longer needed
C O R O U T I N E S V I E W M O D E L
override fun onCleared() {
viewStateChannel.close()
parentJob.cancel() // Cancels the Actor
super.onCleared()
}
@manuelvicnt #DroidconBos
V I E W
• Listens for User Events
• Talks to ViewModel to process Input
• Renders ViewState
@manuelvicnt #DroidconBos
R X J AVA V I E W
• Listens for User Events
• Talks to ViewModel to process Input
private fun setupViews() {
calcButton.setOnClickListener {
viewModel.userActionSubject.onNext(
MainUserAction.Calculate(
input.text.toString().toLong()))
}
funFact.setOnCheckedChangeListener { _, isChecked ->
viewModel.userActionSubject.onNext(
MainUserAction.FunFactEnabled(isChecked))
}
}
@manuelvicnt #DroidconBos
R X J AVA V I E W
• Listens for User Events
• Talks to ViewModel to process Input
private fun setupViews() {
calcButton.setOnClickListener {
viewModel.userActionSubject.onNext(
MainUserAction.Calculate(
input.text.toString().toLong()))
}
funFact.setOnCheckedChangeListener { _, isChecked ->
viewModel.userActionSubject.onNext(
MainUserAction.FunFactEnabled(isChecked))
}
}
@manuelvicnt #DroidconBos
R X J AVA V I E W
• Listens for User Events
• Talks to ViewModel to process Input
private fun setupViews() {
calcButton.setOnClickListener {
viewModel.userActionSubject.onNext(
MainUserAction.Calculate(
input.text.toString().toLong()))
}
funFact.setOnCheckedChangeListener { _, isChecked ->
viewModel.userActionSubject.onNext(
MainUserAction.FunFactEnabled(isChecked))
}
}
@manuelvicnt #DroidconBos
R X J AVA V I E W
private fun listenViewModel() {
viewStateDisposable = viewModel.viewStateObservable
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
when (it) {
MainViewState.Loading -> {
progressBar.visibility = View.VISIBLE
result.text = "Loading..."
funFactText.text = ""
}
MainViewState.WrongInputError -> {
showError()
}
…
}
})
}
@manuelvicnt #DroidconBos
R X J AVA V I E W
• Renders ViewState
private fun listenViewModel() {
viewStateDisposable = viewModel.viewStateObservable
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
when (it) {
MainViewState.Loading -> {
progressBar.visibility = View.VISIBLE
result.text = "Loading..."
funFactText.text = ""
}
MainViewState.WrongInputError -> {
showError()
}
…
}
})
}
@manuelvicnt #DroidconBos
R X J AVA V I E W
private fun listenViewModel() {
viewStateDisposable = viewModel.viewStateObservable
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
when (it) {
MainViewState.Loading -> {
progressBar.visibility = View.VISIBLE
result.text = "Loading..."
funFactText.text = ""
}
MainViewState.WrongInputError -> {
showError()
}
…
}
})
}
@manuelvicnt #DroidconBos
R X J AVA V I E W
• Renders ViewState
private fun listenViewModel() {
viewStateDisposable = viewModel.viewStateObservable
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
when (it) {
MainViewState.Loading -> {
progressBar.visibility = View.VISIBLE
result.text = "Loading..."
funFactText.text = ""
}
MainViewState.WrongInputError -> {
showError()
}
…
}
})
}
@manuelvicnt #DroidconBos
R X J AVA V I E W
private fun listenViewModel() {
viewStateDisposable = viewModel.viewStateObservable
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
when (it) {
MainViewState.Loading -> {
progressBar.visibility = View.VISIBLE
result.text = "Loading..."
funFactText.text = ""
}
MainViewState.WrongInputError -> {
showError()
}
…
}
})
}
@manuelvicnt #DroidconBos
R X J AVA V I E W
• Renders ViewState
private fun listenViewModel() {
viewStateDisposable = viewModel.viewStateObservable
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
when (it) {
MainViewState.Loading -> {
progressBar.visibility = View.VISIBLE
result.text = "Loading..."
funFactText.text = ""
}
MainViewState.WrongInputError -> {
showError()
}
…
}
})
}
@manuelvicnt #DroidconBos
R X J AVA V I E W
override fun onStart() {
super.onStart()
listenViewModel() // Registers to ViewState Observable
}
override fun onStop() {
if (viewStateDisposable?.isDisposed == false) {
viewStateDisposable?.dispose()
}
super.onStop()
}
@manuelvicnt #DroidconBos
R X J AVA V I E W
• View Lifecycle
override fun onStart() {
super.onStart()
listenViewModel() // Registers to ViewState Observable
}
override fun onStop() {
if (viewStateDisposable?.isDisposed == false) {
viewStateDisposable?.dispose()
}
super.onStop()
}
@manuelvicnt #DroidconBos
C O R O U T I N E S V I E W
• Listens for User Events
• Talks to ViewModel to process Input
private fun setupViews() {
calcButton.setOnClickListener {
viewModel.userActionActor.offer(
MainUserAction.Calculate(
input.text.toString().toLong()))
}
funFact.setOnCheckedChangeListener { _, isChecked ->
viewModel.userActionActor.offer(
MainUserAction.FunFactEnabled(isChecked))
}
}
@manuelvicnt #DroidconBos
C O R O U T I N E S V I E W
• Listens for User Events
• Talks to ViewModel to process Input
private fun setupViews() {
calcButton.setOnClickListener {
viewModel.userActionActor.offer(
MainUserAction.Calculate(
input.text.toString().toLong()))
}
funFact.setOnCheckedChangeListener { _, isChecked ->
viewModel.userActionActor.offer(
MainUserAction.FunFactEnabled(isChecked))
}
}
@manuelvicnt #DroidconBos
C O R O U T I N E S V I E W
• Listens for User Events
• Talks to ViewModel to process Input
private fun setupViews() {
calcButton.setOnClickListener {
viewModel.userActionActor.offer(
MainUserAction.Calculate(
input.text.toString().toLong()))
}
funFact.setOnCheckedChangeListener { _, isChecked ->
viewModel.userActionActor.offer(
MainUserAction.FunFactEnabled(isChecked))
}
}
@manuelvicnt #DroidconBos
C O R O U T I N E S V I E W
private val parentJob = Job()
private fun listenViewModel() {
// Launch on the CommonPool to not block the MainThread
launch(parentJob + CommonPool) {
viewModel.viewStateChannel.consumeEach {
withContext(UI) {
when (it) {
MainViewState.Loading -> {
progressBar.visibility = View.VISIBLE
result.text = "Loading..."
funFactText.text = ""
}
…
}
}
}
}
}
@manuelvicnt #DroidconBos
C O R O U T I N E S V I E W
• Renders ViewState
private val parentJob = Job()
private fun listenViewModel() {
// Launch on the CommonPool to not block the MainThread
launch(parentJob + CommonPool) {
viewModel.viewStateChannel.consumeEach {
withContext(UI) {
when (it) {
MainViewState.Loading -> {
progressBar.visibility = View.VISIBLE
result.text = "Loading..."
funFactText.text = ""
}
…
}
}
}
}
}
@manuelvicnt #DroidconBos
C O R O U T I N E S V I E W
private val parentJob = Job()
private fun listenViewModel() {
// Launch on the CommonPool to not block the MainThread
launch(parentJob + CommonPool) {
viewModel.viewStateChannel.consumeEach {
withContext(UI) {
when (it) {
MainViewState.Loading -> {
progressBar.visibility = View.VISIBLE
result.text = "Loading..."
funFactText.text = ""
}
…
}
}
}
}
}
@manuelvicnt #DroidconBos
C O R O U T I N E S V I E W
• Renders ViewState
private val parentJob = Job()
private fun listenViewModel() {
// Launch on the CommonPool to not block the MainThread
launch(parentJob + CommonPool) {
viewModel.viewStateChannel.consumeEach {
withContext(UI) {
when (it) {
MainViewState.Loading -> {
progressBar.visibility = View.VISIBLE
result.text = "Loading..."
funFactText.text = ""
}
…
}
}
}
}
}
@manuelvicnt #DroidconBos
C O R O U T I N E S V I E W
private val parentJob = Job()
private fun listenViewModel() {
// Launch on the CommonPool to not block the MainThread
launch(parentJob + CommonPool) {
viewModel.viewStateChannel.consumeEach {
withContext(UI) {
when (it) {
MainViewState.Loading -> {
progressBar.visibility = View.VISIBLE
result.text = "Loading..."
funFactText.text = ""
}
…
}
}
}
}
}
@manuelvicnt #DroidconBos
C O R O U T I N E S V I E W
• Renders ViewState
private val parentJob = Job()
private fun listenViewModel() {
// Launch on the CommonPool to not block the MainThread
launch(parentJob + CommonPool) {
viewModel.viewStateChannel.consumeEach {
withContext(UI) {
when (it) {
MainViewState.Loading -> {
progressBar.visibility = View.VISIBLE
result.text = "Loading..."
funFactText.text = ""
}
…
}
}
}
}
}
@manuelvicnt #DroidconBos
C O R O U T I N E S V I E W
private val parentJob = Job()
private fun listenViewModel() {
// Launch on the CommonPool to not block the MainThread
launch(parentJob + CommonPool) {
viewModel.viewStateChannel.consumeEach {
withContext(UI) {
when (it) {
MainViewState.Loading -> {
progressBar.visibility = View.VISIBLE
result.text = "Loading..."
funFactText.text = ""
}
…
}
}
}
}
}
@manuelvicnt #DroidconBos
C O R O U T I N E S V I E W
• Renders ViewState
private val parentJob = Job()
private fun listenViewModel() {
// Launch on the CommonPool to not block the MainThread
launch(parentJob + CommonPool) {
viewModel.viewStateChannel.consumeEach {
withContext(UI) {
when (it) {
MainViewState.Loading -> {
progressBar.visibility = View.VISIBLE
result.text = "Loading..."
funFactText.text = ""
}
…
}
}
}
}
}
@manuelvicnt #DroidconBos
C O R O U T I N E S V I E W
override fun onStart() {
super.onStart()
listenViewModel() // Listens to ViewStateChannel
}
override fun onStop() {
parentJob.cancel()
super.onStop()
}
@manuelvicnt #DroidconBos
C O R O U T I N E S V I E W
• View Lifecycle
override fun onStart() {
super.onStart()
listenViewModel() // Listens to ViewStateChannel
}
override fun onStop() {
parentJob.cancel()
super.onStop()
}
S H O W M E S O M E
N U M B E R S
@manuelvicnt #DroidconBos
P E R F O R M A N C E
@manuelvicnt #DroidconBos
P E R F O R M A N C E
• Roughly the same
• Not difficult enough to compare
@manuelvicnt #DroidconBos
S I Z E
Measure Coroutines RxJava Interop
APK Size 2.4MB 2.9MB 3.1MB
Method Count 29,131 37,271 39,590
@manuelvicnt #DroidconBos
G I T H U B L I N K S
manuelvicnt/MathCoroutines
Coroutines
manuelvicnt/MathRxJava
RxJava
manuelvicnt/MathRxCoroutines
Coroutines and RxJava
@manuelvicnt #DroidconBos
C O N C L U S I O N
@manuelvicnt #DroidconBos
C O N C L U S I O N
• Both libraries provide a way to do Asynchronous
Programming
• If you are a RxJava expert, no need to switch
• If you struggle with RxJava, Coroutines is another
option you can try
• For new Android developers, Coroutines has a lower
learning curve
C O R O U T I N E S A N D R X J AVA
A N A S Y N C H R O N I C I T Y C O M PA R I S O N
@ M A N U E LV I C N T
A N Y Q U E S T I O N S ?
M A N U E L V I C E N T E V I V O

More Related Content

What's hot

Ankara jug mayıs 2013 sunumu
Ankara jug mayıs 2013 sunumuAnkara jug mayıs 2013 sunumu
Ankara jug mayıs 2013 sunumuAnkara JUG
 
Kotlin for backend development (Hackaburg 2018 Regensburg)
Kotlin for backend development (Hackaburg 2018 Regensburg)Kotlin for backend development (Hackaburg 2018 Regensburg)
Kotlin for backend development (Hackaburg 2018 Regensburg)Tobias Schneck
 
PyWPS Development restart
PyWPS Development restartPyWPS Development restart
PyWPS Development restartJachym Cepicky
 
Learning Python from Data
Learning Python from DataLearning Python from Data
Learning Python from DataMosky Liu
 
7 Sins of Java fixed in Kotlin
7 Sins of Java fixed in Kotlin7 Sins of Java fixed in Kotlin
7 Sins of Java fixed in KotlinLuca Guadagnini
 
Golang basics for Java developers - Part 1
Golang basics for Java developers - Part 1Golang basics for Java developers - Part 1
Golang basics for Java developers - Part 1Robert Stern
 
Go Language Hands-on Workshop Material
Go Language Hands-on Workshop MaterialGo Language Hands-on Workshop Material
Go Language Hands-on Workshop MaterialRomin Irani
 
Machine Learning on Code - SF meetup
Machine Learning on Code - SF meetupMachine Learning on Code - SF meetup
Machine Learning on Code - SF meetupsource{d}
 
Understanding PyPy - PyConEs 14
Understanding PyPy - PyConEs 14Understanding PyPy - PyConEs 14
Understanding PyPy - PyConEs 14fcofdezc
 
Reactive programming with tracker
Reactive programming with trackerReactive programming with tracker
Reactive programming with trackerDesignveloper
 

What's hot (11)

Ankara jug mayıs 2013 sunumu
Ankara jug mayıs 2013 sunumuAnkara jug mayıs 2013 sunumu
Ankara jug mayıs 2013 sunumu
 
Kotlin for backend development (Hackaburg 2018 Regensburg)
Kotlin for backend development (Hackaburg 2018 Regensburg)Kotlin for backend development (Hackaburg 2018 Regensburg)
Kotlin for backend development (Hackaburg 2018 Regensburg)
 
PyWPS Development restart
PyWPS Development restartPyWPS Development restart
PyWPS Development restart
 
Learning Python from Data
Learning Python from DataLearning Python from Data
Learning Python from Data
 
7 Sins of Java fixed in Kotlin
7 Sins of Java fixed in Kotlin7 Sins of Java fixed in Kotlin
7 Sins of Java fixed in Kotlin
 
Golang basics for Java developers - Part 1
Golang basics for Java developers - Part 1Golang basics for Java developers - Part 1
Golang basics for Java developers - Part 1
 
How to use git without rage
How to use git without rageHow to use git without rage
How to use git without rage
 
Go Language Hands-on Workshop Material
Go Language Hands-on Workshop MaterialGo Language Hands-on Workshop Material
Go Language Hands-on Workshop Material
 
Machine Learning on Code - SF meetup
Machine Learning on Code - SF meetupMachine Learning on Code - SF meetup
Machine Learning on Code - SF meetup
 
Understanding PyPy - PyConEs 14
Understanding PyPy - PyConEs 14Understanding PyPy - PyConEs 14
Understanding PyPy - PyConEs 14
 
Reactive programming with tracker
Reactive programming with trackerReactive programming with tracker
Reactive programming with tracker
 

Similar to Coroutines and RxJava - An Asynchronicity Comparison

Kotlin hands on - MorningTech ekito 2017
Kotlin hands on - MorningTech ekito 2017Kotlin hands on - MorningTech ekito 2017
Kotlin hands on - MorningTech ekito 2017Arnaud Giuliani
 
java8-patterns
java8-patternsjava8-patterns
java8-patternsJustin Lin
 
Kotlin Coroutines and Android sitting in a tree - 2018 version
Kotlin Coroutines and Android sitting in a tree - 2018 versionKotlin Coroutines and Android sitting in a tree - 2018 version
Kotlin Coroutines and Android sitting in a tree - 2018 versionKai Koenig
 
Writing Kotlin Multiplatform libraries that your iOS teammates are gonna love
Writing Kotlin Multiplatform libraries that your iOS teammates are gonna loveWriting Kotlin Multiplatform libraries that your iOS teammates are gonna love
Writing Kotlin Multiplatform libraries that your iOS teammates are gonna loveAndré Oriani
 
Mastering Python lesson3b_for_loops
Mastering Python lesson3b_for_loopsMastering Python lesson3b_for_loops
Mastering Python lesson3b_for_loopsRuth Marvin
 
fun kotlinMultiplatform()
fun kotlinMultiplatform()fun kotlinMultiplatform()
fun kotlinMultiplatform()ssuserdd678d
 
Geecon - Improve your Android-fu with Kotlin
Geecon - Improve your Android-fu with KotlinGeecon - Improve your Android-fu with Kotlin
Geecon - Improve your Android-fu with KotlinNicolas Fränkel
 
The State of Lightweight Threads for the JVM
The State of Lightweight Threads for the JVMThe State of Lightweight Threads for the JVM
The State of Lightweight Threads for the JVMVolkan Yazıcı
 
Reduce dependency on Rx with Kotlin Coroutines
Reduce dependency on Rx with Kotlin CoroutinesReduce dependency on Rx with Kotlin Coroutines
Reduce dependency on Rx with Kotlin CoroutinesLINE Corporation
 
Devoxx 2018: From Java to Kotlin: the adventures of a smooth migration
Devoxx 2018: From Java to Kotlin: the adventures of a smooth migrationDevoxx 2018: From Java to Kotlin: the adventures of a smooth migration
Devoxx 2018: From Java to Kotlin: the adventures of a smooth migrationPaulien van Alst
 
Kotlin - The Swiss army knife of programming languages - Visma Mobile Meet-up...
Kotlin - The Swiss army knife of programming languages - Visma Mobile Meet-up...Kotlin - The Swiss army knife of programming languages - Visma Mobile Meet-up...
Kotlin - The Swiss army knife of programming languages - Visma Mobile Meet-up...Tudor Dragan
 
Kotlin coroutine - the next step for RxJava developer?
Kotlin coroutine - the next step for RxJava developer?Kotlin coroutine - the next step for RxJava developer?
Kotlin coroutine - the next step for RxJava developer?Artur Latoszewski
 
FluentMigrator - Dayton .NET - July 2023
FluentMigrator - Dayton .NET - July 2023FluentMigrator - Dayton .NET - July 2023
FluentMigrator - Dayton .NET - July 2023Matthew Groves
 
Matteo Vaccari - TDD per Android | Codemotion Milan 2015
Matteo Vaccari - TDD per Android | Codemotion Milan 2015Matteo Vaccari - TDD per Android | Codemotion Milan 2015
Matteo Vaccari - TDD per Android | Codemotion Milan 2015Codemotion
 
Kotlin 1.2: Sharing code between platforms
Kotlin 1.2: Sharing code between platformsKotlin 1.2: Sharing code between platforms
Kotlin 1.2: Sharing code between platformsKirill Rozov
 
Free The Enterprise With Ruby & Master Your Own Domain
Free The Enterprise With Ruby & Master Your Own DomainFree The Enterprise With Ruby & Master Your Own Domain
Free The Enterprise With Ruby & Master Your Own DomainKen Collins
 

Similar to Coroutines and RxJava - An Asynchronicity Comparison (20)

Kotlin hands on - MorningTech ekito 2017
Kotlin hands on - MorningTech ekito 2017Kotlin hands on - MorningTech ekito 2017
Kotlin hands on - MorningTech ekito 2017
 
java8-patterns
java8-patternsjava8-patterns
java8-patterns
 
Kotlin Coroutines and Android sitting in a tree - 2018 version
Kotlin Coroutines and Android sitting in a tree - 2018 versionKotlin Coroutines and Android sitting in a tree - 2018 version
Kotlin Coroutines and Android sitting in a tree - 2018 version
 
ForLoops.pptx
ForLoops.pptxForLoops.pptx
ForLoops.pptx
 
Writing Kotlin Multiplatform libraries that your iOS teammates are gonna love
Writing Kotlin Multiplatform libraries that your iOS teammates are gonna loveWriting Kotlin Multiplatform libraries that your iOS teammates are gonna love
Writing Kotlin Multiplatform libraries that your iOS teammates are gonna love
 
Mastering Python lesson3b_for_loops
Mastering Python lesson3b_for_loopsMastering Python lesson3b_for_loops
Mastering Python lesson3b_for_loops
 
Fall in love with Kotlin
Fall in love with KotlinFall in love with Kotlin
Fall in love with Kotlin
 
2-Functions.pdf
2-Functions.pdf2-Functions.pdf
2-Functions.pdf
 
fun kotlinMultiplatform()
fun kotlinMultiplatform()fun kotlinMultiplatform()
fun kotlinMultiplatform()
 
Geecon - Improve your Android-fu with Kotlin
Geecon - Improve your Android-fu with KotlinGeecon - Improve your Android-fu with Kotlin
Geecon - Improve your Android-fu with Kotlin
 
The State of Lightweight Threads for the JVM
The State of Lightweight Threads for the JVMThe State of Lightweight Threads for the JVM
The State of Lightweight Threads for the JVM
 
Reduce dependency on Rx with Kotlin Coroutines
Reduce dependency on Rx with Kotlin CoroutinesReduce dependency on Rx with Kotlin Coroutines
Reduce dependency on Rx with Kotlin Coroutines
 
Devoxx 2018: From Java to Kotlin: the adventures of a smooth migration
Devoxx 2018: From Java to Kotlin: the adventures of a smooth migrationDevoxx 2018: From Java to Kotlin: the adventures of a smooth migration
Devoxx 2018: From Java to Kotlin: the adventures of a smooth migration
 
Kotlin - The Swiss army knife of programming languages - Visma Mobile Meet-up...
Kotlin - The Swiss army knife of programming languages - Visma Mobile Meet-up...Kotlin - The Swiss army knife of programming languages - Visma Mobile Meet-up...
Kotlin - The Swiss army knife of programming languages - Visma Mobile Meet-up...
 
Kotlin coroutine - the next step for RxJava developer?
Kotlin coroutine - the next step for RxJava developer?Kotlin coroutine - the next step for RxJava developer?
Kotlin coroutine - the next step for RxJava developer?
 
FluentMigrator - Dayton .NET - July 2023
FluentMigrator - Dayton .NET - July 2023FluentMigrator - Dayton .NET - July 2023
FluentMigrator - Dayton .NET - July 2023
 
Matteo Vaccari - TDD per Android | Codemotion Milan 2015
Matteo Vaccari - TDD per Android | Codemotion Milan 2015Matteo Vaccari - TDD per Android | Codemotion Milan 2015
Matteo Vaccari - TDD per Android | Codemotion Milan 2015
 
Current State of Coroutines
Current State of CoroutinesCurrent State of Coroutines
Current State of Coroutines
 
Kotlin 1.2: Sharing code between platforms
Kotlin 1.2: Sharing code between platformsKotlin 1.2: Sharing code between platforms
Kotlin 1.2: Sharing code between platforms
 
Free The Enterprise With Ruby & Master Your Own Domain
Free The Enterprise With Ruby & Master Your Own DomainFree The Enterprise With Ruby & Master Your Own Domain
Free The Enterprise With Ruby & Master Your Own Domain
 

More from Manuel Vicente Vivo

Android Development - ConstraintLayout
Android Development - ConstraintLayoutAndroid Development - ConstraintLayout
Android Development - ConstraintLayoutManuel Vicente Vivo
 
Mobile Development - Unit and Automation Testing
Mobile Development - Unit and Automation TestingMobile Development - Unit and Automation Testing
Mobile Development - Unit and Automation TestingManuel Vicente Vivo
 
Agile - Opportunities to improve a team
Agile - Opportunities to improve a teamAgile - Opportunities to improve a team
Agile - Opportunities to improve a teamManuel Vicente Vivo
 
Android 101 - Manuel Vicente Vivo
Android 101 - Manuel Vicente VivoAndroid 101 - Manuel Vicente Vivo
Android 101 - Manuel Vicente VivoManuel Vicente Vivo
 
Go reactive - Manuel Vicente Vivo
Go reactive - Manuel Vicente VivoGo reactive - Manuel Vicente Vivo
Go reactive - Manuel Vicente VivoManuel Vicente Vivo
 
Android Wear - Manuel Vicente Vivo
Android Wear - Manuel Vicente VivoAndroid Wear - Manuel Vicente Vivo
Android Wear - Manuel Vicente VivoManuel Vicente Vivo
 

More from Manuel Vicente Vivo (6)

Android Development - ConstraintLayout
Android Development - ConstraintLayoutAndroid Development - ConstraintLayout
Android Development - ConstraintLayout
 
Mobile Development - Unit and Automation Testing
Mobile Development - Unit and Automation TestingMobile Development - Unit and Automation Testing
Mobile Development - Unit and Automation Testing
 
Agile - Opportunities to improve a team
Agile - Opportunities to improve a teamAgile - Opportunities to improve a team
Agile - Opportunities to improve a team
 
Android 101 - Manuel Vicente Vivo
Android 101 - Manuel Vicente VivoAndroid 101 - Manuel Vicente Vivo
Android 101 - Manuel Vicente Vivo
 
Go reactive - Manuel Vicente Vivo
Go reactive - Manuel Vicente VivoGo reactive - Manuel Vicente Vivo
Go reactive - Manuel Vicente Vivo
 
Android Wear - Manuel Vicente Vivo
Android Wear - Manuel Vicente VivoAndroid Wear - Manuel Vicente Vivo
Android Wear - Manuel Vicente Vivo
 

Coroutines and RxJava - An Asynchronicity Comparison

  • 1. C O R O U T I N E S A N D R X J AVA A N A S Y N C H R O N I C I T Y C O M PA R I S O N M A N U E L V I C E N T E V I V O @ M A N U E LV I C N T
  • 2. @manuelvicnt #DroidconBos A G E N D A • Coroutines Recap • RxJava & Coroutines concepts • Build an App
  • 3. @manuelvicnt #DroidconBos G O A L S • Learn Coroutines with basic RxJava knowledge • Compare both libraries on different topics • Use what we learned to build the App • Code available
  • 5. @manuelvicnt #DroidconBos I N T E N D E D A U D I E N C E • Able to read Kotlin code • Basic RxJava experience • Coroutines 101 • You want to learn differences and similarities between RxJava and Coroutines
  • 6. @manuelvicnt #DroidconBos I H E A R D C O R O U T I N E S ? • From the Kotlin documentation… • Coroutines simplify asynchronous programming • Code can be expressed sequentially and the library handles the asynchronous code for us • Computations can be suspended without blocking a Thread
  • 7. @manuelvicnt #DroidconBos I H E A R D C O R O U T I N E S ? • From the Kotlin documentation… • Coroutines simplify asynchronous programming • Code can be expressed sequentially and the library handles the asynchronous code for us • Computations can be suspended without blocking a Thread
  • 8. @manuelvicnt #DroidconBos I H E A R D C O R O U T I N E S ? • From the Kotlin documentation… • Coroutines simplify asynchronous programming • Code can be expressed sequentially and the library handles the asynchronous code for us • Computations can be suspended without blocking a Thread
  • 9. I WA N T T O P L AY A G A M E
  • 10. @manuelvicnt #DroidconBos What is this? launch(CommonPool) { heavyComputation() }
  • 11. @manuelvicnt #DroidconBos What is this? launch(CommonPool) { heavyComputation() } Coroutine
  • 12. @manuelvicnt #DroidconBos What is this? launch(CommonPool) { heavyComputation() }
  • 13. @manuelvicnt #DroidconBos What is this? launch(CommonPool) { heavyComputation() } Coroutine Builder
  • 14. @manuelvicnt #DroidconBos What is this? launch(CommonPool) { heavyComputation() }
  • 15. @manuelvicnt #DroidconBos What is this? launch(CommonPool) { heavyComputation() } Coroutine Context
  • 16. @manuelvicnt #DroidconBos What is this? launch(CommonPool) { heavyComputation() }
  • 17. @manuelvicnt #DroidconBos What is this? launch(CommonPool) { heavyComputation() } Suspending Lambda
  • 18. @manuelvicnt #DroidconBos What is this? launch(CommonPool) { heavyComputation() val data = makeNetworkRequest() updateDB(data) } Suspending Lambda
  • 19. @manuelvicnt #DroidconBos What is this? launch(CommonPool) { heavyComputation() val data = makeNetworkRequest() updateDB(data) } Suspending Lambda
  • 20. @manuelvicnt #DroidconBos What is this? launch(CommonPool) { heavyComputation() val data = makeNetworkRequest() updateDB(data) } Suspending Lambda
  • 21. @manuelvicnt #DroidconBos What is this? launch(CommonPool) { heavyComputation() val data = makeNetworkRequest() updateDB(data) } Suspending Lambda
  • 22. @manuelvicnt #DroidconBos What is this? launch(CommonPool) { heavyComputation() val data = makeNetworkRequest() updateDB(data) } Suspending Lambda
  • 23. @manuelvicnt #DroidconBos What is this? launch(CommonPool) { heavyComputation() val data = makeNetworkRequest() updateDB(data) } Suspending Lambda
  • 24. @manuelvicnt #DroidconBos What is this? launch(CommonPool) { heavyComputation() val data = makeNetworkRequest() updateDB(data) } Suspending Lambda
  • 25. @manuelvicnt #DroidconBos What is this? launch(CommonPool) { heavyComputation() val data = makeNetworkRequest() updateDB(data) } Suspending Lambda
  • 26. C O R O U T I N E S R E C A P C O M P L E T E D
  • 27. C O R O U T I N E S - R X J AVA C O M PA R I S O N
  • 28. C A N C E L E X E C U T I O N
  • 30. @manuelvicnt #DroidconBos With Disposables How to cancel an Observable? val disposable: Disposable = Observable.interval(1, TimeUnit.SECONDS) .subscribe() disposable.dispose()
  • 31. @manuelvicnt #DroidconBos With Disposables How to cancel an Observable? val disposable: Disposable = Observable.interval(1, TimeUnit.SECONDS) .subscribe() disposable.dispose()
  • 32. @manuelvicnt #DroidconBos With the Coroutine Job (from the Coroutine Context) How to cancel a Coroutine?
  • 33. @manuelvicnt #DroidconBos With the Coroutine Job (from the Coroutine Context) How to cancel a Coroutine? val job = launch(CommonPool) { // my suspending block } job.cancel()
  • 34. @manuelvicnt #DroidconBos With the Coroutine Job (from the Coroutine Context) How to cancel a Coroutine? val parentJob = Job() launch(parentJob + CommonPool) { // my suspending block } parentJob.cancel()
  • 35. @manuelvicnt #DroidconBos With the Coroutine Job (from the Coroutine Context) How to cancel a Coroutine? val parentJob = Job() launch(CommonPool, parent = parentJob) { // my suspending block } parentJob.cancel()
  • 36. C H A N N E L S
  • 37. @manuelvicnt #DroidconBos C H A N N E L S • Transfer stream of values • Similar to Reactive Streams Publisher or RxJava Observable/Flowable
  • 38. @manuelvicnt #DroidconBos C H A N N E L S • Transfer stream of values • Similar to Reactive Streams Publisher or RxJava Observable/Flowable Observable.create<Int> { emitter -> for (i in 1..5) { emitter.onNext(i) } emitter.onComplete() }
  • 39. @manuelvicnt #DroidconBos Observable.create<Int> { emitter -> for (i in 1..5) { emitter.onNext(i) } emitter.onComplete() } C H A N N E L S
  • 40. @manuelvicnt #DroidconBos Observable.create<Int> { emitter -> for (i in 1..5) { emitter.onNext(i) } emitter.onComplete() } C H A N N E L S
  • 41. @manuelvicnt #DroidconBos Observable.create<Int> { emitter -> for (i in 1..5) { emitter.onNext(i) } emitter.onComplete() } C H A N N E L S
  • 42. @manuelvicnt #DroidconBos Observable.create<Int> { emitter -> for (i in 1..5) { emitter.onNext(i) } emitter.onComplete() }.subscribe() C H A N N E L S
  • 43. @manuelvicnt #DroidconBos C H A N N E L S Observable.create<Int> { emitter -> for (i in 1..5) { emitter.onNext(i) } emitter.onComplete() }.subscribe()
  • 44. @manuelvicnt #DroidconBos C H A N N E L S Observer Timeline Observable.create<Int> { emitter -> for (i in 1..5) { emitter.onNext(i) } emitter.onComplete() }.subscribe()
  • 45. @manuelvicnt #DroidconBos C H A N N E L S Observer Timeline 1 Observable.create<Int> { emitter -> for (i in 1..5) { emitter.onNext(i) } emitter.onComplete() }.subscribe()
  • 46. @manuelvicnt #DroidconBos C H A N N E L S Observer Timeline 1 2 Observable.create<Int> { emitter -> for (i in 1..5) { emitter.onNext(i) } emitter.onComplete() }.subscribe()
  • 47. @manuelvicnt #DroidconBos C H A N N E L S Observer Timeline 1 2 3 Observable.create<Int> { emitter -> for (i in 1..5) { emitter.onNext(i) } emitter.onComplete() }.subscribe()
  • 48. @manuelvicnt #DroidconBos C H A N N E L S Observer Timeline 1 2 3 4 Observable.create<Int> { emitter -> for (i in 1..5) { emitter.onNext(i) } emitter.onComplete() }.subscribe()
  • 49. @manuelvicnt #DroidconBos C H A N N E L S Observer Timeline 1 2 3 4 5 Observable.create<Int> { emitter -> for (i in 1..5) { emitter.onNext(i) } emitter.onComplete() }.subscribe()
  • 50. @manuelvicnt #DroidconBos C H A N N E L S Observer Timeline 1 2 3 4 5 Observable.create<Int> { emitter -> for (i in 1..5) { emitter.onNext(i) } emitter.onComplete() }.subscribe()
  • 51. @manuelvicnt #DroidconBos C H A N N E L S Observer Timeline 1 2 3 4 5 Observable.create<Int> { emitter -> for (i in 1..5) { emitter.onNext(i) } emitter.onComplete() }.subscribe()
  • 52. @manuelvicnt #DroidconBos C H A N N E L S Observer Timeline 1 2 3 4 5 Observer 2 Timeline Observable.create<Int> { emitter -> for (i in 1..5) { emitter.onNext(i) } emitter.onComplete() }.subscribe()
  • 53. @manuelvicnt #DroidconBos C H A N N E L S Observer Timeline 1 2 3 4 5 Observer 2 Timeline 1 Observable.create<Int> { emitter -> for (i in 1..5) { emitter.onNext(i) } emitter.onComplete() }.subscribe()
  • 54. @manuelvicnt #DroidconBos C H A N N E L S Observer Timeline 1 2 3 4 5 Observer 2 Timeline 1 2 Observable.create<Int> { emitter -> for (i in 1..5) { emitter.onNext(i) } emitter.onComplete() }.subscribe()
  • 55. @manuelvicnt #DroidconBos C H A N N E L S Observer Timeline 1 2 3 4 5 Observer 2 Timeline 1 2 3 Observable.create<Int> { emitter -> for (i in 1..5) { emitter.onNext(i) } emitter.onComplete() }.subscribe()
  • 56. @manuelvicnt #DroidconBos C H A N N E L S Observer Timeline 1 2 3 4 5 Observer 2 Timeline 1 2 3 4 Observable.create<Int> { emitter -> for (i in 1..5) { emitter.onNext(i) } emitter.onComplete() }.subscribe()
  • 57. @manuelvicnt #DroidconBos C H A N N E L S Observer Timeline 1 2 3 4 5 Observer 2 Timeline 1 2 3 4 5 Observable.create<Int> { emitter -> for (i in 1..5) { emitter.onNext(i) } emitter.onComplete() }.subscribe()
  • 58. @manuelvicnt #DroidconBos C H A N N E L S Observer Timeline 1 2 3 4 5 Observer 2 Timeline 1 2 3 4 5 Observable.create<Int> { emitter -> for (i in 1..5) { emitter.onNext(i) } emitter.onComplete() }.subscribe()
  • 59. @manuelvicnt #DroidconBos C H A N N E L S • Transfer stream of values • Can be shared between different Coroutines • By default, channel capacity == 1
  • 60. @manuelvicnt #DroidconBos C H A N N E L S • Transfer stream of values • Can be shared between different Coroutines • By default, channel capacity == 1 interface Channel<E> : SendChannel<E>, ReceiveChannel<E>
  • 61. @manuelvicnt #DroidconBos interface Channel<E> : SendChannel<E>, ReceiveChannel<E> C H A N N E L S
  • 62. @manuelvicnt #DroidconBos interface Channel<E> : SendChannel<E>, ReceiveChannel<E> public interface SendChannel<in E> { public suspend fun send(element: E) public fun offer(element: E) public fun close(cause: Throwable? = null): Boolean } public interface ReceiveChannel<out E> { public suspend fun receive(): E public fun close(cause: Throwable? = null): Boolean } C H A N N E L S
  • 63. @manuelvicnt #DroidconBos interface Channel<E> : SendChannel<E>, ReceiveChannel<E> public interface SendChannel<in E> { public suspend fun send(element: E) public fun offer(element: E) public fun close(cause: Throwable? = null): Boolean } public interface ReceiveChannel<out E> { public suspend fun receive(): E public fun close(cause: Throwable? = null): Boolean } C H A N N E L S
  • 64. @manuelvicnt #DroidconBos interface Channel<E> : SendChannel<E>, ReceiveChannel<E> public interface SendChannel<in E> { public suspend fun send(element: E) public fun offer(element: E) public fun close(cause: Throwable? = null): Boolean } public interface ReceiveChannel<out E> { public suspend fun receive(): E public fun close(cause: Throwable? = null): Boolean } C H A N N E L S
  • 65. @manuelvicnt #DroidconBos interface Channel<E> : SendChannel<E>, ReceiveChannel<E> public interface SendChannel<in E> { public suspend fun send(element: E) public fun offer(element: E) public fun close(cause: Throwable? = null): Boolean } public interface ReceiveChannel<out E> { public suspend fun receive(): E public fun close(cause: Throwable? = null): Boolean } C H A N N E L S
  • 66. @manuelvicnt #DroidconBos C H A N N E L S val channel = Channel<Int>() Channel
  • 67. @manuelvicnt #DroidconBos C H A N N E L S val channel = Channel<Int>() Channel launch { channel.send(1) }
  • 68. @manuelvicnt #DroidconBos C H A N N E L S val channel = Channel<Int>() Channel 1 launch { channel.send(1) }
  • 69. @manuelvicnt #DroidconBos C H A N N E L S val channel = Channel<Int>() Channel 1 launch { channel.send(1) } launch { val value = channel.receive() }
  • 70. @manuelvicnt #DroidconBos C H A N N E L S val channel = Channel<Int>() Channel 1 launch { channel.send(1) } launch { val value = channel.receive() }
  • 71. @manuelvicnt #DroidconBos C H A N N E L S val channel = Channel<Int>() Channel CapacityChannel 1
  • 72. @manuelvicnt #DroidconBos C H A N N E L S val channel = Channel<Int>() Channel Capacity launch { channel.send(1) channel.send(2) } Channel 1
  • 73. @manuelvicnt #DroidconBos C H A N N E L S val channel = Channel<Int>() Channel Capacity launch { channel.send(1) channel.send(2) } Channel 1
  • 74. @manuelvicnt #DroidconBos C H A N N E L S val channel = Channel<Int>() Channel Capacity 1 launch { channel.send(1) channel.send(2) } Channel 0
  • 75. @manuelvicnt #DroidconBos C H A N N E L S val channel = Channel<Int>() Channel Capacity 1 launch { channel.send(1) channel.send(2) } Channel 0
  • 76. @manuelvicnt #DroidconBos C H A N N E L S val channel = Channel<Int>() Channel Capacity 1 launch { channel.send(1) channel.send(2) } Channel 0 // Suspended until the // channel is NOT full
  • 77. @manuelvicnt #DroidconBos C H A N N E L S val channel = Channel<Int>() Channel Capacity 1 launch { channel.send(1) channel.send(2) } Channel 0 // Suspended until the // channel is NOT full launch { val value = channel.receive() }
  • 78. @manuelvicnt #DroidconBos C H A N N E L S val channel = Channel<Int>() Channel Capacity launch { channel.send(1) channel.send(2) } Channel 1 // Suspended until the // channel is NOT full launch { val value = channel.receive() } 1
  • 79. @manuelvicnt #DroidconBos C H A N N E L S val channel = Channel<Int>() Channel Capacity launch { channel.send(1) channel.send(2) } Channel 1 // Suspended until the // channel is NOT full launch { val value = channel.receive() } 1
  • 80. @manuelvicnt #DroidconBos C H A N N E L S val channel = Channel<Int>() Channel Capacity launch { channel.send(1) channel.send(2) } Channel launch { val value = channel.receive() } 11
  • 81. @manuelvicnt #DroidconBos C H A N N E L S val channel = Channel<Int>() Channel Capacity launch { channel.send(1) channel.send(2) } Channel launch { val value = channel.receive() } 11
  • 82. @manuelvicnt #DroidconBos C H A N N E L S val channel = Channel<Int>() Channel Capacity launch { channel.send(1) channel.send(2) } Channel 0 launch { val value = channel.receive() } 21
  • 83. @manuelvicnt #DroidconBos C H A N N E L S val channel = Channel<Int>() launch { for (x in 1..5) channel.send(x) }
  • 84. @manuelvicnt #DroidconBos C H A N N E L S val channel = Channel<Int>() launch { for (x in 1..5) channel.send(x) } launch { for (value in channel) { consumeValue(value) } } Channel
  • 85. @manuelvicnt #DroidconBos C H A N N E L S val channel = Channel<Int>() launch { for (x in 1..5) channel.send(x) } launch { for (value in channel) { consumeValue(value) } } Channel 1
  • 86. @manuelvicnt #DroidconBos C H A N N E L S val channel = Channel<Int>() launch { for (x in 1..5) channel.send(x) } launch { for (value in channel) { consumeValue(value) } } Channel 1 2
  • 87. @manuelvicnt #DroidconBos C H A N N E L S val channel = Channel<Int>() launch { for (x in 1..5) channel.send(x) } launch { for (value in channel) { consumeValue(value) } } Channel 1 2 3
  • 88. @manuelvicnt #DroidconBos C H A N N E L S val channel = Channel<Int>() launch { for (x in 1..5) channel.send(x) } launch { for (value in channel) { consumeValue(value) } } Channel 1 2 3 4
  • 89. @manuelvicnt #DroidconBos C H A N N E L S val channel = Channel<Int>() launch { for (x in 1..5) channel.send(x) } launch { for (value in channel) { consumeValue(value) } } Channel 1 2 3 4 5
  • 90. @manuelvicnt #DroidconBos C H A N N E L S val channel = Channel<Int>() launch { for (x in 1..5) channel.send(x) } launch { for (value in channel) { consumeValue(value) } } Channel 1 2 3 4 5
  • 91. @manuelvicnt #DroidconBos C H A N N E L S Channel 1 2 3 4 5 launch { consumeValue(channel.receive()) }
  • 92. @manuelvicnt #DroidconBos C H A N N E L S Channel 1 2 3 4 5 launch { consumeValue(channel.receive()) }
  • 93. @manuelvicnt #DroidconBos C H A N N E L S Channel 1 2 3 4 5 launch { consumeValue(channel.receive()) }
  • 94. @manuelvicnt #DroidconBos C H A N N E L S Channel 1 2 3 4 5 launch { consumeValue(channel.receive()) } // Suspended until the // channel is NOT empty
  • 95. @manuelvicnt #DroidconBos B U F F E R E D C H A N N E L S • Channels take an optional capacity parameter • Allow senders to send multiple elements before suspending
  • 96. @manuelvicnt #DroidconBos B U F F E R E D C H A N N E L S • Channels take an optional capacity parameter • Allow senders to send multiple elements before suspending val channel = Channel<Int>(3)
  • 97. @manuelvicnt #DroidconBos C H A N N E L S • We can also use produce that implements the ReceiveChannel interface • Only the code inside produce can send elements to the channel • Useful to create custom operators
  • 98. @manuelvicnt #DroidconBos C H A N N E L S • We can also use produce that implements the ReceiveChannel interface • Only the code inside produce can send elements to the channel • Useful to create custom operators val publisher = produce(capacity = 2) { for (x in 1..5) send(x) }
  • 99. @manuelvicnt #DroidconBos C H A N N E L S val publisher = produce(capacity = 2) { for (x in 1..5) send(x) }
  • 100. @manuelvicnt #DroidconBos C H A N N E L S val publisher = produce(capacity = 2) { for (x in 1..5) send(x) } launch { publisher.consumeEach { consumeValue(it) } }
  • 101. @manuelvicnt #DroidconBos R A C E C O N D I T I O N I N C H A N N E L S val channel = Channel<Int>()
  • 102. @manuelvicnt #DroidconBos R A C E C O N D I T I O N I N C H A N N E L S launch { val value1 = channel.receive() } launch { val value2 = channel.receive() } val channel = Channel<Int>()
  • 103. @manuelvicnt #DroidconBos R A C E C O N D I T I O N I N C H A N N E L S launch { val value1 = channel.receive() } launch { val value2 = channel.receive() } val channel = Channel<Int>() launch { channel.send(1) }
  • 104. B R O A D C A S T C H A N N E L
  • 105. @manuelvicnt #DroidconBos B R O A D C A S T C H A N N E L • Similar to RxJava Subjects • Rx Hot Observable behavior
  • 106. @manuelvicnt #DroidconBos B R O A D C A S T C H A N N E L • Similar to RxJava Subjects • Rx Hot Observable behavior publishSubject.subscribe { consumeValue(it) } publishSubject.subscribe { println(it) } Observer 1 Timeline Observer 2 Timeline
  • 107. @manuelvicnt #DroidconBos B R O A D C A S T C H A N N E L • Similar to RxJava Subjects • Rx Hot Observable behavior publishSubject.subscribe { consumeValue(it) } publishSubject.subscribe { println(it) } publishSubject.onNext(3) Observer 1 Timeline Observer 2 Timeline
  • 108. @manuelvicnt #DroidconBos B R O A D C A S T C H A N N E L • Similar to RxJava Subjects • Rx Hot Observable behavior publishSubject.subscribe { consumeValue(it) } publishSubject.subscribe { println(it) } publishSubject.onNext(3) Observer 1 Timeline Observer 2 Timeline 3 3
  • 109. @manuelvicnt #DroidconBos B R O A D C A S T C H A N N E L • BroadcastChannel implements the SendChannel<E> interface • Emit the same item to multiple consumers that listen for the elements using openSubscription() val channel = BroadcastChannel<Int>(2)
  • 110. @manuelvicnt #DroidconBos B R O A D C A S T C H A N N E L val channel = BroadcastChannel<Int>(2) val observer1Job = launch { channel.openSubscription().use { channel -> for (value in channel) { consumeValue(value) } } }
  • 111. @manuelvicnt #DroidconBos B R O A D C A S T C H A N N E L Observer 1 Timeline val channel = BroadcastChannel<Int>(2) val observer1Job = launch { channel.openSubscription().use { channel -> for (value in channel) { consumeValue(value) } } }
  • 112. @manuelvicnt #DroidconBos B R O A D C A S T C H A N N E L val observer2Job = launch { channel.consumeEach { value -> consumeValue(value) } }
  • 113. @manuelvicnt #DroidconBos B R O A D C A S T C H A N N E L val observer2Job = launch { channel.consumeEach { value -> consumeValue(value) } } Observer 2 Timeline
  • 114. @manuelvicnt #DroidconBos B R O A D C A S T C H A N N E L Observer 2 Timeline Observer 1 Timeline Channel Capacity 2
  • 115. @manuelvicnt #DroidconBos B R O A D C A S T C H A N N E L Observer 2 Timeline Observer 1 Timeline Channel Capacity 2
  • 116. @manuelvicnt #DroidconBos B R O A D C A S T C H A N N E L Observer 2 Timeline Observer 1 Timeline channel.send(4) Channel Capacity 1
  • 117. @manuelvicnt #DroidconBos B R O A D C A S T C H A N N E L Observer 2 Timeline Observer 1 Timeline channel.send(4) 4 Channel Capacity 1
  • 118. @manuelvicnt #DroidconBos B R O A D C A S T C H A N N E L Observer 2 Timeline Observer 1 Timeline channel.send(4) 4 Channel Capacity 1
  • 119. @manuelvicnt #DroidconBos B R O A D C A S T C H A N N E L Observer 2 Timeline Observer 1 Timeline channel.send(4) 4 4 Channel Capacity 2
  • 120. @manuelvicnt #DroidconBos B R O A D C A S T C H A N N E L Observer 2 Timeline Observer 1 Timeline channel.send(4) 4 4 Channel Capacity 2
  • 121. @manuelvicnt #DroidconBos B R O A D C A S T C H A N N E L Observer 2 Timeline Observer 1 Timeline channel.send(4) 4 4 channel.send(2) Channel Capacity 1
  • 122. @manuelvicnt #DroidconBos B R O A D C A S T C H A N N E L Observer 2 Timeline Observer 1 Timeline channel.send(4) 4 4 channel.send(2) 2 Channel Capacity 1
  • 123. @manuelvicnt #DroidconBos B R O A D C A S T C H A N N E L Observer 2 Timeline Observer 1 Timeline channel.send(4) 4 4 channel.send(2) 2 Channel Capacity 1
  • 124. @manuelvicnt #DroidconBos B R O A D C A S T C H A N N E L Observer 2 Timeline Observer 1 Timeline channel.send(4) 4 4 channel.send(2) 2 2 Channel Capacity 2
  • 125. @manuelvicnt #DroidconBos B R O A D C A S T C H A N N E L • Special mention to ConflatedBroadcastChannel • Conflated is a special type of capacity • Behavior similar to Rx BehaviorSubject
  • 126. @manuelvicnt #DroidconBos C O N F L AT E D B R O A D C A S T C H A N N E L Observer 1 Timeline 4 2
  • 127. @manuelvicnt #DroidconBos C O N F L AT E D B R O A D C A S T C H A N N E L Observer 1 Timeline 4 2 // Closes Subscription
  • 128. @manuelvicnt #DroidconBos C O N F L AT E D B R O A D C A S T C H A N N E L Observer 1 Timeline 4 2 // Closes Subscription // Resubscribes to ConflatedBroadcastChannel
  • 129. @manuelvicnt #DroidconBos C O N F L AT E D B R O A D C A S T C H A N N E L Observer 1 Timeline 4 2 // Closes Subscription // Resubscribes to ConflatedBroadcastChannel New Observer 1 Timeline 2
  • 130. @manuelvicnt #DroidconBos It’s supported by default What about Rx back-pressure?
  • 131. @manuelvicnt #DroidconBos Observable Channel Subject Broadcast Channel Cold Hot Hot Hot Unicast Unicast Broadcast Broadcast C O M PA R I S O N
  • 132. @manuelvicnt #DroidconBos C H A N N E L S • If we want a “Cold Observable” behavior, we can use publish
  • 133. @manuelvicnt #DroidconBos C H A N N E L S • If we want a “Cold Observable” behavior, we can use publish val publisher = publish { for (x in 1..5) send(x) }
  • 134. @manuelvicnt #DroidconBos C H A N N E L S • If we want a “Cold Observable” behavior, we can use publish val publisher = publish { for (x in 1..5) send(x) } publisher.openSubscription().use { channel -> for (value in channel) { consumeValue(value) } }
  • 135. I N T E R O P org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlin_coroutines_version
  • 136. @manuelvicnt #DroidconBos R X J AVA - > C O R O U T I N E S • OpenSubscription Observable.interval( 1, TimeUnit.SECONDS ) .openSubscription().use { channel -> for (value in channel) { consumeValue(value) } }
  • 137. @manuelvicnt #DroidconBos R X J AVA - > C O R O U T I N E S • OpenSubscription Observable.interval( 1, TimeUnit.SECONDS ) .openSubscription().use { channel -> for (value in channel) { consumeValue(value) } }
  • 138. @manuelvicnt #DroidconBos R X J AVA - > C O R O U T I N E S • OpenSubscription Observable.interval( 1, TimeUnit.SECONDS ) .openSubscription().use { channel -> for (value in channel) { consumeValue(value) } }
  • 139. @manuelvicnt #DroidconBos R X J AVA - > C O R O U T I N E S • Await
  • 140. @manuelvicnt #DroidconBos R X J AVA - > C O R O U T I N E S • Await val value = Observable.interval( 1, TimeUnit.SECONDS ) .awaitFirstOrDefault(-1)
  • 141. @manuelvicnt #DroidconBos R X J AVA - > C O R O U T I N E S • Await val value = Observable.interval( 1, TimeUnit.SECONDS ) .awaitFirstOrDefault(-1)
  • 142. @manuelvicnt #DroidconBos C O R O U T I N E S - > R X J AVA • Job.asCompletable
  • 143. @manuelvicnt #DroidconBos C O R O U T I N E S - > R X J AVA • Job.asCompletable val job = launch { heavyComputation() } job.asCompletable(CommonPool).subscribe({ // Job completed })
  • 144. @manuelvicnt #DroidconBos C O R O U T I N E S - > R X J AVA • Job.asCompletable val job = launch { heavyComputation() } job.asCompletable(CommonPool).subscribe({ // Job completed })
  • 145. @manuelvicnt #DroidconBos C O R O U T I N E S - > R X J AVA • Job.asCompletable val job = launch { heavyComputation() } job.asCompletable(CommonPool).subscribe({ // Job completed })
  • 146. @manuelvicnt #DroidconBos C O R O U T I N E S - > R X J AVA • Deferred.asSingle val deferred = async { heavyComputation() } deferred.asSingle(CommonPool).subscribe({ // Job completed }, { // Error happened })
  • 147. @manuelvicnt #DroidconBos C O R O U T I N E S - > R X J AVA • Deferred.asSingle val deferred = async { heavyComputation() } deferred.asSingle(CommonPool).subscribe({ // Job completed }, { // Error happened })
  • 148. @manuelvicnt #DroidconBos C O R O U T I N E S - > R X J AVA • Deferred.asSingle val deferred = async { heavyComputation() } deferred.asSingle(CommonPool).subscribe({ // Job completed }, { // Error happened })
  • 149. @manuelvicnt #DroidconBos C O R O U T I N E S - > R X J AVA • CoroutineBuilders • rxCompletable • rxMaybe • rxSingle • rxObservable • rxFlowable
  • 150. @manuelvicnt #DroidconBos C O R O U T I N E S - > R X J AVA • CoroutineBuilders • rxCompletable • rxMaybe • rxSingle • rxObservable • rxFlowable rxCompletable { // Suspending lambda }.subscribe()
  • 151. A C T O R S
  • 152. @manuelvicnt #DroidconBos A C T O R S • Actor = Coroutine + Channel • Opposite of produce
  • 153. @manuelvicnt #DroidconBos A C T O R S • Actor = Coroutine + Channel • Opposite of produce val actor = actor<Int>() { for (int in channel) { // iterate over received Integers } }
  • 154. @manuelvicnt #DroidconBos A C T O R S • Actor = Coroutine + Channel • Opposite of produce val actor = actor<Int>() { for (int in channel) { // iterate over received Integers } } launch { actor.send(2) }
  • 155. @manuelvicnt #DroidconBos A C T O R S val userActionActor = actor<MainUserAction>(CommonPool) { for (msg in channel) { // iterate over incoming messages when (msg) { is MainUserAction.FirstAction -> { // Do something } is MainUserAction.SecondAction -> { // Do something } } } }
  • 156. @manuelvicnt #DroidconBos A C T O R S val userActionActor = actor<MainUserAction>(CommonPool) { for (msg in channel) { // iterate over incoming messages when (msg) { is MainUserAction.FirstAction -> { // Do something } is MainUserAction.SecondAction -> { // Do something } } } }
  • 157. @manuelvicnt #DroidconBos A C T O R S val userActionActor = actor<MainUserAction>(CommonPool) { for (msg in channel) { // iterate over incoming messages when (msg) { is MainUserAction.FirstAction -> { // Do something } is MainUserAction.SecondAction -> { // Do something } } } }
  • 158. @manuelvicnt #DroidconBos A C T O R S val userActionActor = actor<MainUserAction>(CommonPool) { for (msg in channel) { when (msg) { is MainUserAction.FirstAction -> { // Do something } is MainUserAction.SecondAction -> { // Do something } } } }
  • 159. O P E R AT O R S
  • 160. @manuelvicnt #DroidconBos O P E R AT O R S • Some operators are built into the language with Kotlin Collections RxJava Coroutines map map filter filter skip drop reduce reduce
  • 161. @manuelvicnt #DroidconBos O P E R AT O R S • Some others are easy to implement fun range( context: CoroutineContext, start: Int, count: Int ) = publish(context) { for (x in start until start + count) send(x) }
  • 162. @manuelvicnt #DroidconBos O P E R AT O R S • Some others require more work • Completable.zip suspend fun zip(block: () -> Unit, block2: () -> Unit) { val deferred1 = async { block() } val deferred2 = async { block2() } deferred1.await() deferred2.await() }
  • 163. @manuelvicnt #DroidconBos C O M P L E X O P E R AT O R S RxJava Coroutines timeout withTimeoutOrNull retry repeat(times) debounce ReceiveChannel<T>.debounce() groupBy groupBy
  • 164. T H R E A D I N G
  • 165. @manuelvicnt #DroidconBos T H R E A D I N G I N R X
  • 166. @manuelvicnt #DroidconBos T H R E A D I N G I N R X • Threading control Operators: observeOn and subscribeOn
  • 167. @manuelvicnt #DroidconBos T H R E A D I N G I N R X • Threading control Operators: observeOn and subscribeOn • Scheduler is a tool that schedules actions to be performed
  • 168. @manuelvicnt #DroidconBos T H R E A D I N G I N R X • Threading control Operators: observeOn and subscribeOn • Scheduler is a tool that schedules actions to be performed • You can create your own Scheduler
  • 169. @manuelvicnt #DroidconBos T H R E A D I N G Single.zip( Single.just(3), Single.just(4), BiFunction<Int, Int, Int> { n1, n2 -> n1 + n2 } ) .subscribeOn(Schedulers.computation()) .observeOn(Schedulers.io()) .flatMap { n -> Single.just(n * n) } .observeOn(Schedulers.computation()) .flatMap { n -> Single.just(n - 1) } .observeOn(AndroidSchedulers.mainThread()) .subscribe({ println("Finished with result $it") }, { println("Failed with error $it") })
  • 170. @manuelvicnt #DroidconBos T H R E A D I N G Single.zip( Single.just(3), Single.just(4), BiFunction<Int, Int, Int> { n1, n2 -> n1 + n2 } ) .subscribeOn(Schedulers.computation()) .observeOn(Schedulers.io()) .flatMap { n -> Single.just(n * n) } .observeOn(Schedulers.computation()) .flatMap { n -> Single.just(n - 1) } .observeOn(AndroidSchedulers.mainThread()) .subscribe({ println("Finished with result $it") }, { println("Failed with error $it") })
  • 171. @manuelvicnt #DroidconBos T H R E A D I N G Single.zip( Single.just(3), Single.just(4), BiFunction<Int, Int, Int> { n1, n2 -> n1 + n2 } ) .subscribeOn(Schedulers.computation()) .observeOn(Schedulers.io()) .flatMap { n -> Single.just(n * n) } .observeOn(Schedulers.computation()) .flatMap { n -> Single.just(n - 1) } .observeOn(AndroidSchedulers.mainThread()) .subscribe({ println("Finished with result $it") }, { println("Failed with error $it") })
  • 172. @manuelvicnt #DroidconBos T H R E A D I N G Single.zip( Single.just(3), Single.just(4), BiFunction<Int, Int, Int> { n1, n2 -> n1 + n2 } ) .subscribeOn(Schedulers.computation()) .observeOn(Schedulers.io()) .flatMap { n -> Single.just(n * n) } .observeOn(Schedulers.computation()) .flatMap { n -> Single.just(n - 1) } .observeOn(AndroidSchedulers.mainThread()) .subscribe({ println("Finished with result $it") }, { println("Failed with error $it") }) Computation
  • 173. @manuelvicnt #DroidconBos T H R E A D I N G Single.zip( Single.just(3), Single.just(4), BiFunction<Int, Int, Int> { n1, n2 -> n1 + n2 } ) .subscribeOn(Schedulers.computation()) .observeOn(Schedulers.io()) .flatMap { n -> Single.just(n * n) } .observeOn(Schedulers.computation()) .flatMap { n -> Single.just(n - 1) } .observeOn(AndroidSchedulers.mainThread()) .subscribe({ println("Finished with result $it") }, { println("Failed with error $it") })
  • 174. @manuelvicnt #DroidconBos T H R E A D I N G Single.zip( Single.just(3), Single.just(4), BiFunction<Int, Int, Int> { n1, n2 -> n1 + n2 } ) .subscribeOn(Schedulers.computation()) .observeOn(Schedulers.io()) .flatMap { n -> Single.just(n * n) } .observeOn(Schedulers.computation()) .flatMap { n -> Single.just(n - 1) } .observeOn(AndroidSchedulers.mainThread()) .subscribe({ println("Finished with result $it") }, { println("Failed with error $it") }) Computation
  • 175. @manuelvicnt #DroidconBos T H R E A D I N G Single.zip( Single.just(3), Single.just(4), BiFunction<Int, Int, Int> { n1, n2 -> n1 + n2 } ) .subscribeOn(Schedulers.computation()) .observeOn(Schedulers.io()) .flatMap { n -> Single.just(n * n) } .observeOn(Schedulers.computation()) .flatMap { n -> Single.just(n - 1) } .observeOn(AndroidSchedulers.mainThread()) .subscribe({ println("Finished with result $it") }, { println("Failed with error $it") }) Computation
  • 176. @manuelvicnt #DroidconBos T H R E A D I N G Single.zip( Single.just(3), Single.just(4), BiFunction<Int, Int, Int> { n1, n2 -> n1 + n2 } ) .subscribeOn(Schedulers.computation()) .observeOn(Schedulers.io()) .flatMap { n -> Single.just(n * n) } .observeOn(Schedulers.computation()) .flatMap { n -> Single.just(n - 1) } .observeOn(AndroidSchedulers.mainThread()) .subscribe({ println("Finished with result $it") }, { println("Failed with error $it") }) Computation Io
  • 177. @manuelvicnt #DroidconBos T H R E A D I N G Single.zip( Single.just(3), Single.just(4), BiFunction<Int, Int, Int> { n1, n2 -> n1 + n2 } ) .subscribeOn(Schedulers.computation()) .observeOn(Schedulers.io()) .flatMap { n -> Single.just(n * n) } .observeOn(Schedulers.computation()) .flatMap { n -> Single.just(n - 1) } .observeOn(AndroidSchedulers.mainThread()) .subscribe({ println("Finished with result $it") }, { println("Failed with error $it") }) Computation Io
  • 178. @manuelvicnt #DroidconBos T H R E A D I N G Single.zip( Single.just(3), Single.just(4), BiFunction<Int, Int, Int> { n1, n2 -> n1 + n2 } ) .subscribeOn(Schedulers.computation()) .observeOn(Schedulers.io()) .flatMap { n -> Single.just(n * n) } .observeOn(Schedulers.computation()) .flatMap { n -> Single.just(n - 1) } .observeOn(AndroidSchedulers.mainThread()) .subscribe({ println("Finished with result $it") }, { println("Failed with error $it") }) Computation Io
  • 179. @manuelvicnt #DroidconBos T H R E A D I N G Single.zip( Single.just(3), Single.just(4), BiFunction<Int, Int, Int> { n1, n2 -> n1 + n2 } ) .subscribeOn(Schedulers.computation()) .observeOn(Schedulers.io()) .flatMap { n -> Single.just(n * n) } .observeOn(Schedulers.computation()) .flatMap { n -> Single.just(n - 1) } .observeOn(AndroidSchedulers.mainThread()) .subscribe({ println("Finished with result $it") }, { println("Failed with error $it") }) Computation Io
  • 180. @manuelvicnt #DroidconBos T H R E A D I N G Single.zip( Single.just(3), Single.just(4), BiFunction<Int, Int, Int> { n1, n2 -> n1 + n2 } ) .subscribeOn(Schedulers.computation()) .observeOn(Schedulers.io()) .flatMap { n -> Single.just(n * n) } .observeOn(Schedulers.computation()) .flatMap { n -> Single.just(n - 1) } .observeOn(AndroidSchedulers.mainThread()) .subscribe({ println("Finished with result $it") }, { println("Failed with error $it") }) Computation Io Main Thread
  • 181. @manuelvicnt #DroidconBos T H R E A D I N G I N C O R O U T I N E S
  • 182. @manuelvicnt #DroidconBos T H R E A D I N G I N C O R O U T I N E S • It is defined in the CoroutineContext with a value for the key ContinuationInterceptor
  • 183. @manuelvicnt #DroidconBos T H R E A D I N G I N C O R O U T I N E S • It is defined in the CoroutineContext with a value for the key ContinuationInterceptor • Specified with a CoroutineDispatcher • Specific Thread • Thread Pool
  • 184. @manuelvicnt #DroidconBos T H R E A D I N G I N C O R O U T I N E S • Some values: • CommonPool • UI (Android) • Unconfined
  • 185. @manuelvicnt #DroidconBos T H R E A D I N G I N C O R O U T I N E S
  • 186. @manuelvicnt #DroidconBos T H R E A D I N G I N C O R O U T I N E S • Create your own ThreadPoolDispatcher
  • 187. @manuelvicnt #DroidconBos T H R E A D I N G I N C O R O U T I N E S • Create your own ThreadPoolDispatcher • NewSingleThreadContext val coroutineDispatcher = newSingleThreadContext("Name")
  • 188. @manuelvicnt #DroidconBos T H R E A D I N G I N C O R O U T I N E S • Create your own ThreadPoolDispatcher • NewSingleThreadContext • NewFixedThreadPoolContext ••val coroutineDispatcher = newFixedThreadPoolContext(4, "Name") val coroutineDispatcher = newSingleThreadContext("Name")
  • 189. @manuelvicnt #DroidconBos T H R E A D I N G launch(CommonPool) { val deferred1 = async(coroutineContext) { 3 } val deferred2 = async(coroutineContext) { 4 } var result = deferred1.await() + deferred2.await() launch(newSingleThreadContext("CustomThread")) { result = result * result - 1 }.join() withContext(UI) { println("Finished with result $result") } }
  • 190. @manuelvicnt #DroidconBos T H R E A D I N G launch(CommonPool) { val deferred1 = async(coroutineContext) { 3 } val deferred2 = async(coroutineContext) { 4 } var result = deferred1.await() + deferred2.await() launch(newSingleThreadContext("CustomThread")) { result = result * result - 1 }.join() withContext(UI) { println("Finished with result $result") } }
  • 191. @manuelvicnt #DroidconBos T H R E A D I N G launch(CommonPool) { val deferred1 = async(coroutineContext) { 3 } val deferred2 = async(coroutineContext) { 4 } var result = deferred1.await() + deferred2.await() launch(newSingleThreadContext("CustomThread")) { result = result * result - 1 }.join() withContext(UI) { println("Finished with result $result") } }
  • 192. @manuelvicnt #DroidconBos T H R E A D I N G launch(CommonPool) { val deferred1 = async(coroutineContext) { 3 } val deferred2 = async(coroutineContext) { 4 } var result = deferred1.await() + deferred2.await() launch(newSingleThreadContext("CustomThread")) { result = result * result - 1 }.join() withContext(UI) { println("Finished with result $result") } } CommonPool
  • 193. @manuelvicnt #DroidconBos T H R E A D I N G launch(CommonPool) { val deferred1 = async(coroutineContext) { 3 } val deferred2 = async(coroutineContext) { 4 } var result = deferred1.await() + deferred2.await() launch(newSingleThreadContext("CustomThread")) { result = result * result - 1 }.join() withContext(UI) { println("Finished with result $result") } } CommonPool
  • 194. @manuelvicnt #DroidconBos T H R E A D I N G launch(CommonPool) { val deferred1 = async(coroutineContext) { 3 } val deferred2 = async(coroutineContext) { 4 } var result = deferred1.await() + deferred2.await() launch(newSingleThreadContext("CustomThread")) { result = result * result - 1 }.join() withContext(UI) { println("Finished with result $result") } } CommonPool CustomThread
  • 195. @manuelvicnt #DroidconBos T H R E A D I N G launch(CommonPool) { val deferred1 = async(coroutineContext) { 3 } val deferred2 = async(coroutineContext) { 4 } var result = deferred1.await() + deferred2.await() launch(newSingleThreadContext("CustomThread")) { result = result * result - 1 }.join() withContext(UI) { println("Finished with result $result") } } CommonPool CustomThread
  • 196. @manuelvicnt #DroidconBos T H R E A D I N G launch(CommonPool) { val deferred1 = async(coroutineContext) { 3 } val deferred2 = async(coroutineContext) { 4 } var result = deferred1.await() + deferred2.await() launch(newSingleThreadContext("CustomThread")) { result = result * result - 1 }.join() withContext(UI) { println("Finished with result $result") } } CommonPool CustomThread UI
  • 197. M AT H A P P M V I A R C H I T E C T U R E
  • 198.
  • 199. @manuelvicnt #DroidconBos M AT H A P P • Three Projects. Implemented with: • Coroutines • RxJava • Coroutines/RxJava Interop
  • 200. @manuelvicnt #DroidconBos M AT H A P P • MVI • Architecture Components ViewModels • Survives Configuration Changes
  • 201. R E S T O R E S TAT E
  • 202. @manuelvicnt #DroidconBos M V I A R C H I T E C T U R E V I E W V I E W M O D E L 1. User Input 2. User Action 3. Process Result4. View State 5. Render State
  • 203. @manuelvicnt #DroidconBos M V I A R C H I T E C T U R E V I E W V I E W M O D E L 1. User Input 2. User Action 3. Process Result4. View State 5. Render State ?
  • 204. @manuelvicnt #DroidconBos M V I A R C H I T E C T U R E V I E W V I E W M O D E L 1. User Input 2. User Action 3. Process Result4. View State 5. Render State ? ?
  • 205. @manuelvicnt #DroidconBos R X A R C H I T E C T U R E V I E W V I E W M O D E L 1. User Input 2. User Action S U B J E C T * S U B J E C T * 3. Process Result4. View State 5. Render State * Not optimal
  • 206. @manuelvicnt #DroidconBos R X A R C H I T E C T U R E V I E W V I E W M O D E L 2. User Action R X R E L AY O B S E R VA B L E 4. View State Exposes an Observable but implemented with a Subject so View cannot call onNext() RxRelay to avoid the View calling onComplete()
  • 207. @manuelvicnt #DroidconBos C O R O U T I N E S A R C H I T E C T U R E V I E W V I E W M O D E L 1. User Input 2. User Action A C T O R C B C * * ConflatedBroadcastChannel 3. Process Result4. View State 5. Render State
  • 208. @manuelvicnt #DroidconBos C O M M O N I M P L E M E N TAT I O N sealed class MainUserAction { class Calculate( val number: Long ) : MainUserAction() class FunFactEnabled( val enabled: Boolean ) : MainUserAction() }
  • 209. @manuelvicnt #DroidconBos C O M M O N I M P L E M E N TAT I O N sealed class MainViewState { object Loading : MainViewState() class Rendered( val fibonacciNumber: Long, val funFact: String ) : MainViewState() object WrongInputError : MainViewState() object RequestError : MainViewState() }
  • 210. @manuelvicnt #DroidconBos V I E W M O D E L • Extends from ViewModel in Architecture Components • Receives User Actions • Processes User Action • Notifies View with the ViewState
  • 211. @manuelvicnt #DroidconBos R X V I E W M O D E L
  • 212. @manuelvicnt #DroidconBos R X V I E W M O D E L • Receives User Actions val userActionSubject: PublishSubject<MainUserAction>
  • 213. @manuelvicnt #DroidconBos R X V I E W M O D E L • Receives User Actions • Notifies View with the ViewState val viewStateSubject: BehaviorSubject<MainViewState> val userActionSubject: PublishSubject<MainUserAction>
  • 214. @manuelvicnt #DroidconBos R X V I E W M O D E L init { userActionSubject .subscribeOn(Schedulers.computation()) .subscribe({ when (it) { is MainUserAction.Calculate -> { if (it.number <= 0) { viewStateSubject. onNext(MainViewState.WrongInputError) } else { viewStateSubject.onNext(MainViewState.Loading) processCalculation(it) } } is MainUserAction.FunFactEnabled -> { askForFunFact = it.enabled } } }) }
  • 215. @manuelvicnt #DroidconBos R X V I E W M O D E L init { userActionSubject .subscribeOn(Schedulers.computation()) .subscribe({ when (it) { is MainUserAction.Calculate -> { if (it.number <= 0) { viewStateSubject. onNext(MainViewState.WrongInputError) } else { viewStateSubject.onNext(MainViewState.Loading) processCalculation(it) } } is MainUserAction.FunFactEnabled -> { askForFunFact = it.enabled } } }) }
  • 216. @manuelvicnt #DroidconBos R X V I E W M O D E L init { userActionSubject .subscribeOn(Schedulers.computation()) .subscribe({ when (it) { is MainUserAction.Calculate -> { if (it.number <= 0) { viewStateSubject. onNext(MainViewState.WrongInputError) } else { viewStateSubject.onNext(MainViewState.Loading) processCalculation(it) } } is MainUserAction.FunFactEnabled -> { askForFunFact = it.enabled } } }) }
  • 217. @manuelvicnt #DroidconBos R X V I E W M O D E L init { userActionSubject .subscribeOn(Schedulers.computation()) .subscribe({ when (it) { is MainUserAction.Calculate -> { if (it.number <= 0) { viewStateSubject. onNext(MainViewState.WrongInputError) } else { viewStateSubject.onNext(MainViewState.Loading) processCalculation(it) } } is MainUserAction.FunFactEnabled -> { askForFunFact = it.enabled } } }) }
  • 218. @manuelvicnt #DroidconBos R X V I E W M O D E L init { userActionSubject .subscribeOn(Schedulers.computation()) .subscribe({ when (it) { is MainUserAction.Calculate -> { if (it.number <= 0) { viewStateSubject. onNext(MainViewState.WrongInputError) } else { viewStateSubject.onNext(MainViewState.Loading) processCalculation(it) } } is MainUserAction.FunFactEnabled -> { askForFunFact = it.enabled } } }) }
  • 219. @manuelvicnt #DroidconBos • Clean up when it’s not longer needed R X V I E W M O D E L override fun onCleared() { userActionSubject.onComplete() viewStateSubject.onComplete() super.onCleared() }
  • 220. @manuelvicnt #DroidconBos C O R O U T I N E S V I E W M O D E L
  • 221. @manuelvicnt #DroidconBos C O R O U T I N E S V I E W M O D E L • Receives User Actions val userActionActor = actor<MainUserAction>(CommonPool, parent = parentJob)
  • 222. @manuelvicnt #DroidconBos C O R O U T I N E S V I E W M O D E L • Receives User Actions • Notifies View with the ViewState val viewStateChannel = ConflatedBroadcastChannel<MainViewState>() val userActionActor = actor<MainUserAction>(CommonPool, parent = parentJob)
  • 223. @manuelvicnt #DroidconBos C O R O U T I N E S V I E W M O D E L private val parentJob = Job() val userActionActor = actor<MainUserAction>( CommonPool, parent = parentJob ) { for (msg in channel) { // iterate over incoming messages when (msg) { is MainUserAction.Calculate -> { if (msg.number <= 0) { viewStateChannel.offer(MainViewState.WrongInputError) } else { viewStateChannel.offer(MainViewState.Loading) processCalculation(msg) } } is MainUserAction.FunFactEnabled -> { askForFunFact = msg.enabled } } } }
  • 224. @manuelvicnt #DroidconBos C O R O U T I N E S V I E W M O D E L private val parentJob = Job() val userActionActor = actor<MainUserAction>( CommonPool, parent = parentJob ) { for (msg in channel) { // iterate over incoming messages when (msg) { is MainUserAction.Calculate -> { if (msg.number <= 0) { viewStateChannel.offer(MainViewState.WrongInputError) } else { viewStateChannel.offer(MainViewState.Loading) processCalculation(msg) } } is MainUserAction.FunFactEnabled -> { askForFunFact = msg.enabled } } } }
  • 225. @manuelvicnt #DroidconBos C O R O U T I N E S V I E W M O D E L private val parentJob = Job() val userActionActor = actor<MainUserAction>( CommonPool, parent = parentJob ) { for (msg in channel) { // iterate over incoming messages when (msg) { is MainUserAction.Calculate -> { if (msg.number <= 0) { viewStateChannel.offer(MainViewState.WrongInputError) } else { viewStateChannel.offer(MainViewState.Loading) processCalculation(msg) } } is MainUserAction.FunFactEnabled -> { askForFunFact = msg.enabled } } } }
  • 226. @manuelvicnt #DroidconBos C O R O U T I N E S V I E W M O D E L private val parentJob = Job() val userActionActor = actor<MainUserAction>( CommonPool, parent = parentJob ) { for (msg in channel) { // iterate over incoming messages when (msg) { is MainUserAction.Calculate -> { if (msg.number <= 0) { viewStateChannel.offer(MainViewState.WrongInputError) } else { viewStateChannel.offer(MainViewState.Loading) processCalculation(msg) } } is MainUserAction.FunFactEnabled -> { askForFunFact = msg.enabled } } } }
  • 227. @manuelvicnt #DroidconBos C O R O U T I N E S V I E W M O D E L private val parentJob = Job() val userActionActor = actor<MainUserAction>( CommonPool, parent = parentJob ) { for (msg in channel) { // iterate over incoming messages when (msg) { is MainUserAction.Calculate -> { if (msg.number <= 0) { viewStateChannel.offer(MainViewState.WrongInputError) } else { viewStateChannel.offer(MainViewState.Loading) processCalculation(msg) } } is MainUserAction.FunFactEnabled -> { askForFunFact = msg.enabled } } } }
  • 228. @manuelvicnt #DroidconBos • Clean up when it’s not longer needed C O R O U T I N E S V I E W M O D E L override fun onCleared() { viewStateChannel.close() parentJob.cancel() // Cancels the Actor super.onCleared() }
  • 229. @manuelvicnt #DroidconBos V I E W • Listens for User Events • Talks to ViewModel to process Input • Renders ViewState
  • 230. @manuelvicnt #DroidconBos R X J AVA V I E W • Listens for User Events • Talks to ViewModel to process Input private fun setupViews() { calcButton.setOnClickListener { viewModel.userActionSubject.onNext( MainUserAction.Calculate( input.text.toString().toLong())) } funFact.setOnCheckedChangeListener { _, isChecked -> viewModel.userActionSubject.onNext( MainUserAction.FunFactEnabled(isChecked)) } }
  • 231. @manuelvicnt #DroidconBos R X J AVA V I E W • Listens for User Events • Talks to ViewModel to process Input private fun setupViews() { calcButton.setOnClickListener { viewModel.userActionSubject.onNext( MainUserAction.Calculate( input.text.toString().toLong())) } funFact.setOnCheckedChangeListener { _, isChecked -> viewModel.userActionSubject.onNext( MainUserAction.FunFactEnabled(isChecked)) } }
  • 232. @manuelvicnt #DroidconBos R X J AVA V I E W • Listens for User Events • Talks to ViewModel to process Input private fun setupViews() { calcButton.setOnClickListener { viewModel.userActionSubject.onNext( MainUserAction.Calculate( input.text.toString().toLong())) } funFact.setOnCheckedChangeListener { _, isChecked -> viewModel.userActionSubject.onNext( MainUserAction.FunFactEnabled(isChecked)) } }
  • 233. @manuelvicnt #DroidconBos R X J AVA V I E W private fun listenViewModel() { viewStateDisposable = viewModel.viewStateObservable .observeOn(AndroidSchedulers.mainThread()) .subscribe({ when (it) { MainViewState.Loading -> { progressBar.visibility = View.VISIBLE result.text = "Loading..." funFactText.text = "" } MainViewState.WrongInputError -> { showError() } … } }) }
  • 234. @manuelvicnt #DroidconBos R X J AVA V I E W • Renders ViewState private fun listenViewModel() { viewStateDisposable = viewModel.viewStateObservable .observeOn(AndroidSchedulers.mainThread()) .subscribe({ when (it) { MainViewState.Loading -> { progressBar.visibility = View.VISIBLE result.text = "Loading..." funFactText.text = "" } MainViewState.WrongInputError -> { showError() } … } }) }
  • 235. @manuelvicnt #DroidconBos R X J AVA V I E W private fun listenViewModel() { viewStateDisposable = viewModel.viewStateObservable .observeOn(AndroidSchedulers.mainThread()) .subscribe({ when (it) { MainViewState.Loading -> { progressBar.visibility = View.VISIBLE result.text = "Loading..." funFactText.text = "" } MainViewState.WrongInputError -> { showError() } … } }) }
  • 236. @manuelvicnt #DroidconBos R X J AVA V I E W • Renders ViewState private fun listenViewModel() { viewStateDisposable = viewModel.viewStateObservable .observeOn(AndroidSchedulers.mainThread()) .subscribe({ when (it) { MainViewState.Loading -> { progressBar.visibility = View.VISIBLE result.text = "Loading..." funFactText.text = "" } MainViewState.WrongInputError -> { showError() } … } }) }
  • 237. @manuelvicnt #DroidconBos R X J AVA V I E W private fun listenViewModel() { viewStateDisposable = viewModel.viewStateObservable .observeOn(AndroidSchedulers.mainThread()) .subscribe({ when (it) { MainViewState.Loading -> { progressBar.visibility = View.VISIBLE result.text = "Loading..." funFactText.text = "" } MainViewState.WrongInputError -> { showError() } … } }) }
  • 238. @manuelvicnt #DroidconBos R X J AVA V I E W • Renders ViewState private fun listenViewModel() { viewStateDisposable = viewModel.viewStateObservable .observeOn(AndroidSchedulers.mainThread()) .subscribe({ when (it) { MainViewState.Loading -> { progressBar.visibility = View.VISIBLE result.text = "Loading..." funFactText.text = "" } MainViewState.WrongInputError -> { showError() } … } }) }
  • 239. @manuelvicnt #DroidconBos R X J AVA V I E W override fun onStart() { super.onStart() listenViewModel() // Registers to ViewState Observable } override fun onStop() { if (viewStateDisposable?.isDisposed == false) { viewStateDisposable?.dispose() } super.onStop() }
  • 240. @manuelvicnt #DroidconBos R X J AVA V I E W • View Lifecycle override fun onStart() { super.onStart() listenViewModel() // Registers to ViewState Observable } override fun onStop() { if (viewStateDisposable?.isDisposed == false) { viewStateDisposable?.dispose() } super.onStop() }
  • 241. @manuelvicnt #DroidconBos C O R O U T I N E S V I E W • Listens for User Events • Talks to ViewModel to process Input private fun setupViews() { calcButton.setOnClickListener { viewModel.userActionActor.offer( MainUserAction.Calculate( input.text.toString().toLong())) } funFact.setOnCheckedChangeListener { _, isChecked -> viewModel.userActionActor.offer( MainUserAction.FunFactEnabled(isChecked)) } }
  • 242. @manuelvicnt #DroidconBos C O R O U T I N E S V I E W • Listens for User Events • Talks to ViewModel to process Input private fun setupViews() { calcButton.setOnClickListener { viewModel.userActionActor.offer( MainUserAction.Calculate( input.text.toString().toLong())) } funFact.setOnCheckedChangeListener { _, isChecked -> viewModel.userActionActor.offer( MainUserAction.FunFactEnabled(isChecked)) } }
  • 243. @manuelvicnt #DroidconBos C O R O U T I N E S V I E W • Listens for User Events • Talks to ViewModel to process Input private fun setupViews() { calcButton.setOnClickListener { viewModel.userActionActor.offer( MainUserAction.Calculate( input.text.toString().toLong())) } funFact.setOnCheckedChangeListener { _, isChecked -> viewModel.userActionActor.offer( MainUserAction.FunFactEnabled(isChecked)) } }
  • 244. @manuelvicnt #DroidconBos C O R O U T I N E S V I E W private val parentJob = Job() private fun listenViewModel() { // Launch on the CommonPool to not block the MainThread launch(parentJob + CommonPool) { viewModel.viewStateChannel.consumeEach { withContext(UI) { when (it) { MainViewState.Loading -> { progressBar.visibility = View.VISIBLE result.text = "Loading..." funFactText.text = "" } … } } } } }
  • 245. @manuelvicnt #DroidconBos C O R O U T I N E S V I E W • Renders ViewState private val parentJob = Job() private fun listenViewModel() { // Launch on the CommonPool to not block the MainThread launch(parentJob + CommonPool) { viewModel.viewStateChannel.consumeEach { withContext(UI) { when (it) { MainViewState.Loading -> { progressBar.visibility = View.VISIBLE result.text = "Loading..." funFactText.text = "" } … } } } } }
  • 246. @manuelvicnt #DroidconBos C O R O U T I N E S V I E W private val parentJob = Job() private fun listenViewModel() { // Launch on the CommonPool to not block the MainThread launch(parentJob + CommonPool) { viewModel.viewStateChannel.consumeEach { withContext(UI) { when (it) { MainViewState.Loading -> { progressBar.visibility = View.VISIBLE result.text = "Loading..." funFactText.text = "" } … } } } } }
  • 247. @manuelvicnt #DroidconBos C O R O U T I N E S V I E W • Renders ViewState private val parentJob = Job() private fun listenViewModel() { // Launch on the CommonPool to not block the MainThread launch(parentJob + CommonPool) { viewModel.viewStateChannel.consumeEach { withContext(UI) { when (it) { MainViewState.Loading -> { progressBar.visibility = View.VISIBLE result.text = "Loading..." funFactText.text = "" } … } } } } }
  • 248. @manuelvicnt #DroidconBos C O R O U T I N E S V I E W private val parentJob = Job() private fun listenViewModel() { // Launch on the CommonPool to not block the MainThread launch(parentJob + CommonPool) { viewModel.viewStateChannel.consumeEach { withContext(UI) { when (it) { MainViewState.Loading -> { progressBar.visibility = View.VISIBLE result.text = "Loading..." funFactText.text = "" } … } } } } }
  • 249. @manuelvicnt #DroidconBos C O R O U T I N E S V I E W • Renders ViewState private val parentJob = Job() private fun listenViewModel() { // Launch on the CommonPool to not block the MainThread launch(parentJob + CommonPool) { viewModel.viewStateChannel.consumeEach { withContext(UI) { when (it) { MainViewState.Loading -> { progressBar.visibility = View.VISIBLE result.text = "Loading..." funFactText.text = "" } … } } } } }
  • 250. @manuelvicnt #DroidconBos C O R O U T I N E S V I E W private val parentJob = Job() private fun listenViewModel() { // Launch on the CommonPool to not block the MainThread launch(parentJob + CommonPool) { viewModel.viewStateChannel.consumeEach { withContext(UI) { when (it) { MainViewState.Loading -> { progressBar.visibility = View.VISIBLE result.text = "Loading..." funFactText.text = "" } … } } } } }
  • 251. @manuelvicnt #DroidconBos C O R O U T I N E S V I E W • Renders ViewState private val parentJob = Job() private fun listenViewModel() { // Launch on the CommonPool to not block the MainThread launch(parentJob + CommonPool) { viewModel.viewStateChannel.consumeEach { withContext(UI) { when (it) { MainViewState.Loading -> { progressBar.visibility = View.VISIBLE result.text = "Loading..." funFactText.text = "" } … } } } } }
  • 252. @manuelvicnt #DroidconBos C O R O U T I N E S V I E W override fun onStart() { super.onStart() listenViewModel() // Listens to ViewStateChannel } override fun onStop() { parentJob.cancel() super.onStop() }
  • 253. @manuelvicnt #DroidconBos C O R O U T I N E S V I E W • View Lifecycle override fun onStart() { super.onStart() listenViewModel() // Listens to ViewStateChannel } override fun onStop() { parentJob.cancel() super.onStop() }
  • 254. S H O W M E S O M E N U M B E R S
  • 255. @manuelvicnt #DroidconBos P E R F O R M A N C E
  • 256. @manuelvicnt #DroidconBos P E R F O R M A N C E • Roughly the same • Not difficult enough to compare
  • 257. @manuelvicnt #DroidconBos S I Z E Measure Coroutines RxJava Interop APK Size 2.4MB 2.9MB 3.1MB Method Count 29,131 37,271 39,590
  • 258. @manuelvicnt #DroidconBos G I T H U B L I N K S manuelvicnt/MathCoroutines Coroutines manuelvicnt/MathRxJava RxJava manuelvicnt/MathRxCoroutines Coroutines and RxJava
  • 259. @manuelvicnt #DroidconBos C O N C L U S I O N
  • 260. @manuelvicnt #DroidconBos C O N C L U S I O N • Both libraries provide a way to do Asynchronous Programming • If you are a RxJava expert, no need to switch • If you struggle with RxJava, Coroutines is another option you can try • For new Android developers, Coroutines has a lower learning curve
  • 261. C O R O U T I N E S A N D R X J AVA A N A S Y N C H R O N I C I T Y C O M PA R I S O N @ M A N U E LV I C N T A N Y Q U E S T I O N S ? M A N U E L V I C E N T E V I V O