Part of this article is overdue and up to dateRxHttp + coroutines
Documentation, 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 us
async
The 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, 🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏🙏