Part of this article is overdue and up to dateRxHttp + coroutinesDocumentation, please checkRxHttp, a more elegant coroutine experience than Retrofit

1, the preface

The addition of coroutine support to RxHttp in V2.0 was well received by Kotlin users, who were impressed that coroutine requests could be so elegant and more than a little more powerful than Retrofit, but was it enough? Far from enough, why, because there are still pain points to solve, for this reason, I also collected a few current network requests encountered pain points, as follows:

  • Asynchronous operations, coroutines already provide for usasyncThe operator handles asynchrony, but when used, it has to be wrapped once each time, which is unacceptable
  • Timeouts and retries are rare, but they happen to almost every developer, and when they do, it can be annoying if there is no API for them
  • Request start/end delay, which is not often, but it’s not uncommon to meet a lot of people, and it’s a real hassle to handle on your own
  • In request parallelism, suppose there are two or more requests A and B, and they are not dependent on each other. However, in coroutine, if A request is abnormal, the coroutine will be aborted and B will also be aborted. This is the result we do not want to see. The usual practice is to exception every request so that the coroutine does not end if an exception occurs. But each request needs to be processed individually, and writing it can be a pain in the neck

And so on, in fact, there are many small details of the problem, here is not a list.

Because of these problems, RxHttp version V2.2.0 is here. The main changes are as follows

  • Add a series of very useful operators, such as:asysn,timeout,retry,tryAwait, etc.
  • RxHttp supports both RxJava2 and RxJava3. RxHttp supports both RxJava2 and RxJava3
  • Extracting RxLieScope into a separate library that handles coroutine on/off/exception handling, which is covered separately later in this article

Gradle rely on

1, will be selected

// Must be used when kapt relies on rxhttp-compiler
apply plugin: 'kotlin-kapt'

android {
    // Must, Java 8 or higher
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    implementation 'com. LJX. RXHTTP: RXHTTP: 2.5.2'
    implementation 'com. Squareup. Okhttp3: okhttp: 4.9.0' // From RXHTTP v2.2.2, you need to manually rely on okhttp
    kapt 'com. LJX. RXHTTP: RXHTTP - compiler: 2.5.2' // Generate RxHttp class, pure Java project, please use annotationProcessor instead of kapt
 }
Copy the code

2, optional

android {
    defaultConfig {
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [
                    rxhttp_package: 'rxhttp'.// Optional, specify the RxHttp class package name
                    // Pass the version of RxJava that you depend on. You can pass rxJavA2, rxJavA3, but you must if you depend on RxJava
                    rxhttp_rxjava: 'rxjava3'

                ]
            }
        }
    }
}
dependencies {
    implementation 'com. LJX. Rxlife: rxlife - coroutine: 2.0.1' // Manage the coroutine life cycle, page destruction, close requests
    
    //rxjava2 (rxjava2 /Rxjava3)
    implementation 'the IO. Reactivex. Rxjava2: rxjava: 2.2.8'
    implementation 'the IO. Reactivex. Rxjava2: rxandroid: 2.1.1'
    implementation 'com. LJX. Rxlife2: rxlife - rxjava: 2.0.0' // Manage the RxJava2 lifecycle, page destruction, close requests

    //rxjava3
    implementation 'the IO. Reactivex. Rxjava3: rxjava: 3.0.6'
    implementation 'the IO. Reactivex. Rxjava3: rxandroid: 3.0.0'
    implementation 'com. LJX. Rxlife3: rxlife - rxjava: 3.0.0' // Manage the RxJava3 lifecycle, page destruction, close requests

    RxHttp has GsonConverter built in by default
    implementation 'com. LJX. RXHTTP: converter - fastjson: 2.5.2'
    implementation 'com. LJX. RXHTTP: converter - Jackson: 2.5.2'
    implementation 'com. LJX. RXHTTP: converter - moshi: 2.5.2'
    implementation 'com. LJX. RXHTTP: converter - protobuf: 2.5.2'
    implementation 'com. LJX. RXHTTP: converter - simplexml: 2.5.2'
}
Copy the code

Note: For pure Java projects, use annotationProcessor instead of Kapt; The RxHttp class is generated after the dependency is over. Remember rebuild

Welcome to join the RxHttp&RxLife group: 378530627

2. Request a trilogy

For those of you who have never heard of RxHttp before, here is the RxHttp request flow chart. Remember this diagram and you will have the essence of RxHttp as follows:The code says

val str = RxHttp.get("/service/...") // The first step is to determine the request mode. You can select postForm, postJson, and other methods
    .toStr()    // The second step is to confirm the return type, which represents a mandatory String
    .await()    // Next, use the await method to get the return value
Copy the code

So, isn’t that easy?

3. RxHttp operator

3.1 if a retry fails, try again

This operator is very powerful, not only for failure retries, but also for periodic failure retries, which are repeated after a few seconds. See the full method signature

/** * Retry on failure. This method is only valid when coroutines are used *@paramTimes Indicates the number of retries. By default, Int.MAX_VALUE indicates repeated retries@paramPeriod Retry period. The default value is 0. The unit is milliseconds *@paramTest Retry condition. The default value is null, that is, retry unconditionally */
fun retry(
    times: Int = Int.MAX_VALUE,
    period: Long = 0,
    test: ((Throwable) - >Boolean)? = null
)
Copy the code

The retry() method has three parameters: retry times, retry period, and retry condition. Each parameter has its default value. The three parameters can be used randomly, for example:

retry()    // Retry unconditionally and continuously
retry(2)   // Retry twice
retry(2.1000)   Retry twice at an unconditional interval of 1s
retry { it is ConnectException } // Conditional, uninterrupted, and always retry
retry(2) { it is ConnectException }  // Conditional, uninterrupted, retry 2 times
retry(2.1000) { it is ConnectException }  // If yes, the interval is 1s and retry twice
retry(period = 1000) { it is ConnectException } // If yes, try again for 1s
Copy the code

If we need to retry, we will return true. If we don’t, we will return false. If we don’t, we will return false

val student = RxHttp.postForm("/service/...")
    .toClass<Student>()
    .retry(2.1000) {       // Retry twice with 1s interval
        it is ConnectException   // If the network is abnormal, try again
    }                                             
    .await()                     
Copy the code

3.2. Timeout Times out

OkHttp provides global read, write, and connection timeouts. Sometimes we need to set a different timeout period for a request. In this case, we can use the timeout(Long) method of RxHttp.

val student = RxHttp.postForm("/service/...")
    .toClass<Student>()
    .timeout(3000)      // The timeout duration is 3s
    .await()                       
Copy the code

3.3. Async The asynchronous operator

If we have two requests that need to be parallel, we can use this operator as follows:

// Obtain information about two students at the same time
suspend void initData() {
  val asyncStudent1 = RxHttp.postForm("/service/...")
    .toClass<Student>()
    .async(this)   // This is a CoroutineScope object. Deferred
      
    
  val asyncStudent2 = RxHttp.postForm("/service/...")
    .toClass<Student>()
    .async(this)   // This is a CoroutineScope object. Deferred
      

  // Then call the await method to get the object
  val student1 = asyncStudent1.await()
  val student2 = asyncStudent2.await()
} 
Copy the code

3.4, delay, startDelay Delay

The delay operator is a delay of some time after the request has finished. The startDelay operator, on the other hand, delays sending the request for a period of time, as follows:

val student = RxHttp.postForm("/service/...")
    .toClass<Student>()
    .delay(1000)      // After the request returns, delay the return for 1s
    .await()       
    
val student = RxHttp.postForm("/service/...")
    .toClass<Student>()
    .startDelay(1000)     // Delay the request for 1s before sending it
    .await()     
Copy the code

3.5 Default value of onErrorReturn and onErrorReturnItem exception

In cases where we don’t want an exception to be called, we can use the default values with both operators, as follows:

// Give default values based on exceptions
val student = RxHttp.postForm("/service/...")
    .toClass<Student>()
    .timeout(100)      // The timeout period is 100 milliseconds
    .onErrorReturn {
        // If the exception is timeout, the default value is given, otherwise, the original exception is thrown
        return@onErrorReturn if (it is TimeoutCancellationException)
            Student()                                              
        else                                                        
            throw it                                                
    }
    .await()
    
// Return the default value whenever an exception occurs
val student = RxHttp.postForm("/service/...")
    .toClass<Student>()
    .timeout(100)      // The timeout period is 100 milliseconds
    .onErrorReturnItem(Student())
    .await()
Copy the code

3.6. TryAwait exception returns null

TryAwait is useful if you do not want to return the default value when an exception occurs and do not want the exception to affect the execution of the program. It returns null when an exception occurs, as follows:

val student = RxHttp.postForm("/service/...")
    .toClass<Student>()
    .timeout(100)      // The timeout period is 100 milliseconds
    .tryAwait()     // Return Student? Object, that is, it may be empty
Copy the code

3.7 map conversion symbol

The Map operator is easy to understand. Both RxJava and coroutine Flow operators have the same function. It is used to transform objects, as follows:

val student = RxHttp.postForm("/service/...")
    .toStr()
    .map { it.length }  / / String Int
    .tryAwait()     // Return Student? Object, that is, it may be empty
