Slide 1

Slide 1 text

Akshay Chordiya Go with the Flow

Slide 2

Slide 2 text

Akshay Chordiya Go with the Flow

Slide 3

Slide 3 text

Coroutines Let’s rewind ⏪ a little bit

Slide 4

Slide 4 text

Synchronous code fun showUser() { val user = db.loadUser() // Blocks the thread render(user) }

Slide 5

Slide 5 text

Synchronous code fun showUser() { val user = db.loadUser() // Blocks the thread render(user) } loadUser

Slide 6

Slide 6 text

Synchronous code fun showUser() { val user = db.loadUser() // Blocks the thread render(user) } loadUser render

Slide 7

Slide 7 text

With Coroutines fun showUser() { val user = db.loadUser() render(user) }

Slide 8

Slide 8 text

With Coroutines suspend fun showUser() { val user = db.loadUser() // Background thread render(user) }

Slide 9

Slide 9 text

With Coroutines suspend fun showUser() { val user = db.loadUser() // Background thread render(user) }

Slide 10

Slide 10 text

With Coroutines suspend fun showUser() { val user = db.loadUser() // Background thread withContext(Dispatchers.Main) { render(user) // UI thread } }

Slide 11

Slide 11 text

With Coroutines suspend fun showUser() { val user = db.loadUser() // Background thread withContext(Dispatchers.Main) { render(user) // UI thread } } showUser

Slide 12

Slide 12 text

With Coroutines suspend fun showUser() { val user = db.loadUser() // Background thread withContext(Dispatchers.Main) { render(user) // UI thread } } showUser loadUser

Slide 13

Slide 13 text

With Coroutines suspend fun showUser() { val user = db.loadUser() // Background thread withContext(Dispatchers.Main) { render(user) // UI thread } } showUser loadUser render

Slide 14

Slide 14 text

A suspend function can only be called from another suspend function

Slide 15

Slide 15 text

A suspend function can only be called from another suspend function or a coroutine scope

Slide 16

Slide 16 text

Coroutine scope

Slide 17

Slide 17 text

Coroutine scope ~ Lifecycle scope

Slide 18

Slide 18 text

fun main() { lifecycleScope.launch { showUser() } } * available in lifecycle-ktx Jetpack library

Slide 19

Slide 19 text

fun main() { lifecycleScope.launch { showUser() } } CREATED DESTROYED

Slide 20

Slide 20 text

fun main() { lifecycleScope.launch { showUser() } }

Slide 21

Slide 21 text

“A coroutine scope is kinda a subscription management system associated to lifecycle ♻”

Slide 22

Slide 22 text

Structured Concurrency

Slide 23

Slide 23 text

Why Kotlin Flow?

Slide 24

Slide 24 text

Why Kotlin Flow? Kotlin Coroutine

Slide 25

Slide 25 text

RxKotlin or from scratch?

Slide 26

Slide 26 text

Me “At the end it’s important that any new library raises the bar compared to existing ones and is not just a replica with slight improvements”

Slide 27

Slide 27 text

So What’s special

Slide 28

Slide 28 text

What’s special ✨ Null safety in streams ✨ Interoperability between other reactive streams and coroutines ✨ Supports Kotlin multiplatform ✨ No special handling for back pressure ✨ Fewer and simple operators ✨ Perks of structured concurrency

Slide 29

Slide 29 text

What is Flow?

Slide 30

Slide 30 text

What is Flow? Based upon reactive stream specification Similar to a list / collection It has operators like map, switchMap, etc Built on top of coroutines It’s ❄ in nature

Slide 31

Slide 31 text

Basics

Slide 32

Slide 32 text

fun stream(): Flow = flow { emit("") // Emits the value upstream ☝ emit("⚽") emit("") }

Slide 33

Slide 33 text

fun stream(): Flow = flow { emit("") // Emits the value upstream ☝ emit("⚽") emit("") }

Slide 34

Slide 34 text

fun stream(): Flow = flow { emit("") // Emits the value upstream ☝ emit("⚽") emit("") }

Slide 35

Slide 35 text

fun stream(): Flow = flow { emit("") // Emits the value upstream ☝ emit("⚽") emit("") } ❄ ❄ ❄ ❄ ❄

Slide 36

Slide 36 text

fun stream(): Flow = flow { emit("") // Emits the value upstream ☝ emit("⚽") emit("") } Emitter Collector fun main() = runBlocking { stream().collect { value -> println(value) } }

Slide 37

Slide 37 text

fun stream(): Flow = flow { emit("") // Emits the value upstream ☝ emit("⚽") emit("") } Emitter Collector fun main() = runBlocking { stream().collect { value -> println(value) } } main() stream()

Slide 38

Slide 38 text

fun stream(): Flow = flow { emit("") // Emits the value upstream ☝ emit("⚽") emit("") } Emitter Collector fun main() = runBlocking { stream().collect { value -> println(value) } } main() stream()

Slide 39

Slide 39 text

fun stream(): Flow = flow { emit("") // Emits the value upstream ☝ emit("⚽") emit("") } Emitter Collector fun main() = runBlocking { stream().collect { value -> println(value) } } main() stream()

Slide 40

Slide 40 text

fun stream(): Flow = flow { emit("") // Emits the value upstream ☝ emit("⚽") emit("") } Emitter Collector fun main() = runBlocking { stream().collect { value -> println(value) } } main() stream()

Slide 41

Slide 41 text

fun stream(): Flow = flow { emit("") // Emits the value upstream ☝ emit("⚽") emit("") } Emitter Collector fun main() = runBlocking { stream().collect { value -> println(value) } } main() stream() collect

Slide 42

Slide 42 text

fun stream(): Flow = flow { emit("") // Emits the value upstream ☝ emit("⚽") emit("") } Emitter Collector fun main() = runBlocking { stream().collect { value -> println(value) } } main() stream() collect emit

Slide 43

Slide 43 text

fun stream(): Flow = flow { emit("") // Emits the value upstream ☝ emit("⚽") emit("") } Emitter Collector fun main() = runBlocking { stream().collect { value -> println(value) } } main() stream() collect emit

Slide 44

Slide 44 text

fun stream(): Flow = flow { emit("") // Emits the value upstream ☝ emit("⚽") emit("") } Emitter Collector fun main() = runBlocking { stream().collect { value -> println(value) } } main() stream() collect emit collect emit ⚽

Slide 45

Slide 45 text

fun stream(): Flow = flow { emit("") // Emits the value upstream ☝ emit("⚽") emit("") } Emitter Collector fun main() = runBlocking { stream().collect { value -> println(value) } } main() stream() collect emit collect emit ⚽ ⚽

Slide 46

Slide 46 text

fun stream(): Flow = flow { emit("") // Emits the value upstream ☝ emit("⚽") emit("") } Emitter Collector fun main() = runBlocking { stream().collect { value -> println(value) } } main() stream() collect emit collect emit ⚽ ⚽ ..

Slide 47

Slide 47 text

fun stream(): Flow = flow { emit("") // Emits the value upstream ☝ emit("⚽") emit("") } Emitter Collector fun main() = runBlocking { stream().collect { value -> println(value) } } main() stream() collect emit collect emit ⚽ ⚽ collect ..

Slide 48

Slide 48 text

Let’s dive deeper

Slide 49

Slide 49 text

public interface Flow { public suspend fun collect(collector: FlowCollector) } Collector

Slide 50

Slide 50 text

public interface Flow { public suspend fun collect(collector: FlowCollector) } Collector public interface FlowCollector { public suspend fun emit(value: T) } Emitter

Slide 51

Slide 51 text

public interface Flow { public suspend fun collect(collector: FlowCollector) } Collector public interface FlowCollector { public suspend fun emit(value: T) } Emitter

Slide 52

Slide 52 text

Flow ❤ Coroutines

Slide 53

Slide 53 text

fun loadFromDatabase(): String { // Heavy operation of fetching from DB return "Very heavy " }

Slide 54

Slide 54 text

suspend fun loadFromDatabase(): String { // Heavy operation of fetching from DB return "Very heavy " }

Slide 55

Slide 55 text

suspend fun loadFromDatabase(): String { // Heavy operation of fetching from DB return "Very heavy " } fun stream() = flow { val value = loadFromDatabase() emit(value) }

Slide 56

Slide 56 text

suspend fun loadFromDatabase(): String { // Heavy operation of fetching from DB return "Very heavy " } fun stream() = flow { val value = loadFromDatabase() emit(value) } public fun flow(@BuilderInference block: suspend FlowCollector.() -> Unit): Flow

Slide 57

Slide 57 text

Principles of Flow

Slide 58

Slide 58 text

fun main() = runBlocking { flow { emit("Context") } .flowOn(Dispatchers.IO) }

Slide 59

Slide 59 text

fun main() = runBlocking { flow { emit("Context") // IO thread } .flowOn(Dispatchers.IO) } ‐

Slide 60

Slide 60 text

fun main() = runBlocking { flow { emit("Context") // IO thread } .flowOn(Dispatchers.IO) .map { "$it Preservation" } }

Slide 61

Slide 61 text

Context Preservation

Slide 62

Slide 62 text

Observable.just("") .map{ /* IO thread */ } .subscribeOn(Schedulers.io()) .map{ /* IO thread */ } .subscribeOn(Schedulers.computation()) .map{ /* IO thread */ } RxJava example

Slide 63

Slide 63 text

Observable.just("") .map{ /* IO thread */ } .subscribeOn(Schedulers.io()) .map{ /* IO thread */ } .subscribeOn(Schedulers.computation()) .map{ /* IO thread */ } .observeOn(Schedulers.computation()) .map { /* */ } RxJava example

Slide 64

Slide 64 text

Exception Transparency ☢

Slide 65

Slide 65 text

flow { val value = loadFromDatabase() emit(value) }

Slide 66

Slide 66 text

flow { val value = loadFromDatabase() emit(value) } .catch { emit("Fallback value") }

Slide 67

Slide 67 text

flow { try { val value = loadFromDatabase() emit(value) } catch (e: Exception) { emit("Fallback value") } }

Slide 68

Slide 68 text

flow { try { val value = loadFromDatabase() emit(value) } catch (e: Exception) { when (e) { is CancellationException -> throw e else -> emit("Fallback value") } } }

Slide 69

Slide 69 text

flow { try { val value = loadFromDatabase() emit(value) } catch (e: IOException) { emit("Fallback value") } }

Slide 70

Slide 70 text

flow { val value = loadFromDatabase() emit(value) } .catch { emit("Fallback value") } .collect { }

Slide 71

Slide 71 text

sealed class State { object Success: State() object Failure: State() } fun main() = runBlocking { flow { emit(State.Success) } .catch { emit(State.Failure) } .collect { state -> when (state) { State.Success -> println("Success") State.Failure -> println("Failure") } } } x Example

Slide 72

Slide 72 text

“- Use suspend for one-shot operations like insert operation or a network operation - Use Flow to get stream of data like getting updates on data changes in the database.”

Slide 73

Slide 73 text

“- Use suspend for one-shot operations like insert operation or a network operation - Use Flow to get stream of data like getting updates on data changes in the database.”

Slide 74

Slide 74 text

“- Use suspend for one-shot operations like insert operation or a network operation - Use Flow to get stream of data like getting updates on data changes in the database.”

Slide 75

Slide 75 text

Channels

Slide 76

Slide 76 text

Channels are communication primitives

Slide 77

Slide 77 text

Channels are communication primitives Source

Slide 78

Slide 78 text

Source Channels are communication primitives

Slide 79

Slide 79 text

Channels are hot in nature

Slide 80

Slide 80 text

Channels are low level primitives

Slide 81

Slide 81 text

private val searchQueryChannel = ConflatedBroadcastChannel()

Slide 82

Slide 82 text

private val searchQueryChannel = ConflatedBroadcastChannel()

Slide 83

Slide 83 text

private val searchQueryChannel = ConflatedBroadcastChannel()

Slide 84

Slide 84 text

// On text change searchQueryChannel.send("Bob the builder ") private val searchQueryChannel = ConflatedBroadcastChannel()

Slide 85

Slide 85 text

// On text change searchQueryChannel.send("Bob the builder ") private val searchQueryChannel = ConflatedBroadcastChannel() searchQueryChannel .asFlow() /* Get the results for the search query */ .distinctUntilChanged() .collect { // Render the result on UI }

Slide 86

Slide 86 text

StateFlow

Slide 87

Slide 87 text

private val searchQueryFlow = MutableStateFlow("")

Slide 88

Slide 88 text

private val searchQueryFlow = MutableStateFlow("") // On text change searchQueryFlow.value = "Bob the builder "

Slide 89

Slide 89 text

private val searchQueryFlow = MutableStateFlow("") // On text change searchQueryFlow.value = "Bob the builder " searchQueryFlow /* Get the results for the search query */ .collect { // Render the result on UI }

Slide 90

Slide 90 text

private val searchQueryFlow = MutableStateFlow("") // On text change searchQueryFlow.value = "Bob the builder " searchQueryFlow /* Get the results for the search query */ .distinctUntilChanged() .collect { // Render the result on UI }

Slide 91

Slide 91 text

Legacy ➡ Flow

Slide 92

Slide 92 text

Setup migration process

Slide 93

Slide 93 text

Discuss with your team

Slide 94

Slide 94 text

Discuss with your team Setup a meeting

Slide 95

Slide 95 text

Discuss with your team Setup a meeting Getting comfortable

Slide 96

Slide 96 text

Discuss with your team Setup a meeting Getting comfortable Evaluate

Slide 97

Slide 97 text

Discuss with your team Setup a meeting Getting comfortable Evaluate Set the common goal

Slide 98

Slide 98 text

Setup foundation ⛩

Slide 99

Slide 99 text

Setup foundation ⛩ Setup guidelines

Slide 100

Slide 100 text

Setup foundation ⛩ Setup guidelines Setup the necessary base classes

Slide 101

Slide 101 text

Setup foundation ⛩ Setup guidelines Setup the necessary base classes Setup the testing framework

Slide 102

Slide 102 text

Setup foundation ⛩ Setup guidelines Setup the necessary base classes Setup the testing framework Expose common features as Flow

Slide 103

Slide 103 text

Expose common features as Flow interface UserRepository { // Legacy function fun observeUser(): Observable }

Slide 104

Slide 104 text

Expose common features as Flow interface UserRepository { // Legacy function fun observeUser(): Observable // Flow equivalent of [observeUser]. fun getUser(): Flow }

Slide 105

Slide 105 text

Expose common features as Flow interface UserRepository { // Legacy function @Deprecated( message = "Use the flow equivalent -> getUser()", replaceWith = ReplaceWith("getUser()", "") ) fun observeUser(): Observable // Flow equivalent of [observeUser]. fun getUser(): Flow } ☠

Slide 106

Slide 106 text

Setup safeguards ☢

Slide 107

Slide 107 text

Setup safeguards ☢ • Ensuring that the new code is written using Kotlin Coroutines while doing the PR reviews • Ensuring when the legacy code is touched, the developer has tried to convert it coroutines or flow • Monthly or quarterly checking of progress of the gradual migration • And other ideas which will pop in your brilliant minds

Slide 108

Slide 108 text

Interoperability

Slide 109

Slide 109 text

fun updateOnDocumentChange(document: Document) { val listener = Document.ChangeListener { updateUI(it) } document.addChangeListener(listener) }

Slide 110

Slide 110 text

fun updateOnDocumentChange(document: Document) { val listener = Document.ChangeListener { updateUI(it) } document.addChangeListener(listener) } removeChangeListener(listener)

Slide 111

Slide 111 text

fun Document.awaitChange(): Flow = callbackFlow { }

Slide 112

Slide 112 text

fun Document.awaitChange(): Flow = callbackFlow { val listener = Document.ChangeListener { } addChangeListener(listener) }

Slide 113

Slide 113 text

fun Document.awaitChange(): Flow = callbackFlow { val listener = Document.ChangeListener { offer(it) } addChangeListener(listener) }

Slide 114

Slide 114 text

fun Document.awaitChange(): Flow = callbackFlow { val listener = Document.ChangeListener { offer(it) } addChangeListener(listener) awaitClose { removeChangeListener(listener) } }

Slide 115

Slide 115 text

/** * Listen for changes in the [Document] and notify the * receivers about changes. */ fun Document.awaitChange(): Flow = callbackFlow { val listener = Document.ChangeListener { offer(it) } addChangeListener(listener) awaitClose { removeChangeListener(listener) } }

Slide 116

Slide 116 text

Rx ➡ Flow

Slide 117

Slide 117 text

Reactive Stream Specification Flow Rx

Slide 118

Slide 118 text

https://github.com/Kotlin/kotlinx.coroutines/tree/master/reactive/kotlinx-coroutines-rx2 fun flow(): Flow { return Observable.just("a") .asFlow() }

Slide 119

Slide 119 text

fun observable(): Observable { return flow { emit("a") }.asObservable() } https://github.com/Kotlin/kotlinx.coroutines/tree/master/reactive/kotlinx-coroutines-rx2 fun flow(): Flow { return Observable.just("a") .asFlow() }

Slide 120

Slide 120 text

Rx Coroutines Single / Completable / Maybe suspend function Observable Flow Subject Channel Schedulers Dispatchers Disposables Scopes subscribe collect Terminology between Rx and Coroutine world

Slide 121

Slide 121 text

Fun part

Slide 122

Slide 122 text

public fun ObservableSource.asFlow(): Flow = callbackFlow { val observer = object : Observer { override fun onComplete() { close() } override fun onSubscribe(d: Disposable) { .. } override fun onNext(t: T) { sendBlocking(t) } override fun onError(e: Throwable) { close(e) } } subscribe(observer) awaitClose { /* Dispose */ } }

Slide 123

Slide 123 text

Rx1 ➡ Flow https://github.com/AkshayChordiya/kotlinx.coroutines-rx1

Slide 124

Slide 124 text

import rx.Observable fun observable(): Observable { return flow { emit("a") }.asObservable() } import rx.Observable fun flow(): Flow { return Observable.just("a") .asFlow() } https://github.com/AkshayChordiya/kotlinx.coroutines-rx1

Slide 125

Slide 125 text

Testing

Slide 126

Slide 126 text

@RunWith(JUnit4::class) class TestingWithFlow { fun getFlow() = flow { emit("Bob the builder ") } }

Slide 127

Slide 127 text

@RunWith(JUnit4::class) class TestingWithFlow { fun getFlow() = flow { emit("Bob the builder ") } @Test fun testFlow() = runBlocking { } }

Slide 128

Slide 128 text

@RunWith(JUnit4::class) class TestingWithFlow { fun getFlow() = flow { emit("Bob the builder ") } @Test fun testFlow() = runBlocking { val expected = listOf("Bob the builder ") val actualList = getFlow().take(1).toList() // Assert the list assertEquals(expected, actualList) } }

Slide 129

Slide 129 text

@RunWith(JUnit4::class) class TestingWithFlow { fun getFlow() = flow { emit("Bob the builder ") } @Test fun testFlow() = runBlocking { val expected = listOf("Bob the builder ") val actualList = getFlow().take(1).toList() // Assert the list assertEquals(expected, actualList) } }

Slide 130

Slide 130 text

@RunWith(JUnit4::class) class TestingWithFlow { fun getFlow() = flow { emit("Bob the builder ") } @Test fun testFlow() = runBlocking { val expected = listOf("Bob the builder ") val actualList = getFlow().take(1).toList() // Assert the list assertEquals(expected, actualList) } }

Slide 131

Slide 131 text

/** * Asserts only the [expected] items by just taking that many from the stream */ suspend fun Flow.assertItems(vararg expected: T) { assertEquals(expected.toList(), this.take(expected.size).toList()) }

Slide 132

Slide 132 text

@RunWith(JUnit4::class) class TestingWithFlow { fun getFlow() = flow { emit("Bob the builder ") } @Test fun testFlow() = runBlocking { val expected = "Bob the builder " // Assert the list getFlow().assertItems(expected) } }

Slide 133

Slide 133 text

Conclusion

Slide 134

Slide 134 text

Conclusion • Revisited coroutines • Why Flow? And what makes it special? • Basics of Flow • Principles of Flow • Legacy code -> Flow • Testing • Pro-tips along the way

Slide 135

Slide 135 text

Me “Kotlin Flow raises the bar and introduces easy reactive streaming, that said it’s work in progress and lots of exciting things are coming sooooooon ” Final thoughts

Slide 136

Slide 136 text

Further resources • News App https://github.com/AkshayChordiya/News • Go with the Kotlin Flow https://medium.com/google-developer-experts/go-with-the- kotlin-flow-7067564665a3 • Migrating from legacy code to Kotlin Flow https://medium.com/@aky/migrating-legacy-code-to-kotlin- flow-3f3e1854925

Slide 137

Slide 137 text

Thank You https://bit.ly/2Favsdn