Slide 1

Slide 1 text

Akshay Chordiya Go with the Flow @Akshay_Chordiya Hey everyone, welcome and thank you for joining. It’s great to be here at Android Summit virtually! I’m Akshay Chordiya, I’m based in Berlin, Germany. I work as Android Developer at Clue which is a female health company. I’m also Google Developer Expert for Android.

Slide 2

Slide 2 text

Akshay Chordiya Go with the Flow @Akshay_Chordiya Today we are going to talk about something which many people are excited about i.e Kotlin Flow

Slide 3

Slide 3 text

Coroutines Let’s rewind ⏪ a little bit Let’s rewind a little bit and look at what Kotlin gave us - coroutines. They simplify asynchronous programming in general and especially on Android

Slide 4

Slide 4 text

Synchronous code fun showUser() { val user = db.loadUser() // Blocks the thread render(user) } Here is an example of synchronous function called `showUser` which internally loads the user from the database.

Slide 5

Slide 5 text

Synchronous code fun showUser() { val user = db.loadUser() // Blocks the thread render(user) } loadUser When this function calls `loadUser` it blocks the current thread and waits till user is loaded from the database, this blocking is bad because at this moment the whole UI is frozen -> making the user unhappy

Slide 6

Slide 6 text

Synchronous code fun showUser() { val user = db.loadUser() // Blocks the thread render(user) } loadUser render And then once the data is loaded from database the `render` function is called which renders the user data on the UI. There are multiple ways to tackle this. Let’s look at how we can convert this function to use coroutine

Slide 7

Slide 7 text

With Coroutines fun showUser() { val user = db.loadUser() render(user) } Let’s start by taking the original synchronous function

Slide 8

Slide 8 text

With Coroutines suspend fun showUser() { val user = db.loadUser() // Background thread render(user) } First we need to mark the `showUser` function with suspend keyword, of course we will need to mark the `loadUser` as suspend too since we also want want it to run on background thread. You can see that just by marking it as suspend the code will run in background thread

Slide 9

Slide 9 text

With Coroutines suspend fun showUser() { val user = db.loadUser() // Background thread render(user) } But there is a problem the `render` function needs to be called from UI thread since its touching the view hierarchy.

Slide 10

Slide 10 text

With Coroutines suspend fun showUser() { val user = db.loadUser() // Background thread withContext(Dispatchers.Main) { render(user) // UI thread } } We can use the `withContext` function which allows to switch the thread inside the suspending function and change the thread to main and call `render` function, and that’s it!

Slide 11

Slide 11 text

With Coroutines suspend fun showUser() { val user = db.loadUser() // Background thread withContext(Dispatchers.Main) { render(user) // UI thread } } showUser This is how it will work, we start by calling the `showUser` function, since it’s marked as suspend it will switch to a background thread or the thread specified while launching the coroutine

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 and then the user will be loaded from the database on a different thread while the main thread continues to go ahead without waiting for anything i.e not freezing or blocking the user experience

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 And then it will switch back to main thread because of `withContext` and then call the `render` function on the Main / UI thread

Slide 14

Slide 14 text

A suspend function can only be called from another suspend function One key thing to remember is 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 Or from a coroutine scope,

Slide 16

Slide 16 text

Coroutine scope A coroutine scope represents the scope of the coroutines, each coroutine needs a scope to run in and when the scope is destroyed all the running jobs inside the scope are also stopped or cancelled

Slide 17

Slide 17 text

Coroutine scope ~ Lifecycle scope This scope is generally associated to the lifecycle scope for simplicity. For instance, on Android each activity has a lifecycle. Let’s look at example to help understand this better

Slide 18

Slide 18 text

fun main() { lifecycleScope.launch { showUser() } } * available in lifecycle-ktx Jetpack library For example, in order to call our `showUser` suspend function from a normal function we would need a coroutine scope. On Android these scopes are already implemented for us like `lifecycleScope` which is part of `lifecycle-ktx` library from Jetpack components. it is provided to use coroutine function in Activity or Fragment. It’s a coroutine scope which is tied to the lifecycle of activity or fragment

Slide 19

Slide 19 text

fun main() { lifecycleScope.launch { showUser() } } CREATED DESTROYED This provides the magic of automatically clearing or cancelling all the jobs running in the specified scope when the activity or fragment is destroyed

Slide 20

Slide 20 text

fun main() { lifecycleScope.launch { showUser() } } The launch function is used to start the coroutine, it will immediately start the coroutine That means coroutines are hot in nature, this also one of the reason why coroutines are not replacement for reactive streams

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

Structured Concurrency This concept is called as Structured Concurrency and this is what makes Kotlin coroutines so powerful and also helps developers prevent leaking the job / subscription

Slide 23

Slide 23 text

Why Kotlin Flow? Let’s come to the important question, why Kotlin Flow?

Slide 24

Slide 24 text

Why Kotlin Flow? Kotlin Coroutine With growing usage of Kotlin and the love for Kotlin coroutines, people expressed a lot of interest in having a pure Kotlin implementation of RxJava to leverage all the amazing features which Kotlin provides

Slide 25

Slide 25 text

RxKotlin or from scratch? But the confusion was creating some sort of magic on top of RxJava to leverage Kotlin or to build a whole new library from scratch. In case if you don’t know RxKotlin does exist but it cannot really leverage all the Kotlin features.

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” To me it’s important that any new library raises the bar compared to any existing libraries out there and is not just a replica with slight improvements. And Kotlin Flow does raise the bar

Slide 27

Slide 27 text

So What’s special So let’s look at what’s special in Kotlin Flow to raise the bar

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 • Flow provides null safety in the stream that means you cannot send null values in the stream, this is not something new; RxJava 2 has it too • This is the key thing, Flow provides easy APIs to convert Flow to any other reactive stream like RxJava Flowable and the reverse. Also you can combine Flow and coroutines, we will see this later • You can actually use Kotlin coroutines and Flow on any of the supported Kotlin platforms like JVM, JS and native + use it the common code or shared code for Kotlin multiplatform projects • There is no special handling for back pressure, there are literally no operators for handling back pressure cause this is magically handled by the suspending nature of coroutines • There are fewer operators which directly correlates to ease of understanding, this is because of 2 reasons • Kotlin’s extension function magic • Suspending nature of coroutines allow having same operator for synchronous / asynchronous code which cuts down the number of operators •

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 Let’s look at some of the basics of Flow

Slide 32

Slide 32 text

fun stream(): Flow = flow { emit("") // Emits the value upstream ☝ emit("⚽") emit("") } Essentially there are 2 components i.e emitter and collector where emitter emits new values and collector receives the value. Here is an example of an emitter.

Slide 33

Slide 33 text

fun stream(): Flow = flow { emit("") // Emits the value upstream ☝ emit("⚽") emit("") } Which returns Flow of string very similar to how you would return a list of string

Slide 34

Slide 34 text

fun stream(): Flow = flow { emit("") // Emits the value upstream ☝ emit("⚽") emit("") } There are various ways of creating a flow using something called as flow builders (they are functions provided by Kotlin coroutines library). It internally creates the flow for us, we will come to it later.

Slide 35

Slide 35 text

fun stream(): Flow = flow { emit("") // Emits the value upstream ☝ emit("⚽") emit("") } ❄ ❄ ❄ ❄ ❄ Since Flow is cold, nothing inside the flow builder block will be called until there is a subscriber or a collector observing the flow

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) } } Let’s look at the collector, here we have a main function which gets the flow inside the `runBlocking` function. For people who haven’t used coroutines; `runBlocking` function is a blocking coroutine scope i.e it blocks the current thread until everything is completed. We need the coroutine scope since the `collect` function is a suspend function, so is `emit`. We will see ahead why they are suspend functions

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() Let’s understand how things work internally

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() Initially the main function calls the stream function

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() Which immediately returns the flow

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() Which immediately returns the flow

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 Then we call the collect function on the flow, which essentially means we are subscribed to the emitter

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 That the emitter starts emitting the value sequentially

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 And the collector receives them and prints in this case

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 ⚽ And then the same loop happens emitting the next value

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 ⚽ ⚽ .. This goes on

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 .. Till the emitter has no values that’s when the execution moves out of collect and is complete

Slide 48

Slide 48 text

Let’s dive deeper Let’s dive a little deeper and see how this is working internally

Slide 49

Slide 49 text

public interface Flow { public suspend fun collect(collector: FlowCollector) } Collector As I mentioned before Flow is based upon 2 components a collector and emitter, essentially they are interfaces. The following represents the interface for collector which exposes the `collect` function

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 And the emitter interface which allows emitting values

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 The key thing to note over here is both the functions are suspend which allows the magic of interoperability with coroutines and also allows using the structured concurrency provided by coroutines. The funny thing is the whole internal mechanism of flow is essentially `emit` and `collect` function calling each other.

Slide 52

Slide 52 text

Flow ❤ Coroutines Flow loves coroutine also because it’s built on top of it

Slide 53

Slide 53 text

fun loadFromDatabase(): String { // Heavy operation of fetching from DB return "Very heavy " } Let’s say you have a function which loads some data from database, since this operation takes time to perform you’d ideally want to perform it in the background thread by marking it as suspend

Slide 54

Slide 54 text

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

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) } you can just directly call any coroutine function inside the `flow` block

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 That’s because the `flow` block accepts a suspending lambda and also because the both the collector and emitter are suspend functions which is just great!

Slide 57

Slide 57 text

Principles of Flow Let’s take a look at core ideology of Flow and how they make our life easier

Slide 58

Slide 58 text

fun main() = runBlocking { flow { emit("Context") } .flowOn(Dispatchers.IO) } With Flow you can define or switch the thread by using the operator called as `flowOn` which takes in a dispatcher which in this case is IO.

Slide 59

Slide 59 text

fun main() = runBlocking { flow { emit("Context") // IO thread } .flowOn(Dispatchers.IO) } ‐ This operator sets the specified this thread only upstream i.e anything above the `flowOn` operator will run on IO thread.

Slide 60

Slide 60 text

fun main() = runBlocking { flow { emit("Context") // IO thread } .flowOn(Dispatchers.IO) .map { "$it Preservation" } } The map operator below the `flowOn` will run on some different thread and not IO thread

Slide 61

Slide 61 text

Context Preservation This concept of preventing context from leaking is called as “Context Preservation” where the context is encapsulated and it’s never allowed to go downstream ⬇ . There is only one way to change the context of a flow

Slide 62

Slide 62 text

Observable.just("") .map{ /* IO thread */ } .subscribeOn(Schedulers.io()) .map{ /* IO thread */ } .subscribeOn(Schedulers.computation()) .map{ /* IO thread */ } RxJava example If you compare this to RxJava where it’s super confusing especially for beginners to understand which thread their code is going to run on. In the example above you can see that only the first `subscribeOn` is applied and other one is ignored

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 Combined with another operator to change thread i.e `observeOn` with different behaviour, it’s a facepalm moment

Slide 64

Slide 64 text

Exception Transparency ☢ This is the 2nd principle or ideology of Flow. Flow implementations never catch or handle exceptions that occur in downstream ⬇ flows, they catch only for upstream ⬆ ; this is again due to context preservation. There is only 1 operator to catch error which is the `catch` operators. Let’s look at an example

Slide 65

Slide 65 text

flow { val value = loadFromDatabase() emit(value) } Let’s take the previous example of a flow which tries to get the value from the database and emit. As you can imagine anything can happen while loading from database which might throw some sort of exception

Slide 66

Slide 66 text

flow { val value = loadFromDatabase() emit(value) } .catch { emit("Fallback value") } Very similar to try/catch block, flow has a `catch` operator which will catch the any exceptions thrown above

Slide 67

Slide 67 text

flow { try { val value = loadFromDatabase() emit(value) } catch (e: Exception) { emit("Fallback value") } } You can even use the classic try/catch block, it will behave exactly similar to the catch operator

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") } } } I’d recommend to use the catch operator but there are can be use-cases. During those time just make sure to ignore the `CancellationException` because you don’t want to emit an error when your flow or coroutine was cancelled when the activity was closed or something. This is pretty how the catch operator works internally

Slide 69

Slide 69 text

flow { try { val value = loadFromDatabase() emit(value) } catch (e: IOException) { emit("Fallback value") } } Or you can specifically handle the exception you expect to see. For example, in case of database operation you’d specifically want to exception related to IO operation.

Slide 70

Slide 70 text

flow { val value = loadFromDatabase() emit(value) } .catch { emit("Fallback value") } .collect { } The value emitted from the catch is received in the collect, that means there is only a single place to handle all types of cases either success or failure which makes it super transparent to handle all the cases

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") } } } Ex Example I’d recommend to toss in sealed classes so you always cover all your cases

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.” It might be confusing to understand when to use suspend and when to use `Flow`.

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.” The general rule of thumb is use `suspend` for one-shot operations like insert into database operation or a network operation.

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.” And use Flow when there is stream of data involved, for example get data from the database and continuously listening for updates.

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 And they allow to send and receive data between different coroutines. The idea is to use communication for sharing information instead of shared mutable memory

Slide 78

Slide 78 text

Source Channels are communication primitives A coroutine that sends (produces) information is often called a producer, and a coroutine that receives (consumes) information is called a consumer.

Slide 79

Slide 79 text

Channels are hot in nature Channels are hot in nature hence they are not always the best candidate to use

Slide 80

Slide 80 text

Channels are low level primitives Channels are low level primitives and Kotlin Flow internally is built using coroutines and channels. Generally you should try using Flow for your use cases just because with channels you need to be a bit careful with it’s hot nature, cancellation, etc.

Slide 81

Slide 81 text

private val searchQueryChannel = ConflatedBroadcastChannel() Let’s take an example of a search application, where the app needs to show some results after the user enters the search query; for which we will use the ConflatedBroadcastChannel

Slide 82

Slide 82 text

private val searchQueryChannel = ConflatedBroadcastChannel() which is basically a broadcast channel or send channel i.e it allows to broadcast or send values

Slide 83

Slide 83 text

private val searchQueryChannel = ConflatedBroadcastChannel() and Conflated means only recent values are emitted and every subscriber immediately receives the most recently sent element

Slide 84

Slide 84 text

// On text change searchQueryChannel.send("Bob the builder ") private val searchQueryChannel = ConflatedBroadcastChannel() And somewhere from your activity or fragment when the search text changes, you’d call the send function on the channel which will emit a new value to it’s observers

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 } On the receiving end, you can convert the channel to Flow and do some map operation to get the result for the specified search query, take only unique results using distinctUntilChanged and finally render the result on the UI

Slide 86

Slide 86 text

StateFlow But like I said Channels are low level and you should try to use Flow and Kotlin team is working on this, one such class is available to us is StateFlow. StateFlow is a new primitive and it’s designed to emit updates to it’s collectors and it will eventually replace ConflatedBroadcastChannel we just saw

Slide 87

Slide 87 text

private val searchQueryFlow = MutableStateFlow("") It’s pretty similar to the previous example, there is StateFlow and MutableStateFlow, the difference is Mutable one allows setting the value to the Flow. Make sure to always expose StateFlow outside to avoid side effects

Slide 88

Slide 88 text

private val searchQueryFlow = MutableStateFlow("") // On text change searchQueryFlow.value = "Bob the builder " On text change, we will set the value to the flow and whenever this value is updated, the collectors will receive it

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 } And then like every other flow, we can just directly collect it and listen to the updates.

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 } One key thing to remember is the operator distinctUntilChanged is automatically applied StateFlow. The IDE will warn you if you forget so that’s good

Slide 91

Slide 91 text

Legacy ➡ Flow A lot of the codebases are stuck with legacy code either callbacks, RxJava 1, loaders or other old asynchronous programming APIs and it’s totally fine! If it’s working it’s great but the problem lies with slower and hard code maintenance and refactoring when there is a (new) requirement. This slows the efficiency and the velocity ⏩ of developing the given requirement.

Slide 92

Slide 92 text

Setup migration process There are 2 major things to do for migration, first is setting up the process with the team is super important … (little pause) so the whole team works towards the single goal of migration There is no rule of thumb here, these are just my recommendations

Slide 93

Slide 93 text

Discuss with your team First order of business is to discuss within your team and trying to figure what alternative to pick, that works for your team and the use-case you are trying to solve. I’d recommend doing as followed to get started and decide what other tool suits the best for your codebase:

Slide 94

Slide 94 text

Discuss with your team Setup a meeting - Setup a meeting with the team and list out all the possible alternatives like RxJava 2, 3 or Flow. Discuss the pros — cons — advantages of all the options

Slide 95

Slide 95 text

Discuss with your team Setup a meeting Getting comfortable It’s best if the team gets to try out and get comfortable with the option you all like, this might include doing a small sample app, giving an internal presentation, articles, talks, etc.

Slide 96

Slide 96 text

Discuss with your team Setup a meeting Getting comfortable Evaluate It’s also important to evaluate how hard the migration of certain option looks like for your codebase, the learning curve for new joiners to the team. For instance, we at Clue migrated from RxJava 1 to Flow and we had evaluated RxJava 2 and 3 at that time and for our codebase it would have been super hard and we really liked Flow and everyone felt comfortable with it

Slide 97

Slide 97 text

Discuss with your team Setup a meeting Getting comfortable Evaluate Set the common goal At the end of this, you have a common goal in mind; for example migrate from RxJava 1 to Flow.

Slide 98

Slide 98 text

Setup foundation ⛩ Setting up the foundation is very key to the whole migration because it’s what will motivate the team to write the new code in Flow or whatever is decided. You don’t want any developer to start thinking to write the new feature in Flow and then realise there is no testing framework which will make the developer go back into comfort zone since all of us are humans.

Slide 99

Slide 99 text

Setup foundation ⛩ Setup guidelines Setup guidelines with standard practices of how team decided to use Flow throughout the codebase

Slide 100

Slide 100 text

Setup foundation ⛩ Setup guidelines Setup the necessary base classes Setup the necessary base classes for your codebase and to work with your architecture including the dependencies

Slide 101

Slide 101 text

Setup foundation ⛩ Setup guidelines Setup the necessary base classes Setup the testing framework Setup the testing framework / utilities so it’s easier to do get started and do testing like setting up the dispatchers

Slide 102

Slide 102 text

Setup foundation ⛩ Setup guidelines Setup the necessary base classes Setup the testing framework Expose common features as Flow Each codebase has some base APIs which are commonly used by features like getting the user details. It’s ideal if those base APIs are exposed as Flow equivalent functions

Slide 103

Slide 103 text

Expose common features as Flow interface UserRepository { // Legacy function fun observeUser(): Observable } Let’s say you have this existing interface which exposes an Observable of user. One approach is to convert this function to return Flow or coroutine which is great if it’s possible, but it not be feasible always

Slide 104

Slide 104 text

Expose common features as Flow interface UserRepository { // Legacy function fun observeUser(): Observable // Flow equivalent of [observeUser]. fun getUser(): Flow } Another approach is to duplicate those base APIs and expose the duplicated API as Flow or coroutine

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 } ☠ Mark the legacy function as @Deprecated so everyone uses the new function and avoids the legacy one ☠ And eventually with time as everyone uses the new function, you can remove the legacy function Try to extract the business logic so the existing function and new Flow function shares the same logic preventing updating it twice when needed

Slide 106

Slide 106 text

Setup safeguards ☢ Now that we are super motivated to write Kotlin Coroutines, Safeguards will prevent kinda force the team to write more and more code in Kotlin Flow / Coroutines and also convert the legacy code.

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 Each codebase has some base APIs which are commonly used by features like getting the user details. It’s ideal if those base APIs are exposed as Flow equivalent functions

Slide 108

Slide 108 text

Interoperability The key to success of Java-Kotlin migration was interoperability and ease of using one from another which opened the doors for gradual migration. Similarly having this ability allows to quickly interop between the legacy code and Flow or Coroutines which can drastically change the landscape and Flow provides an amazing API to covert any legacy callback based API to Flow

Slide 109

Slide 109 text

fun updateOnDocumentChange(document: Document) { val listener = Document.ChangeListener { updateUI(it) } document.addChangeListener(listener) } Let’s say you have a function which has a listener which is triggered each time the document is updated.

Slide 110

Slide 110 text

fun updateOnDocumentChange(document: Document) { val listener = Document.ChangeListener { updateUI(it) } document.addChangeListener(listener) } removeChangeListener(listener) You need to make sure to remove this listener when the component is destroyed for example when activity is destroyed

Slide 111

Slide 111 text

fun Document.awaitChange(): Flow = callbackFlow { } Flow provides an API called as `callbackFlow`

Slide 112

Slide 112 text

fun Document.awaitChange(): Flow = callbackFlow { val listener = Document.ChangeListener { } addChangeListener(listener) } Where you can write the legacy listener aka callback

Slide 113

Slide 113 text

fun Document.awaitChange(): Flow = callbackFlow { val listener = Document.ChangeListener { offer(it) } addChangeListener(listener) } And offer which basically means emit this value each time the document is changed. But you also need to remove the listener at some point right?

Slide 114

Slide 114 text

fun Document.awaitChange(): Flow = callbackFlow { val listener = Document.ChangeListener { offer(it) } addChangeListener(listener) awaitClose { removeChangeListener(listener) } } You can use the function `awaitClose` which is called when the flow or coroutine is cancelled, where we can safely remove the 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) } } Finally with code comments it looks something like this which a lot of people forget to write

Slide 116

Slide 116 text

Rx ➡ Flow Let’s look at how you can migrate from Rx to Flow, the goal here is to not tell you to go next moment and migrate your codebase to Flow [I mean that would be great for your own projects].

Slide 117

Slide 117 text

Reactive Stream Specification Flow Rx Flow and Rx follow the same reactive stream specification so in theory it’s very easy to convert data streams from Rx to Flow and the reverse

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() } There are two ways to convert, 1st is like refactor your Rx implementation to use flow but that’s time confusing and sometimes you just want to switch types so you can gradually move to flow. In that cases you can use the `asFlow` extension function to quick switch the type from `Flowable` / `Observable` to `Flow`

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() } Same can be done to convert flow to observable or flowable when you need it

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 Here’s a quick comparison of terminology between Rx and coroutine which I stole from Clue’s documentation. In my defence I created this table at Clue Use this chart as a reference when you are new to coroutines world or working on migrating from Rx to coroutines or reverse. Now that see, we pretty much covered all the things mentioned.

Slide 121

Slide 121 text

Fun part Let’s look at something really fun

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 */ } } The `asFlow` function actually uses `callbackFlow` to convert observable to flow which is soooo cool

Slide 123

Slide 123 text

Rx1 ➡ Flow https://github.com/AkshayChordiya/kotlinx.coroutines-rx1 If your codebase is stuck with RxJava 1 and you want to move away. I’d recommend to migrate to Kotlin Flow rather than RxJava 2 / 3. Since RxJava 1 bindings are not officially supported. I have created a library for those need it.

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 It offers the API for RxJava 1 types to coroutines and also API for Flow and Observable almost similar to RxJava 2

Slide 125

Slide 125 text

Testing Now to everyone’s favourite topic - testing Testing flow or coroutine is pretty easy

Slide 126

Slide 126 text

@RunWith(JUnit4::class) class TestingWithFlow { fun getFlow() = flow { emit("Bob the builder ") } } Let’s take a simple flow of string

Slide 127

Slide 127 text

@RunWith(JUnit4::class) class TestingWithFlow { fun getFlow() = flow { emit("Bob the builder ") } @Test fun testFlow() = runBlocking { } } And write the test function and use the `runBlocking` coroutine scope because we want our test to wait till the coroutine is completed

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) } } Like I said Flow is similar to a list or a collection

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) } } So you can use `take(1)` operator to pick the 1st value from the stream

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) } } and then convert the flow to list so you can easily assert it

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()) } Or use this extension function which I again stole from our Clue code

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) } } With the extension function it would be something like this, much more cleaner

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 Here’s a quick summary what you learned today - We revisited coroutines - We saw why Kotlin Flow came into the picture and what makes it special - We saw basics of Flow i.e the emitter and collector and how they work internally - Principles of Flow i.e context preservation and exception transparency and how they make our lives easy - then how to convert Rx or Legacy code to Flow - How you can test flows - And some 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 To conclude Kotlin Flow raises the bar and makes reactive programming easy, gives us great Kotlin features. That said it’s still work in progress and lots of exciting things are coming soooon.

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

Also make sure to check out the Kotlin online event to hear new updates on Kotlin Flow and coroutines

Slide 138

Slide 138 text

Thank You https://bit.ly/2Favsdn Below the feedback link to my talk if you want to give one, the QR code does the same. And yeah thank you so much!