Farewell to RxJava: Coroutine Channel replaces Rx Observable/Subject

Insert picture description here
Recently there are some articles suggesting that you give up RxJava. In the MVVM best practice recommended by AAC, RxJava is heavily relied on. Is it also possible to consider removing RxJava?
Insert picture description here

RxJava problem


  1. The
    use of RxJava in MVVM with excess functions is mainly used for asynchronous requests and subscriptions, but RxJava's capabilities are far more than this. It is a stream-responsive framework with many operators. Many functions are not well known and used, except for the increase. The package volume believes that there is a risk of bugs caused by misuse of operators

  2. Kotlin is not friendly
    . Although RxJava can be used in Kotlin, it is written in Java after all. It does not support Kotlin enough for functional Lambda. It uses a lot of custom Function types. It is easy to confuse when used with Kotlin. RxKotlin extension package Just adding some Kt extension methods, there is still a gap with the ReactiveX framework implemented in Kotlin.

  3. Life cycle control
    Compared with LiveData, RxJava cannot automatically perceive the life cycle and needs to be manually disposeed, which is easy to forget and cause leakage. Of course, a certain degree of automation can be achieved by defining some Observable extension methods, but in general RxJava is not a framework specifically implemented for GUI, and the construction of LivecycleAware is relatively lacking.

Although RxJava has many problems, it is a great improvement compared to JDK Promise/ Future, Android Handler/, AsyncTasketc. However, with the introduction of Coroutine, many scenarios can be given priority to use Coroutine instead


Replace with Coroutine


With the continuous improvement of Coroutine, basically all types of RxJava can be covered in terms of asynchronous calls.
Specific reference based Coroutine transformation RxJava project , which introduced the Single request transformation Coroutine transformation scene. Here I want to introduce how Observable/Subject can be transformed with Coroutine. Channel is generally recommended for this scenario.

Channel与BroadcastChannel

The essence of Channel is a blocking queue. There are two types in Coroutine:

  • Channel: 1 production end corresponds to 1 consumption end
  • BroadcastChannel: One production end corresponds to n consumption ends
// Channel Sample
val c = ArrayChannel<String>(2) // 缓冲区capacity=2 
launch(CommonPool) {
    
    
    c.consumeEach {
    
    
        Log.d("KT","${
      
      it}")
    }
}

launch(UI) {
    
    
    c.send("a")
    c.send("b")
    c.send("c")
}

// BroadcastChannel Sample
launch(UI) {
    
    
    val bc = ArrayBroadcastChannel<String>(10)
    bc.send("A")
    bc.send("B")

    // Consumer1
    launch(newSingleThreadContext("threadA")) {
    
    
        val subscription1: SubscriptionReceiveChannel<String> = bc.openSubscription()
        subscription1.consumeEach {
    
    
            Log.d("KT-BROADCAST-1","${
      
      it}")
        }
        // 执行不到
    }

    // Consumer2
    launch(newSingleThreadContext("threadB")) {
    
    
        val subscription2: SubscriptionReceiveChannel<String> = bc.openSubscription()
        subscription2.consumeEach {
    
    
            Log.d("KT-BROADCAST-2","${
      
      it}")
        }
        // 执行不到
    }

    delay(2000)
    bc.send("C")
    bc.send("D")
}

Output result

KT a
   b
   c

KT-BROADCAST-1  C
KT-BROADCAST-2  C
                D
KT-BROADCAST-1  D

openSubscriptionCreate multiple Subscription instances to subscribe separately. Channel data must be sent after subscription before it can be received by the consumer, otherwise the data cannot be received

send() and offer()

Both send and offer add data to the channel

  • send()
    can only be used in the coroutine (launch{... }), and you can still add more than the buffer quantity
  • offer()
    can be called outside the coroutine, it returns false when the number of buffers is exceeded, and cannot be added

ArrayBroadcastChannel与 ConflatedBroadcastChannel

ArrayBroadcastChannel and ConflatedBroadcastChannel are both the implementation of BroadcastChannel

launch(UI) {
    
    
    val bc = ConflatedBroadcastChannel<String>()
    bc.send("A")
    bc.send("B")

    // Consumer1
    launch(newSingleThreadContext("threadA")) {
    
    
        val subscription1: SubscriptionReceiveChannel<String> = bc.openSubscription()
        subscription1.consumeEach {
    
    
            Log.d("KT-BROADCAST-1","${
      
      it}")
        }
    }

    // Consumer2
    launch(newSingleThreadContext("threadB")) {
    
    
        val subscription2: SubscriptionReceiveChannel<String> = bc.openSubscription()
        subscription2.consumeEach {
    
    
            Log.d("KT-BROADCAST-2","${
      
      it}")
        }
    }

    delay(2000)
    bc.send("C")
    bc.send("D")

    Log.d("KT-BROADCAST-LATEST","${
      
      bc.valueOrNull}")
}
KT-BROADCAST-1  B
KT-BROADCAST-2  B
KT-BROADCAST-1  C
KT-BROADCAST-2  C
                D
KT-BROADCAST-LATEST  D
KT-BROADCAST-1  D

Able to receive the last data B before subscribing


Contrast with Rx


Coroutine's Channel and Rx's Observable/Subject control relationship

Rx Observable/Subject Coroutine Channel
Channel Observable (Hot Stream)
ArrayBroadChannel PublishSubject (Hot Stream)
ConflatedBroadcastChannel BehaviorSubject (Hot Stream)

To summarize briefly:

  • Observable interface can be transformed into Channel
  • Subject interface can be transformed into BroadChannel

The repo of MVVM transformed with Coroutine is probably the following feeling:

// Address地址
interface AddressRepository {
    
    
   // 订阅Address的变更通知
   val addresses: BroadcastChannel<Array<Address>>

   // 异步获取指定条件的Address
   fun filter(criteria: String): Channel<Array<Address>>

   // 异步判断Address是否存在
   suspend fun exists(name: String): Boolean
}

At last


This article mainly introduces the basic idea of ​​using Coroutine to replace Rx's Observable, Subject and other interfaces. Of course, in some scenarios, RxJava can still play an irreplaceable role with its rich operators. For example, when a large amount of data is received in a short time, you can use debounce for anti-shake; when you need to process multiple asynchronous streams, use concat, combineLatestetc. to implement some complex logic.

Guess you like

Origin blog.csdn.net/vitaviva/article/details/107885555