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
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()
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()
}
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>
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
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()
}
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
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
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)
}
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)
}
}
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)
}
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
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
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)
}
}
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)
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()
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
}
}
}
}
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
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
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
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
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
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()
}
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()
}
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
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