Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Go with the Kotlin Flow ๐ŸŒŠ

Go with the Kotlin Flowย ๐ŸŒŠ

Kotlin Flow is yet another implementation of Reactive Stream specification made on top of coroutines for Kotlin.

In this talk, we will look at Kotlin Flow, why it was introduced, some basics, show what makes it special, how can you migrate and most important testing

Agenda
- Why Flow and it's history
- Basics of Kotlin Flow
- How it compares with RxJava and LiveData
- Why and how to migrate from RxJava 1 or 2
- Working with RxJava โค Flow in single codebase
- Magic of multi-platform
- Tests
- Conclusion

Youโ€™ll walk away with a clear idea of what Kotlin Flow is and how it compares with other reactive implementation particularly RxJava and how to evaluate and how you can gradually migrate from Rx if you want to

Akshay Chordiya

August 09, 2020
Tweet

More Decks by Akshay Chordiya

Other Decks in Programming

Transcript

  1. Synchronous code fun showUser() { val user = db.loadUser() //

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

    Blocks the thread render(user) } loadUser render
  3. With Coroutines suspend fun showUser() { val user = db.loadUser()

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

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

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

    // Background thread withContext(Dispatchers.Main) { render(user) // UI thread } } showUser loadUser render
  7. 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โ€
  8. 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
  9. 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
  10. fun stream(): Flow<String> = flow { emit("") // Emits the

    value upstream โ˜ emit("โšฝ") emit("") }
  11. fun stream(): Flow<String> = flow { emit("") // Emits the

    value upstream โ˜ emit("โšฝ") emit("") }
  12. fun stream(): Flow<String> = flow { emit("") // Emits the

    value upstream โ˜ emit("โšฝ") emit("") }
  13. fun stream(): Flow<String> = flow { emit("") // Emits the

    value upstream โ˜ emit("โšฝ") emit("") } โ„ โ„ โ„ โ„ โ„
  14. fun stream(): Flow<String> = flow { emit("") // Emits the

    value upstream โ˜ emit("โšฝ") emit("") } Emitter Collector fun main() = runBlocking { stream().collect { value -> println(value) } }
  15. fun stream(): Flow<String> = flow { emit("") // Emits the

    value upstream โ˜ emit("โšฝ") emit("") } Emitter Collector fun main() = runBlocking { stream().collect { value -> println(value) } } main() stream()
  16. fun stream(): Flow<String> = flow { emit("") // Emits the

    value upstream โ˜ emit("โšฝ") emit("") } Emitter Collector fun main() = runBlocking { stream().collect { value -> println(value) } } main() stream()
  17. fun stream(): Flow<String> = flow { emit("") // Emits the

    value upstream โ˜ emit("โšฝ") emit("") } Emitter Collector fun main() = runBlocking { stream().collect { value -> println(value) } } main() stream()
  18. fun stream(): Flow<String> = flow { emit("") // Emits the

    value upstream โ˜ emit("โšฝ") emit("") } Emitter Collector fun main() = runBlocking { stream().collect { value -> println(value) } } main() stream()
  19. fun stream(): Flow<String> = flow { emit("") // Emits the

    value upstream โ˜ emit("โšฝ") emit("") } Emitter Collector fun main() = runBlocking { stream().collect { value -> println(value) } } main() stream() collect
  20. fun stream(): Flow<String> = flow { emit("") // Emits the

    value upstream โ˜ emit("โšฝ") emit("") } Emitter Collector fun main() = runBlocking { stream().collect { value -> println(value) } } main() stream() collect emit
  21. fun stream(): Flow<String> = flow { emit("") // Emits the

    value upstream โ˜ emit("โšฝ") emit("") } Emitter Collector fun main() = runBlocking { stream().collect { value -> println(value) } } main() stream() collect emit
  22. fun stream(): Flow<String> = flow { emit("") // Emits the

    value upstream โ˜ emit("โšฝ") emit("") } Emitter Collector fun main() = runBlocking { stream().collect { value -> println(value) } } main() stream() collect emit collect emit โšฝ
  23. fun stream(): Flow<String> = flow { emit("") // Emits the

    value upstream โ˜ emit("โšฝ") emit("") } Emitter Collector fun main() = runBlocking { stream().collect { value -> println(value) } } main() stream() collect emit collect emit โšฝ โšฝ
  24. fun stream(): Flow<String> = flow { emit("") // Emits the

    value upstream โ˜ emit("โšฝ") emit("") } Emitter Collector fun main() = runBlocking { stream().collect { value -> println(value) } } main() stream() collect emit collect emit โšฝ โšฝ ..
  25. fun stream(): Flow<String> = 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 ..
  26. public interface Flow<out T> { public suspend fun collect(collector: FlowCollector<T>)

    } Collector public interface FlowCollector<in T> { public suspend fun emit(value: T) } Emitter
  27. public interface Flow<out T> { public suspend fun collect(collector: FlowCollector<T>)

    } Collector public interface FlowCollector<in T> { public suspend fun emit(value: T) } Emitter
  28. suspend fun loadFromDatabase(): String { // Heavy operation of fetching

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

    from DB return "Very heavy " } fun stream() = flow { val value = loadFromDatabase() emit(value) } public fun <T> flow(@BuilderInference block: suspend FlowCollector<T>.() -> Unit): Flow<T>
  30. fun main() = runBlocking { flow { emit("Context") // IO

    thread } .flowOn(Dispatchers.IO) } โ€
  31. fun main() = runBlocking { flow { emit("Context") // IO

    thread } .flowOn(Dispatchers.IO) .map { "$it Preservation" } }
  32. Observable.just("") .map{ /* IO thread */ } .subscribeOn(Schedulers.io()) .map{ /*

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

    IO thread */ } .subscribeOn(Schedulers.computation()) .map{ /* IO thread */ } .observeOn(Schedulers.computation()) .map { /* */ } RxJava example
  34. flow { try { val value = loadFromDatabase() emit(value) }

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

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

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

    emit("Fallback value") } .collect { }
  38. sealed class State { object Success: State() object Failure: State()

    } fun main() = runBlocking { flow<State> { emit(State.Success) } .catch { emit(State.Failure) } .collect { state -> when (state) { State.Success -> println("Success") State.Failure -> println("Failure") } } } x Example
  39. โ€œ- Use suspend for one-shot operations like insert operation or

    a network operation - Use Flow<T> to get stream of data like getting updates on data changes in the database.โ€
  40. โ€œ- Use suspend for one-shot operations like insert operation or

    a network operation - Use Flow<T> to get stream of data like getting updates on data changes in the database.โ€
  41. โ€œ- Use suspend for one-shot operations like insert operation or

    a network operation - Use Flow<T> to get stream of data like getting updates on data changes in the database.โ€
  42. // On text change searchQueryChannel.send("Bob the builder ") private val

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

    searchQueryChannel = ConflatedBroadcastChannel<String>() searchQueryChannel .asFlow() /* Get the results for the search query */ .distinctUntilChanged() .collect { // Render the result on UI }
  44. private val searchQueryFlow = MutableStateFlow<String>("") // On text change searchQueryFlow.value

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

    = "Bob the builder " searchQueryFlow /* Get the results for the search query */ .distinctUntilChanged() .collect { // Render the result on UI }
  46. Setup foundation โ›ฉ Setup guidelines Setup the necessary base classes

    Setup the testing framework Expose common features as Flow
  47. Expose common features as Flow interface UserRepository { // Legacy

    function fun observeUser(): Observable<User> }
  48. Expose common features as Flow interface UserRepository { // Legacy

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

    function @Deprecated( message = "Use the flow equivalent -> getUser()", replaceWith = ReplaceWith("getUser()", "<package>") ) fun observeUser(): Observable<User> // Flow equivalent of [observeUser]. fun getUser(): Flow<User> } โ˜ 
  50. 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
  51. fun updateOnDocumentChange(document: Document) { val listener = Document.ChangeListener { updateUI(it)

    } document.addChangeListener(listener) } removeChangeListener(listener)
  52. fun Document.awaitChange(): Flow<Document> = callbackFlow { val listener = Document.ChangeListener

    { offer(it) } addChangeListener(listener) awaitClose { removeChangeListener(listener) } }
  53. /** * Listen for changes in the [Document] and notify

    the * receivers about changes. */ fun Document.awaitChange(): Flow<Document> = callbackFlow { val listener = Document.ChangeListener { offer(it) } addChangeListener(listener) awaitClose { removeChangeListener(listener) } }
  54. fun observable(): Observable<String> { return flow { emit("a") }.asObservable() }

    https://github.com/Kotlin/kotlinx.coroutines/tree/master/reactive/kotlinx-coroutines-rx2 fun flow(): Flow<String> { return Observable.just("a") .asFlow() }
  55. Rx Coroutines Single / Completable / Maybe suspend function Observable

    Flow Subject Channel Schedulers Dispatchers Disposables Scopes subscribe collect Terminology between Rx and Coroutine world
  56. public fun <T: Any> ObservableSource<T>.asFlow(): Flow<T> = callbackFlow { val

    observer = object : Observer<T> { 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 */ } }
  57. import rx.Observable fun observable(): Observable<String> { return flow { emit("a")

    }.asObservable() } import rx.Observable fun flow(): Flow<String> { return Observable.just("a") .asFlow() } https://github.com/AkshayChordiya/kotlinx.coroutines-rx1
  58. @RunWith(JUnit4::class) class TestingWithFlow { fun getFlow() = flow { emit("Bob

    the builder ") } @Test fun testFlow() = runBlocking { } }
  59. @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) } }
  60. @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) } }
  61. @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) } }
  62. /** * Asserts only the [expected] items by just taking

    that many from the stream */ suspend fun <T> Flow<T>.assertItems(vararg expected: T) { assertEquals(expected.toList(), this.take(expected.size).toList()) }
  63. @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) } }
  64. 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
  65. 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
  66. 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