$30 off During Our Annual Pro Sale. View Details »

Flowing Things, not so strange in the MVI world

ragdroid
August 27, 2019

Flowing Things, not so strange in the MVI world

Migrating an Rx MVI pattern completely to coroutines using Flow. In the MVI world, there was a missing piece from the coroutines framework and due to this, it always felt strange to completely adopt coroutines. Recently, with the introduction of Co-routines Flow library, things are not so strange anymore. In this talk we will have a look at Coroutines Flow library, its need and how it compares with the reactive world. We will then learn to migrate an Rx MVI pattern to use coroutines Flow.

ragdroid

August 27, 2019
Tweet

More Decks by ragdroid

Other Decks in Programming

Transcript

  1. FLOWING
    THINGS
    Not so strange in the MVI world

    View Slide

  2. Garima Jain
    @ragdroid

    View Slide

  3. @ragdroid
    #droidconNYC
    Who?

    View Slide

  4. Who?
    • Rx background
    • Coroutines
    • MVI
    • Stranger Things?

    View Slide

  5. @ragdroid
    #droidconNYC
    What?

    View Slide

  6. What?
    • Chapter One : Coroutines and Flow
    • Chapter Two : Channels and Flows
    • Chapter Three : MVI
    • Chapter Four : Rx to Flow
    • Summary

    View Slide

  7. CHAPTER ONE
    THE FLOW

    View Slide

  8. Coroutines
    • Lightweight thread
    • Run computations without blocking
    • Can be suspended

    View Slide

  9. Coroutines
    • Coroutine Scope
    ★ ViewModel Scope
    ★ Lifecycle Scope
    • Coroutine Builders
    ★ launch { } - fire and forget
    ★ async { } - await() result
    ★ within a scope, Structured Concurrency

    View Slide

  10. Channels
    • Communication between different coroutines
    • Similar to BlockingQueue

    View Slide

  11. Flow
    • Cold asynchronous stream that sequentially emits values
    • Utilizes coroutines and channels
    • Experimental Recently Stable APIs in 1.3.0

    View Slide

  12. Flow Builders
    ★ flowOf( )
    ★ asFlow( )
    ★ flow { }
    ★ channelFlow { }

    View Slide

  13. Flow Operators
    • Intermediate :
    ★ map, filter, take, zip, etc.
    • Terminal :
    ★ collect, single, reduce, etc.

    View Slide

  14. Flow Operators
    fun main() = runBlocking {
    val flow = flow {
    for (i in 1..10) {
    delay(500L)
    emit(i)
    }
    }.filter {
    it % 2 == 0
    }
    flow.collect {
    println(it)
    }
    }
    2
    4
    6
    8
    10

    View Slide

  15. Flow Constraints
    ★ Context Preservation
    ★ Exception Transparency

    View Slide

  16. Context Preservation
    val flowA = flowOf(1, 2, 3)
    .map { it + 1 }
    .flowOn(ctxA)
    val filtered = flowA
    .filter { it == 3 }
    withContext(Dispatchers.Main) {
    val result = filtered.single()
    myUi.text = result
    }
    https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/
    // Will be executed in ctxA
    // Changes the upstream context: flowOf and map
    // Pure operator without a context yet
    // All non-encapsulated operators will be executed in Main: filter and single

    View Slide

  17. CHAPTER TWO
    CONSUMER, DO YOU COPY?
    Hot Channels and Cold Flows

    View Slide

  18. fun main() = runBlocking {
    val job = launch {
    produce {
    println(“We are hot")
    send(1)
    }
    }
    job.invokeOnCompletion {
    println("Completed")
    }
    delay(1000L)
    println("Done waiting")
    }
    Channels
    Done waiting
    Delay
    We are hot

    View Slide

  19. Channels
    fun main() = runBlocking {
    val job = launch {
    val hotChannel = produce {
    println(“We are hot")
    send(1)
    }
    channel.consumeEach {
    println(it)
    }
    }
    job.invokeOnCompletion {
    println("Completed")
    }
    delay(1000L)
    println("Done waiting")
    }
    1
    Completed
    Done waiting
    We are hot

    View Slide

  20. Flows
    fun main() = runBlocking {
    val job = launch {
    val coldFlow = flow {
    println(“We are cold”)
    emit(1)
    }
    }
    job.invokeOnCompletion {
    println("Completed")
    }
    delay(1000L)
    println("Done waiting")
    }
    Completed
    Done waiting

    View Slide

  21. Flows
    fun main() = runBlocking {
    val job = launch {
    val coldFlow = flow {
    println("We are cold")
    emit(1)
    }
    coldFlow.collect {
    println(it)
    }
    }
    job.invokeOnCompletion {
    println("Completed")
    }
    delay(1000L)
    println("Done waiting")
    }
    We are cold
    1
    Completed
    Done waiting

    View Slide

  22. Channel Gotchas
    • Channels are a pathway to a different dimension (Upside Down)
    • If we they are open, another dimension exists
    • Existence of another dimension could be dangerous
    • It could use up some heavy resources

    View Slide

  23. Channel Gotchas
    Easy to make mistakes

    View Slide

  24. Channel Gotchas
    Easy to make mistakes

    View Slide

  25. CHAPTER THREE
    THE BATTLE OF ACTIONS
    MVI

    View Slide

  26. Model View Intent

    View Slide

  27. Model View Intent
    State View Intention

    View Slide

  28. State View Intention
    User
    Intent
    State
    View
    Changes
    Updates
    Seen Interacts

    View Slide

  29. View
    ViewModel Data
    Intent
    newState
    Action
    Result
    previousState
    Reducer
    State View Intention
    State
    MVI on top of MVVM

    View Slide

  30. View
    ViewModel Data
    Intent
    newState
    Action
    Result
    previousState
    Reducer
    State View Intention
    State
    4
    3
    5
    1 2
    MVI on top of MVVM

    View Slide

  31. State View Intention
    1
    3
    2
    4
    5
    Action Events
    Actions to Results
    Reduce Results to new State
    Plugging-in the pieces
    State stream

    View Slide

  32. CHAPTER FOUR
    VANISHING OF Rx FLOWABLES
    Migrating an Rx MVI pattern to use Flow

    View Slide

  33. Demo

    View Slide

  34. Actions
    sealed class MainAction {
    object PullToRefresh
    object LoadData
    data class LoadDescription(val characterId: Long)
    }

    View Slide

  35. Actions - ViewModel
    interface ViewModel {
    fun onAction(action: MainAction)
    ...
    }
    Rx

    View Slide

  36. Actions - ViewModel
    interface ViewModel {
    fun onAction(action: MainAction)
    ...
    }
    Flow

    View Slide

  37. Action Events
    viewModel.onAction(MainAction.LoadData)
    viewModel.onAction(..)

    View Slide

  38. viewModel.onAction(MainAction.LoadData)
    viewModel.onAction(..)
    refreshLayout.setOnRefreshListener {
    viewModel.onAction(MainAction.PullToRefresh)
    }
    Action Events

    View Slide

  39. viewModel.onAction(MainAction.LoadData)
    viewModel.onAction(..)
    override fun onCharacterDescriptionClicked(itemId: Long) {
    viewModel.onAction(MainAction.LoadDescription(itemId))
    }
    refreshLayout.setOnRefreshListener {
    viewModel.onAction(MainAction.PullToRefresh)
    }
    Action Events

    View Slide

  40. State View Intention
    1 Action Events
    3
    2
    4
    5
    Actions to Results
    Reduce Results to new State
    State stream
    Plugging-in the pieces

    View Slide

  41. State View Intention
    1 Action Events
    3
    2
    4
    5
    Actions to Results
    Reduce Results to new State
    State stream
    Plugging-in the pieces

    View Slide

  42. State View Intention
    1 Action Events
    3
    2
    4
    5
    Actions to Results
    Reduce Results to new State
    State stream
    Plugging-in the pieces
    same

    View Slide

  43. Actions to Results
    sealed class MainResult {
    object Loading
    object LoadingError
    data class LoadingComplete(val characters: List)
    object PullToRefreshing: MainResult()
    object PullToRefreshError
    data class PullToRefreshComplete(val characters: List)
    sealed class DescriptionResult(val characterId: Long) {
    data class DescriptionLoading(private val id: Long)
    data class DescriptionError(private val id: Long)
    data class DescriptionLoadComplete(private val id: Long,
    val description: String)
    }
    }

    View Slide

  44. View
    ViewModel Data
    Intent
    newState
    Action
    Result
    previousState
    Reducer
    State View Intention
    State

    View Slide

  45. View ViewModel
    Data
    Intent
    newState
    Action Result
    previousState
    Reducer
    State View Intention
    State
    Repository Network

    View Slide

  46. Data Layer
    @GET("characters/{characterId}")
    fun getCharactersSingle(...): Single>
    fun fetchCharactersSingle(): Single> =
    marvelApi.getCharactersSingle( ... )
    MarvelApi
    MainRepository

    View Slide

  47. Actions to Results - LoadData
    fun loadingResult(loadDataActionStream: Flowable)
    : Flowable {
    return loadDataActionStream
    .observeOn(Schedulers.io())
    .flatMap {
    mainRepository.fetchCharactersSingle().toFlowable()
    .map { list -> MainResult.LoadingComplete(list) }
    .startWith(MainResult.Loading)
    .onErrorReturn { error ->
    navigate(MainNavigation.Snackbar(error.message))
    MainResult.LoadingError
    }
    }
    }
    ViewModel

    View Slide

  48. Data Layer
    @GET("characters/{characterId}")
    fun getCharactersSingle(...): Single>
    MarvelApi - Rx

    View Slide

  49. Data Layer
    @GET("characters/{characterId}")
    suspend fun getCharacters(…): List
    MarvelApi - Coroutines
    2.6.0

    View Slide

  50. Data Layer
    fun fetchCharactersSingle(): Single> =
    marvelApi.getCharactersSingle( ... )
    MainRepository - Rx

    View Slide

  51. Data Layer
    suspend fun fetchCharacters(): List =
    marvelApi.getCharacters( ... )
    MainRepository - Coroutines

    View Slide

  52. fun loadingResult(actions: Flowable<...>)
    : Flowable =
    actions
    .observeOn(Schedulers.io())
    .flatMap {
    mainRepository.fetchCharactersFlowable()
    .map { MainResult.LoadingComplete }
    .startWith(MainResult.Loading)
    .onErrorReturn { error ->
    navigate(MainNavigation.Snackbar)
    MainResult.LoadingError
    }
    }
    fun loadingResult(actions: Flowable)
    : Flowable =
    actions
    .observeOn(Schedulers.io())
    .flatMap {
    mainRepository.fetchCharactersSingle().toFlowable()
    .map { states -> MainResult.LoadingComplete(states) }
    .startWith(MainResult.Loading)
    .onErrorReturn { error ->
    navigate(MainNavigation.Snackbar(error.message)
    MainResult.LoadingError
    }
    }
    Actions to Results - Load Data
    RxViewModel

    View Slide

  53. fun loadingResult(actions: Flowable<...>)
    : Flowable =
    actions
    .observeOn(Schedulers.io())
    .flatMap {
    mainRepository.fetchCharactersFlowable()
    .map { MainResult.LoadingComplete }
    .startWith(MainResult.Loading)
    .onErrorReturn { error ->
    navigate(MainNavigation.Snackbar)
    MainResult.LoadingError
    }
    }
    RxViewModel
    fun loadingResult(actionsFlow: Flow)
    : Flow =
    actionsFlow.flatMapMerge {
    flow {
    emit(MainResult.Loading)
    val characters = mainRepository.fetchCharacters()
    emit(MainResult.LoadingComplete(characters))
    }.catch {
    navigate(MainNavigation.Snackbar(it.message)
    emit(MainResult.LoadingError)
    }
    }
    FlowViewModel
    Actions to Results - Load Data

    View Slide

  54. fun loadingResult(actions: Flowable<...>)
    : Flowable =
    actions
    .observeOn(Schedulers.io())
    .flatMap {
    mainRepository.fetchCharactersFlowable()
    .map { MainResult.LoadingComplete }
    .startWith(MainResult.Loading)
    .onErrorReturn { error ->
    navigate(MainNavigation.Snackbar)
    MainResult.LoadingError
    }
    }
    RxViewModel FlowViewModel
    fun loadingResult(actionsFlow: Flow)
    : Flow =
    actionsFlow.flatMapMerge {
    flow {
    emit(mainRepository.fetchCharacters())
    }
    .map { MainResult.LoadingComplete(characters) }
    .onStart { emit(MainResult.Loading) }
    .catch {
    navigate(MainNavigation.Snackbar(it.message)
    emit(MainResult.LoadingError)
    }
    }
    Actions to Results - Load Data

    View Slide

  55. fun pullToRefreshResult(actions: Flowable)
    : Flowable =
    ...
    Actions to Results - Pull To Refresh
    RxViewModel

    View Slide

  56. fun pullToRefreshResult(actions: Flowable)
    : Flow =
    ...
    Actions to Results - Pull To Refresh
    FlowViewModel

    View Slide

  57. fun loadDesciptionResult(actions: Flowable)
    : Flowable =
    ...
    Actions to Results - Load Desciption
    RxViewModel

    View Slide

  58. fun loadDesciptionResult(actions: Flowable)
    : Flow =
    ...
    Actions to Results - Load Desciption
    FlowViewModel

    View Slide

  59. Actions to Results
    fun actionsToResultTransformer(actions: Flowable): Flowable =
    actions.publish { shared ->
    Flowable.merge(loadingResult(shared.ofType(MainAction.LoadData::class.java)),
    loadDescriptionResult(shared.ofType(MainAction.LoadDescription::class.java)),
    pullToRefreshResult(shared.ofType(MainAction.PullToRefresh::class.java)))
    }
    RxViewModel

    View Slide

  60. Actions to Results
    fun Flow.actionsToResultTransformer(): Flow =
    publish {
    loadingResult(it.ofType(MainAction.LoadData::class.java))
    .merge(loadDescriptionResult(it.ofType(MainAction.LoadDescription::class.java)),
    pullToRefreshResult(it.ofType(MainAction.PullToRefresh::class.java)))
    }
    Flow

    View Slide

  61. Merge Operator
    Actions to Results
    fun Flow.actionsToResultTransformer(): Flow =
    publish {
    loadingResult(it.ofType(MainAction.LoadData::class.java))
    .merge(loadDescriptionResult(it.ofType(MainAction.LoadDescription::class.java)),
    pullToRefreshResult(it.ofType(MainAction.PullToRefresh::class.java)))
    }
    Flow

    View Slide

  62. Merge Operator
    • No merge operator at the time
    • Creating operators with coroutines is comparatively easy
    • Let's create one

    View Slide

  63. fun Flow.merge(other: Flow): Flow = channelFlow {
    launch {
    collect { send(it) }
    }
    launch {
    other.collect {
    send(it)
    }
    }
    }
    FlowExtensions.kt
    // collect from this coroutine and send it
    // collect and send from this coroutine, too, concurrently
    Merge Operator

    View Slide

  64. fun Flow.actionsToResultTransformer(): Flow =
    publish {
    loadingResult(it.ofType(MainAction.LoadData::class.java))
    .merge(loadDescriptionResult(it.ofType(MainAction.LoadDescription::class.java)),
    pullToRefreshResult(it.ofType(MainAction.PullToRefresh::class.java)))
    }
    Actions to Results - publish
    Flow

    View Slide

  65. fun Flow.actionsToResultTransformer(): Flow =
    publish {
    loadingResult(it.ofType(MainAction.LoadData::class.java))
    .merge(loadDescriptionResult(it.ofType(MainAction.LoadDescription::class.java)),
    pullToRefreshResult(it.ofType(MainAction.PullToRefresh::class.java)))
    }
    Actions to Results - publish
    Flow
    kotlin-flow-extensions: https://github.com/akarnokd/kotlin-flow-extensions

    View Slide

  66. Merge Operator
    Actions to Results - broadcastIn
    inputActionsFlow
    .broadcastIn(viewModelScope).asFlow()
    .actionToResultTransformer()
    ...
    Flow

    View Slide

  67. Actions to Results
    fun Flow.actionsToResultTransformer(): Flow =
    flatMapMerge {
    flow {
    when(it) {
    is MainAction.PullToRefresh -> { ... }
    is MainAction.LoadData -> {
    try {
    emit(MainResult.Loading)
    val characters = mainRepository.fetchCharacters()
    emit(MainResult.LoadingComplete(characters))
    } catch (exception: Exception) {
    navigate(MainNavigation.Snackbar(exception.message))
    emit(MainResult.LoadingError(exception))
    }
    }
    is MainAction.LoadDescription -> { … }
    }
    }
    }

    View Slide

  68. View ViewModel
    Data
    Intent
    newState
    Action Result
    previousState
    Reducer
    State
    Repository Network
    State View Intention (Rx)

    View Slide

  69. View ViewModel
    Data
    Intent
    newState
    Action Result
    previousState
    Reducer
    State View Intention (Co-routines)
    State
    Repository Network

    View Slide

  70. View ViewModel
    Data
    Intent
    newState
    Action Result
    previousState
    Reducer
    State View Intention (Co-routines)
    State
    Repository Network
    Room
    2.2.0-
    alpha

    View Slide

  71. State View Intention
    3
    2
    4
    5
    Actions to Results
    Reduce Results to new State
    State stream
    Plugging-in the pieces
    1 Action Events same

    View Slide

  72. State View Intention
    3
    2
    4
    5
    Actions to Results
    Reduce Results to new State
    State stream
    Plugging-in the pieces
    1 Action Events same

    View Slide

  73. Reduce Results to new State
    data class MainViewState(val characters: List,
    val emptyState: EmptyState,
    val loadingState: LoadingState): MviState
    sealed class EmptyState {
    object None: EmptyState()
    object NoData: EmptyState()
    object NoInternet: EmptyState()
    }
    sealed class LoadingState {
    object None: LoadingState()
    object Loading: LoadingState()
    object PullToRefreshing: LoadingState()
    }
    }
    State

    View Slide

  74. Reduce Results to new State
    fun reduce(result: MainResult) {
    return when (result) {
    is MainResult.Loading -> copy(loadingState = LoadingState.Loading)
    is MainResult.LoadingError -> copy(loadingState = LoadingState.None,
    emptyState = EmptyState.NoData)
    is MainResult.LoadingComplete -> {
    val characterStates = reduceCharactersList(null, result.characters,
    resources)
    copy(characterStates, loadingState = LoadingState.None,
    emptyState = EmptyState.None)
    }
    ...
    }
    RxReducer

    View Slide

  75. Reduce Results to new State
    FlowReducer
    fun reduce(result: MainResult) {
    return when (result) {
    is MainResult.Loading -> copy(loadingState = LoadingState.Loading)
    is MainResult.LoadingError -> copy(loadingState = LoadingState.None,
    emptyState = EmptyState.NoData)
    is MainResult.LoadingComplete -> {
    val characterStates = reduceCharactersList(null, result.characters,
    resources)
    copy(characterStates, loadingState = LoadingState.None,
    emptyState = EmptyState.None)
    }
    ...
    }

    View Slide

  76. State View Intention
    1 Action Events
    3
    2
    4
    5
    Actions to Results
    Reduce Results to new State
    State stream
    Plugging-in the pieces
    same

    View Slide

  77. State View Intention
    3
    2
    4
    5
    Actions to Results
    Reduce Results to new State
    Plugging-in the pieces
    State stream
    1 Action Events same

    View Slide

  78. State View Intention
    3
    2
    4
    5
    Actions to Results
    Reduce Results to new State
    Plugging-in the pieces
    State stream
    1 Action Events same
    same

    View Slide

  79. State
    State stream
    data class MainViewState(val characters: List,
    val emptyState: EmptyState,
    val loadingState: LoadingState): MviState
    sealed class EmptyState {
    object None: EmptyState()
    object NoData: EmptyState()
    object NoInternet: EmptyState()
    }
    sealed class LoadingState {
    object None: LoadingState()
    object Loading: LoadingState()
    object PullToRefreshing: LoadingState()
    }
    }

    View Slide

  80. State LiveData
    fun stateLiveData(): LiveData = stateLiveData
    private val stateLiveData = MutableLiveData()

    stateLiveData.postValue(it)
    ViewModel

    View Slide

  81. State LiveData
    fun stateLiveData(): LiveData = stateLiveData
    private val stateLiveData = MutableLiveData()

    stateLiveData.postValue(it)
    ViewModel
    viewModel.stateLiveData()
    .observe(viewLifecycleOwner,
    Observer { render(it) })
    View

    View Slide

  82. State Flow
    private val stateChannel = ConflatedBroadcastChannel()
    val stateFlow = stateChannel.asFlow()

    stateChannel.offer(it)
    ViewModel

    View Slide

  83. State Flow
    private val stateChannel = ConflatedBroadcastChannel()
    val stateFlow = stateChannel.asFlow()

    stateChannel.offer(it)
    ViewModel
    View
    viewModel.stateFlow.collect {
    render(it)
    }

    View Slide

  84. ConflatedBroadcastChannel
    State Flow
    private val stateChannel = ConflatedBroadcastChannel()
    val stateFlow = stateChannel.asFlow()

    stateChannel.offer(it)
    ViewModel
    lifecycleScope.launch {
    viewModel.stateFlow.collect {
    render(it)
    }
    }
    View
    2.2.0-
    alpha02

    View Slide

  85. ConflatedBroadcastChannel
    • BroadcastChannel - multiple receivers
    • Conflate - combine
    • Recent value is emitted
    • Like RxJava BehaviorSubject

    View Slide

  86. Render
    override fun render(state: MainViewState) {
    binding.refreshing = state.loadingState == MainViewState.LoadingState.PullToRefreshing
    binding.loading = state.loadingState == MainViewState.LoadingState.Loading
    val characterModelList =
    state.characters.map {
    CharacterItem(it, this)
    }
    adapter.replaceItems(characterModelList, true)
    }
    View

    View Slide

  87. State View Intention
    4
    5 Plugging-in the pieces
    State stream
    3
    2 Actions to Results
    Reduce Results to new State
    1 Action Events same
    same

    View Slide

  88. State View Intention
    1 Action Events
    3
    2
    4
    5
    Actions to Results
    Reduce Results to new State
    Plugging-in the pieces
    State stream
    3
    2 Actions to Results
    Reduce Results to new State
    1 Action Events same
    same

    View Slide

  89. State View Intention
    1 Action Events
    3
    2
    4
    5
    Actions to Results
    Reduce Results to new State
    Plugging-in the pieces
    State stream
    3
    2 Actions to Results
    Reduce Results to new State
    1 Action Events same
    same
    same

    View Slide

  90. ViewModel
    Plugging-in the pieces
    interface ViewModel {
    fun onAction(action: MainAction)
    ...
    }

    View Slide

  91. RxViewModel
    Plugging-in the pieces
    val actionsProcessor: PublishProcessor =
    PublishProcessor.create()
    fun onAction(action: Action) {
    actionsProcessor.onNext(action)
    }

    View Slide

  92. FlowViewModel
    Plugging-in the pieces
    var broadcastChannel = ConflatedBroadcastChannel()
    fun onAction(action: MainAction) = broadcastChannel.offer(action)
    var actionsFlow = broadcastChannel.asFlow()

    View Slide

  93. fun processActions() {
    actionsProcessor
    .compose(actionToResultTransformer)
    .scan(initialState) { state, result ->
    reduce(state, result)}
    .subscribe({
    stateLiveData.postValue(it)
    }, Timber::e)
    .bindToLifecycle()
    }
    fun processActions() {
    actionsProcessor
    .compose(actionToResultTransformer)
    .scan(initialState) { state, result: Result ->
    reduce(state, result)}
    .subscribe({
    stateLiveData.postValue(it)
    }, Timber::e)
    .bindToLifecycle()
    }
    Plugging-in the pieces
    RxViewModel

    View Slide

  94. Plugging-in the pieces
    fun processActions() {
    actionsProcessor
    .compose(actionToResultTransformer)
    .scan(initialState) { state, result ->
    reduce(state, result)}
    .subscribe({
    stateLiveData.postValue(it)
    }, Timber::e)
    .bindToLifecycle()
    }
    fun processActions() {
    viewModelScope.launch {
    actionsFlow
    .broadcastIn(viewModelScope).asFlow()
    .actionToResultTransformer()
    .scan(initialState) { state, result ->
    reduce(state, result) }
    .collect {
    stateLiveData.postValue(it)
    }
    }
    }
    RxViewModel
    FlowViewModel

    View Slide

  95. State View Intention
    5 Plugging-in the pieces
    1 Action Events
    3
    2
    4
    Actions to Results
    Reduce Results to new State
    State stream
    3
    2 Actions to Results
    Reduce Results to new State
    1 Action Events same
    same
    same

    View Slide

  96. State View Intention
    5 Plugging-in the pieces
    1 Action Events
    3
    2
    4
    Actions to Results
    Reduce Results to new State
    State stream
    3
    2 Actions to Results
    Reduce Results to new State
    1 Action Events same
    same
    same

    View Slide

  97. State View Intention
    Congratulations !!

    View Slide

  98. @ragdroid
    #droidconNYC
    Summary

    View Slide

  99. What Did we learn?
    Rx Coroutines
    Single / Completable suspend function
    Flowable/Observable Flow
    BehaviorSubject
    ConflatedBroadcastChannel
    (DataFlow proposal)
    Schedulers Dispatchers
    Disposables Scopes

    View Slide

  100. What Did we learn?
    Rx Flow
    just(“Hello”) flowOf(“Hello”)
    flatmap() flatmapMerge()
    subscribe() collect()
    publish() broadcastIn()
    startWith() onStart()
    onErrorReturn() catch()
    map() map()
    scan() scan()
    ... ...

    View Slide

  101. What Did we learn?
    Rx Flow
    subscribeOn() flowOn()
    observeOn() flowOn()


    View Slide

  102. @ragdroid
    #droidconNYC
    Common Questions

    View Slide

  103. Why Flow? - Current State

    View Slide

  104. Why Flow?
    • First Party support
    • Android libraries have Flow support now like Room
    • Reducing complexity of business logic
    • Hopefully share more business logic

    View Slide

  105. RxJava Coroutines Interop
    • Yes org.jetbrains.kotlinx:kotlinx-coroutines-rx2:1.3.0
    • Single.await(), Flowable.awaitX()
    • rxSingle { }, rxFlowable { } …

    View Slide

  106. Non coroutine APIs to Coroutine APIs
    • Yes
    • suspendCoroutine { }
    • Continuation.resumeWith( ) Continuation.resumeWithException()

    View Slide

  107. Testing?
    • Yes
    • TestDispatcher
    • Dispatchers.setMain()
    • Inject a DispatcherProvider?

    View Slide

  108. What Next?
    • kotlin-flow-extensions: https://github.com/akarnokd/kotlin-flow-extensions
    • CoroutineBinding: CoroutineBinding: https://github.com/satoshun/CoroutineBinding
    • Flowing in the Deep: https://www.droidcon.com/media-detail?video=352670453
    • DataFlow: https://github.com/Kotlin/kotlinx.coroutines/pull/1354/files
    • Flow Guide: https://github.com/Kotlin/kotlinx.coroutines/blob/1.3.0/docs/flow.md
    • Flow vs Channel: https://github.com/manuelvicnt/MathCoroutinesFlow
    • Demo Project: https://github.com/ragdroid/klayground/tree/kotlin-flow

    View Slide

  109. References & Acknowledgements
    • https://github.com/brewin/mvi-coroutines
    • Manuel Vivo @manuelvicnt
    • Hannes Dorfmann @sockeqwe
    • Android Team @Over
    • Ritesh Gupta @_riteshhh
    • https://medium.com/@elizarov - Roman Elizarov
    • https://medium.com/@objcode - Sean McQuillan
    • https://www.youtube.com/watch?v=PXBXcHQeDLE - MVI for Android, Benoît Quenaudon

    View Slide

  110. @ragdroid
    #droidconNYC

    View Slide

  111. Questions?

    View Slide