Copy the code

3.8. The above operators are optional

The above operators can be used in conjunction with each other, but the effect is different depending on the order in which they are called. Quietly, the above operators will only affect the upstream code.

Such as timeout and retry:

val student = RxHttp.postForm("/service/...")
    .toClass<Student>()
    .timeout(50)
    .retry(2.1000) { it is TimeoutCancellationException }                                  
    .await()                       
Copy the code

The above code, whenever a timeout occurs, will retry, at most twice.

If (timeout) and (retry) are interchanged, the difference is as follows:

val student = RxHttp.postForm("/service/...")
    .toClass<Student>()
    .retry(2.1000) { it is TimeoutCancellationException }       
    .timeout(50)                                  
    .await()                       
Copy the code

At this point, if the request is not completed within 50 milliseconds, a timeout exception will be raised and an exception callback will be performed without a retry. Why is that? The reason for this is simple: the timeout and retry operators only work for upstream code. Such as the retry operator, downstream exceptions are not caught, which is why timeout in a retry case does not trigger the retry mechanism.

Look at the timeout and startDelay operators

val student = RxHttp.postForm("/service/...")
    .toClass<Student>()
    .startDelay(2000)       
    .timeout(1000)                                  
    .await()                       
Copy the code

The above code is bound to trigger a timeout exception because startDelay is 2000 milliseconds late and the timeout is only 1000 milliseconds. But when you switch places, it’s different, as follows:

val student = RxHttp.postForm("/service/...")
    .toClass<Student>()
    .timeout(1000)    
    .startDelay(2000)       
    .await()                       
Copy the code

The above code normally gets the return value correctly, why? The reason for this is simple. As mentioned above, the operator only affects the upstream. It does not care about the downstream startDelay.

4, coroutine open/close/exception processing

In the example above, we use await/tryAwait operators to get the return value of the request. These are both suspend functions that need to be called inside another suspend function or coroutine. Therefore, we provide the RxLifeScope library to handle coroutine opening, closing and exception handling. The usage is as follows:

In FragemntActivity/fragments/ViewModel environment

In this case, open the coroutine by calling the Lanuch method on the rxLifeScope object as follows:

rxLifeScope.lanuch({
    // Coroutine code block, run in the UI thread
    val student = RxHttp.postForm("/service/...")
    .toClass<Student>()                      
    .await()   
    // Update the UI directly
}, {
    // The exception callback is used to retrieve the Throwable object
})
Copy the code

The above code will automatically close the coroutine and the request when the page is destroyed, without worrying about memory leaks

The FragemntActivity/fragments/ViewModel environment

In this case, we need to manually create the RxLifeScope object and then call the Lanuch method to open the coroutine

val job = RxLifeScope().lanuch({
    // Coroutine code block, run in the UI thread
    val student = RxHttp.postForm("/service/...")
    .toClass<Student>()                      
    .await()   
    // Update the UI directly
}, {
    // The exception callback is used to retrieve the Throwable object
})

// Close the coroutine at the appropriate time
job.cancel()
Copy the code

Since the above code is not bound to the life cycle, we need to manually close the coroutine at the appropriate time. When the coroutine closes, the request closes

Listen for coroutine start/end callbacks

In the lanuch method above, we pass in the coroutine run and exception callbacks. We can also pass in the coroutine start and end callbacks, as follows:

rxLifeScope.launch({                                      
    // Coroutine code block
    val student = RxHttp.postForm("/service/...")
        .toClass<Student>()
        .await()   
    // Update the UI directly
}, {                                                      
    // The exception callback, where you can get the Throwable object, runs on the UI thread
}, {                                                     
    // Start the callback, which can open the wait popover, running on the UI thread
}, {                                                     
    // The end callback can destroy the wait popover that runs on the UI thread
})                                                       
Copy the code

The above callbacks all run on the UI thread

5, summary

As you can see, the previous articles mentioned timeout/retry problem in the beginning, with a timeout/retry, delay in delay/startDelay, abnormal coroutines don’t want to interrupt the operation, with onErrorReturn/onErrorReturnItem or tryAwait, in short, Everything is so elegant.

The elegance of RxHttp is much more than that. The processing of BaseUrl, file upload/download/progress monitoring, cache processing, uniform business code judgment and so on, are all amazing.

See the following article for more features

Coroutine usage: RxHttp, a more elegant coroutine experience than Retrofit

RxHttp is an Http request framework that makes your eyes pop

RxHttp Optimal solution for network Http cache

Finally, open source is not easy, writing articles is not easy, if you feel good RxHttp or brought you help, welcome to like the collection, in case of need, if you can, and give a star, I will be grateful, 🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