RxJava has been widely known and used as a streaming asynchronous framework. Now Kotlin provides us with a new choice, Coroutine, which can replace RxJava in some scenarios. This article compares the differences in the use of the two, hoping that users of RxJava can understand the advantages of Coroutine, which can be used as a new option in the project. This article is suitable for the following readers:
Before Flow came out, Coroutine could not be used to Stream data, so it could not replace Rx. The emergence of Flow makes up for the shortcomings, and Coroutine can basically completely replace the use of Rx. The powerful operators in Rx are also supported in Flow, such asmap、filter、scan、retryWait. RxObservableType can be replaced by Flow, RxSingle、CompletableIt can be replaced by a simpler Coroutine processing method:
| One-time data | Stream data (Cold) | Stream data (Hot) | |
|---|---|---|---|
| RxJava | Single/Maybe/Completable | Observable | Subject |
| Coroutine | Suspend Functions | Flow | Channel |
Through the above table, you can easily understand which types of data processing are used by Rx and Coroutine.
Use Single in Rx when processing data that is only transmitted once, such as a single DB operation or Http request, etc.
fun getRequest(): Single<String> {
return Single.create<String> { emitter ->
// ...
emitter.onSuccess(body.message)
}
}
Coroutine can use the suspend function to return the result synchronously after performing any processing like a normal function, which is very refreshing:
suspend fun getRequest(): String{
// Time-consuming processing
return body.message
}
It should be noted that Single is a Cold Stream, which is triggered when subscribing, and the suspend function is not required. Therefore, scenarios that require Cold triggering must use Coroutine's Channel
Sometimes we will convert a Callback into Rx Single
fun getRequest(): Single<String> {
return Single.create<String> { emitter ->
val callback = object: Callback {
override fun onNextValue(value: String) {
emitter.onSuccess(value)
}
override fun onApiError(cause: Throwable) {
emitter.onFailure(Exception("API Error", cause))
}
}
}
}
Similarly, we can convert a Callback into Coroutine's suspend function:suspendCoroutineThe function is suspended after the call, and then passedresumeorresumeWithExceptionResume execution
suspend fun getMessages(): String {
return suspendCoroutine<String> { coroutine ->
val callback = object: Callback {
override fun onNextValue(value: String) {
coroutine.resume(value)
}
override fun onApiError(cause: Throwable) {
coroutine.resumeWithException(Exception("API Error", cause))
}
}
}
}
Rx does not return data directlyonCompleteWhen using Completable
fun updateRequest(): Completable {
return Completable.create { emitter ->
// ...
emitter.onComplete()
}
}
Return without returning data
suspend fun updateRequest() {
// ...
return
}
Observable.just()Used to transmit one-time data, actually equivalent to Single
fun getMessage(): Observable<String> {
return Observable.just("rxData")
}
can useflowOfreplaceObservable.just()
fun getMessage(): Flow<String> {
return flowOf("flowData")
}
When multiple data needs to be transmitted, useObservable.create()
fun getMessages(): Observable<String> {
return Observable.create { observer ->
for(/*Loop multiple times*/) {
observer.onNext("rxData")
}
observer.onComplete()
}
}
The flow block will be automatically called before exiting executioncompleted
fun getMessages(): Flow<String> {
return flow {
repeat(/*Loop multiple times*/) {
emit("flowData")
}
}
}
You can convert a Callback with multiple callbacks into an Observable, throughonNextData will be sent in the form of Stream
fun getMessages(): Observable<String> {
return Observable.create { observer ->
val callback = object: Callback {
override fun onNextValue(value: String) {
observer.onNext(value)
}
override fun onApiError(cause: Throwable) {
observer.onError(Exception("API Error", cause))
}
override fun onComplete() = observer.onComplete()
}
}.doFinally {
//Post-processing at the end of the stream
}
}
fun getMessages(): Flow<String> {
return callbackFlow {
val callback = object: Callback {
override fun onNextValue(value: String) {
offer(value)
}
override fun onApiError(cause: Throwable) {
cancel(Exception("API Error", cause))
}
override fun onComplete() = channel.close()
}
}
awaitClose {
//Post-processing at the end of the stream
}
// Note that there should be no other code after awaitClose{...}
/* End */
}
BehaviorSubject()Is a Hot Stream holding a recent data
class SampleClass {
private val behaviorSubject: BehaviorSubject<Int> = BehaviorSubject.create(0)
}
class SampleClass {
private val channel: Channel<Int> = Channel(Channel.CONFLATED)
}
class SampleClass {
private val replaySubject: ReplaySubject<Int> = ReplaySubject.create()
}
class SampleClass {
private val channel: Channel<Int> = Channel(Channel.UNLIMTED)
}
Single.just(1).subscribeBy(
onNext = { v ->
// ...
}
)
suspend fun sample(): Int {
return 1
}
//...
launch {
sample() // want to call ordinary functions
}
fun getFlow(): Flow<Int> = flowOf(1)
//...
launch {
getFlow().collect{ v ->
// ...
}
}
fun getChannel(): ReceiveChannel<Int> {
return this.channel
}
//...
launch {
getChannel()
.consumeAsFlow() // Convert Hot Stream to Cold Stream, which is equivalent to flow
.collect { v ->
// ...
}
}
fun getMessages(): Observable<String> {
return Observable.create<String> { observer ->
val callback = object: Callback {
override fun onNextValue(value: String) {
observer.onNext(value)
}
override fun onApiError(cause: Throwable) {
observer.onError(Exception("API Error", cause))
}
override fun onComplete() = observer.onComplete()
}
}.doFinally {/*...*/}
}
fun getRequest(url: String): Single<String> {
return Single.create { emitter ->
// http request
emitter.onSuccess(message.body)
}
}
fun insertDB(body: String): Completable {
return Completable.create { emitter ->
// Update DB
emitter.onComplete()
}
}
//...
getMessages()
.flatMapSingle { getRequest(it) }
.flatMapCompletable { insertDB(it) }
.subscribeBy(onComplete = {})
fun getMessages(): Flow<String> {
return callbackFlow {
val callback = object : Callback {
override fun onNextValue(value: String) {
offer(value)
}
override fun onApiError(cause: Throwable) {
cancel(Exception("API Error", cause))
}
override fun onComplete() = channel.close()
}
}
awaitClose {/*...*/}
}
suspend fun getRequest(url: String): String {
// http request
return message.body
}
suspend fun insertDB(body: String) {
// Insert DB
return
}
//...
launch {
getMessages().onEach { url ->
val body = getRequest(rul)
insertDB(body)
}.collect {}
}
byzipInitiate two Http requests at the same time
fun getHogeRequest(): Single<String> {
return Single.create { emitter ->
// HTTP request
emitter.onSuccess(message.body)
}
}
fun getFugaRequest(): Single<String> {
return Single.create { emitter ->
// HTTP request
emitter.onSuccess(message.body)
}
}
//...
Single.zip(getHogeRequest(),
getFugaRequest()).
subscribeBy(
onNext = {
v: Pair<String, String> ->
}
)
asyncStart two Http requests at the same time, throughawaitAll()Wait for two requests to return results
suspend fun getHogeRequest(): String {
// HTTP request
return message.body
}
suspend fun getFugaRequest(): String {
// HTTP request
return message.body
}
//...
launch {
val resultList: List<String> = listOf(
async{ getHogeRequest() },
async{ getFugaRequest() })
.awaitAll()
}
val disposable = hogeObservable().subscribeBy(onNext = {})
disposable.dispose()
val job = launch {
hogeFlow.collect {}
}
job.cancel()
Finally, summarize the points of attention when Coroutine replaces Rx
Each type in RxJava, whether it is Observable, Single or Subject, supports the same API: they all support the same operators, and they can all be subscribed.
In contrast to Coroutine, the suspend function, Flow or Channel are all based on the principle of function suspend, but the supported APIs are quite different. This may be a threshold in the use of Coroutine. In addition, you need to understandCoroutineScopeversusCoroutineContextSuch concepts are the basis for the smooth use of Coroutine.
1: Core thinking The core thinking of RXJAVA1.0 and RXJAVA2.0 is the observer mode, but RXJAVA2.0 optimizes some methods in the basis of RXJAVA1.0, which is convenient for developers to better underst...
RxJava1 The open source library RXJAVA for the response programming is already fired for a long time. After being transplanted to a lot of platforms such as RXANDROID, it has been aware of it in recen...
Today's Android projects generally use the combination of Retrofit+RxJava to implement network interface requests and data presentation. This feature is also easily implemented through the Kotlin lang...
Foreword Guide to Retrofit articles: Android Retrofit source code series (1) ~ Principle analysis Android Retrofit source code series (2) ~ custom CallAdapter Android Retrofit source code series (3) ~...
#Use generator to implement coroutine Why is the generator? Because the generator is similar to the pause feature. Yield Look at a simple generator first. Generator generated and received values yield...
yield When the yield keyword appears in a function, then this function is a generator. You can iterate with a for loop or a next() function. Yield can receive variables passed in from the outside worl...
C # foreach is actually achieved through a process of continuous iterative set of outputs. It is a condition to be met 1 foreach object has GetEnumarator iterative method to return a collection...
Before we learned the concepts of threads and processes, we learned that in the operating system, processes are the smallest unit of resource allocation, and threads are the smallest unit of CPU sched...
Personal blog navigation page (clickRightlinkTo open the personal blog):Daniel takes you to the technology stack Coroutine, also known as micro-thread, fiber, English name Coroutine; in one sent...
yield and yield from The dictionary gives two definitions for the verb "to yield": yield and concession. For yield in Python generators, both meanings are true. The yield item line of code w...