Part of this article is overdue and up to dateRxHttp + coroutines
Documentation, please checkRxHttp, a more elegant coroutine experience than Retrofit
1, the preface
RxHttp has been updated to V2.3.3 and has passed 2200+ Star on Github. For a project that has been open source for a little over a year, It can be said that we have achieved a good result. Here, thanks for your support and affirmation. RxHttp will be better and better.
RxHttp already has a number of useful operators, such as timeout, retry, map, and so on. For example, flowOn, asFlow, filter, DISTINCT, sort and a series of operators are frequently used and support customization, which will be described next.
This article is too long, you can like the collection, like, hope you can give a star support, open source is not easy, grateful.
RxHttp&RxLife exchange group: 378530627
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: After dependency, remember rebuild (must), RxHttp class will be generated
This article covers only the coroutine-related parts of RxHttp. If you want to learn more about RxJava, read RxHttp’s stunning Http request framework
2, RxHttp coroutine use
2.1. Request trilogy
Any request, any return value, follows the request trilogy. To master the request trilogy, you master the essence of RxHttp, as follows:
Note: RxHttp2.2.0 version, has completely eliminated RxJava, using the plug-in method to replace, support RxJava2, RxJava3, details view RxHttp start
The coroutine requests three code representations
// coroutine to get the data returned by the interface and return it as String
val str = RxHttp.get("/service/...") //1, determine the request mode, optionally get, postXxx and other methods
.toStr() //2. Use the toXxx series of methods to determine the return type
.await() //3, use the await method to get the return value
Copy the code
Coroutine request trilogy in detail
-
The first step is to select get, postForm, postJson and other methods to determine the request mode. Then you can add parameters, files, request headers using the add, addFile, addHeader/ operators. Of course, we can call more operators to meet the business needs. Such as setCacheXxx, upload and so on to set the cache policy and listen to upload progress and so on
-
The second step is to call the toXxx series of methods to determine the return type. Commonly used are toStr, toClass, toList, More than 30 operators, including asFlow, retry, timeout, flowOn, filter, DISTINCT, and sort, can then be invoked to perform different business logic, as described in this article
-
Third, finally, we need to call any of the three operators of await, tryAwait, or awaitResult to get the return value. This step needs to be called in the coroutine environment
For example, we want to send a post form request, add one parameter/file/request header, and deserialize the returned data into a Student object with a timeout of 5 seconds. Failed to retry 2 times, each interval 1 second, the code is as follows:
val student = RxHttp
.postForm("/service/...") //1. Determine the request mode. You can select get, postXxx and other methods
.add("key"."value")
.addFile("file", File("... /1.png"))
.addHeader("headerKey"."headerValue")
.toClass<Student>() //2. Use the toXxx series of methods to determine the return type
.timeout(5000)
.retry(2.1000)
.await() //3, use the await method to get the return value
Copy the code
2.2 BaseUrl processing
RxHttp uses @defaultDomain and @domain annotations to configure the default and non-default Domain names as follows:
public class Url {
@DefaultDomain // Use this annotation to set the default domain name
public static String BASE_URL = "https://www.wanandroid.com";
/ / the name parameter in this will generate setDomainToGoogleIfAbsent method, can be arbitrarily specified name
// The className argument generates the RxGoogleHttp class, optionally named
@Domain(name = "Google", className = "Google")
public static String GOOGLE = "https://www.google.com";
}
Copy the code
Set www.wanandroid.com as the default domain name and www.google.com as the non-default domain name
More BaseUrl processing
// Send the request using the default domain name
RxHttp.get("/service/...")
.toSrt().await()
// Use the Google domain name method 1: The incoming URL directly carries the Google domain name
RxHttp.get("https://wwww.google.com/service/...")
.toSrt().await()
/ / by using Google domain way 2: call setDomainToGoogleIfAbsent method
RxHttp.get("/service/...")
.setDomainToGoogleIfAbsent()
.toSrt().await()
// Use the Google domain name method 3: directly use the RxGoogleHttp class to send the request
RxGoogleHttp.get("/service/...")
.toSrt().await()
Copy the code
Note: The domain name passed in manually has the highest priority, followed by a call to the setDomainToXxx method, before the default domain name is used
Dynamic domain name processing
// Re-assign the url value directly, and the change takes effect immediately
Url.BASE_URL = "https://www.baidu.com";
RxHttp.get("/service/...")
.toSrt().await()
/ / request url for https://www.baidu.com/service/ at this time...
Copy the code
2.3 Unified judgment of service code
I think most people’s interface return format is like this
class Response<T> {
var code = 0
var msg : String? = null
var data : T
}
Copy the code
The first step in getting this object is to make a judgment on code. If code! = 200(assuming that 200 is correct), it will get the MSG field and give the user some error. If it is equal to 200, it will get the DATA field to update the UI
val response = RxHttp.get("/service/...")
.toClass<Response<Student>>()
.await()
if (response.code == 200) {
// Get the data field (Student) and refresh the UI
} else {
// Get the MSG field and give an error message
}
Copy the code
If you think about a project with at least a dozen of these interfaces, it would be inelegant and disastrous if each interface read this judgment, and no one would do it. And for the UI, all you need is the data field. I don’t care about errors.
Is there any way to directly get the data field and make a uniform judgment on code? Yes, go straight to the code
val student = RxHttp.get("/service/...")
.toResponse<Student>() // Call this method to get the data field, which is the Student object
.await()
// Start updating the UI directly
Copy the code
As you can see, when you call the toResponse() method, you get the data field directly, which is the Student object.
At this point, I’m sure a lot of people are wondering,
-
Where is the business code judged?
-
When the service code is not 200, how to get the MSG field?
To this end, let’s answer the first question, where to judge the business code?
The toResponse() method is not provided internally by RxHttp, but is automatically generated by the annotation handler rxHTTP-compiler. It doesn’t matter. Just look at the code
@Parser(name = "Response")
open class ResponseParser<T> : AbstractParser<T> {
// The following two constructors are required
protected constructor() : super(a)constructor(type: Type) : super(type)
@Throws(IOException::class)
override fun onParse(response: okhttp3.Response): T {
val type: Type = ParameterizedTypeImpl[Response::class.java, mType] // Get the generic type
val data: Response<T> = convert(response, type) // Get the Response object
val t = data.data // Get the data field
if (data.code ! =200 || t == null) { // If code is not equal to 200, the data is incorrect and an exception is thrown
throw ParseException(data.code.toString(), data.msg, response)
}
return t
}
}
Copy the code
The above code only needs to focus on two things,
First, we start the class with the @parser annotation and name the Parser Response, so we have the toResponse() method (named as: to + the name set in the Parser annotation).
Second, in the if statement, we made a judgment that if code is not 200 or data is null, we will throw an exception with the code and MSG fields, so we will get those two fields in the exception callback
Then to answer the second question, how do I get the MSG field when code is not 200? Go straight to the code and see a complete example of sending a request using a coroutine
// The current environment is in the Fragment
fun getStudent(a) {
//rxLifeScope is in the rxLife-Coroutine library and needs to be relied on separately
rxLifeScope.launch({ // Start a coroutine with the launch method
val student = RxHttp.get("/service/...")
.toResponse<Student>()
.await()
}, {
// Exception callback, where it is Throwable
val code = it.code
val msg = it.msg
})
}
Copy the code
Note: RxLifeScope isRxLife-CoroutineLibrary classes, described in detail later in this article
It. Code and it. MSG are the two properties I extend for Throwable class.
val Throwable.code: Int
get() {
val errorCode = when (this) {
is HttpStatusCodeException -> this.statusCode // The Http status code is abnormal
is ParseException -> this.errorCode // The service code is abnormal
else -> "1"
}
return try {
errorCode.toInt()
} catch (e: Exception) {
-1}}val Throwable.msg: String
get() {
return if (this is UnknownHostException) { // The network is abnormal
"Currently no network, please check your network Settings."
} else if (
this is SocketTimeoutException // Okhttp global Settings timeout
|| this is TimeoutException // The timeout method in rxJava times out
|| this is TimeoutCancellationException // The coroutine times out
) {
"Connection timed out, please try again later"
} else if (this is ConnectException) {
"The network is not working, please try again later!"
} else if (this is HttpStatusCodeException) { // The request failed
"Http status code is abnormal"
} else if (this is JsonSyntaxException) { // The request succeeds, but the Json syntax is abnormal, causing parsing failure
"Data parsing failed. Please check if the data is correct."
} else if (this is ParseException) { // ParseException Indicates that the request was successful, but the data is incorrect
this.message ? : errorCode// MSG is empty, and code is displayed
} else {
"Request failed. Please try again later."}}Copy the code
In the ResponseParser parser, you need to change the if statement to determine the condition
2.4. Operation Description
Map conversion symbol
The Map operator is easy to understand. Both RxJava and coroutine Flow have the same function. It is used to transform objects.
val student = RxHttp.postForm("/service/...")
.toStr()
.map { it.length } / / String Int
.tryAwait() // Return Student? Object, that is, it may be empty
Copy the code
The timeout timeout
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/...")
.toResponse<Student>()
.timeout(3000) // The timeout duration is 3s
.await()
Copy the code
Retry failed retry
OkHttp provides us with a global failure retry mechanism, however, this is far from sufficient for our needs. For example, I have partial interfaces that require failure retries rather than global ones. I need to decide if I need to retry based on certain conditions; Or MAYBE I need to try again periodically, after a few seconds, etc
If we need to retry 2 times with an interval of 1 second when the network is abnormal, the code is as follows:
val student = RxHttp.postForm("/service/...")
.toResponse<Student>()
.retry(2.1000) { // Retry twice with 1s interval
it is ConnectException // If the network is abnormal, try again
}
.await()
Copy the code
The retry() method has three parameters, namely retry times, retry period, and retry condition, which have default values. The three parameters can be used as follows:
/** * 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, which defaults to true. Retry */ whenever an exception occurs
fun retry(
times: Int = Int.MAX_VALUE,
period: Long = 0,
test: suspend (Throwable) - >Boolean = { true})
Copy the code
Filter Filter operation
If the server returns list data, we can filter the list as follows:
val students = RxHttp.postForm("/service/...")
.toList<Student>()
.filter{ it.age > 20 } // Filter students over 20 years old
.await()
Copy the code
You can also use the filterTo operator to add the filtered data to the specified list, as shown below:
val list = mutableListOf<Student>()
val students = RxHttp.postForm("/service/...")
.toList<Student>()
.filterTo(list){ it.age > 20 } // Filter students over 20 years old
.await() // The list object returned is the list object we passed in
Copy the code
Distinct to heavy
This operator can redo the list returned by the server, as follows:
// Deduplicate according to the hashCode of the Student object
val students = RxHttp.postForm("/service/...")
.toList<Student>()
.distinct()
.await()
// Deduplicate the Student object based on its ID
val students = RxHttp.postForm("/service/...")
.toList<Student>()
.distinctBy { it.id }
.await()
// Add the deduplicated data to the specified list, and judge the data in the specified list when deduplicated
val list = mutableListOf<Student>()
val students = RxHttp.postForm("/service/...")
.toList<Student>()
.distinctTo(list) { it.id }
.await()
Copy the code
The sort order
Sorting sortXxx, sortedXxx two types of operators, the difference is that sortXxx within the list sorting, sorting out, return to itself, and sortedXxx list sorting, sorting out, return to the new list, here only to sortXxx introduction, as follows:
// Sort by ID order
val students = RxHttp.postForm("/service/...")
.toList<Student>()
.sortBy { it.id }
.await()
// Sort by id and age, id first, age second
val students = RxHttp.postForm("/service/...")
.toList<Student>()
.sortBy({ it.id }, { it.age })
.await()
// Return two collation objects and implement the collation rules yourself
val students = RxHttp.postForm("/service/...")
.toList<Student>()
.sortWith { student1, student2 ->
student1.id.compareTo(student2.id)
}
.await()
Copy the code
FlowOn Specifies the upstream thread
This operator, like the flowOn operator in Flow, specifies the upstream thread, as follows:
val students = RxHttp.postForm("/service/...")
.toList<Student>()
.sortBy { it.id } // The IO thread executes
.flowOn(Dispatchers.IO)
.distinctBy { it.id } // The Default thread executes
.flowOn(Dispatchers.Default)
.filter{ it.age > 20 } // The IO thread executes
.flowOn(Dispatchers.IO)
.flowOn(Dispatchers.Default)
.await()
Copy the code
AsFlow To Flow object
If you like Kotlin’s flow, then asFlow comes in handy, as follows:
RxHttp.postForm("/service/...")
.toList<Student>()
.asFlow()
.collect {
// Get the List
object
}
Copy the code
Note: After using the asFlow operator, you need to use collect instead of await operator
SubList, take interception list
SubList is used to intercept a certain list. If the interception range is out of bounds, an out-of-bounds exception is thrown. Take is used to take n data from 0, and return all data when n is less than, as follows:
val students = RxHttp.postForm("/service/...")
.toList<Student>()
.subList(1.10) // Capture 9 data
.take(5) // Take the first five out of nine
.await()
Copy the code
Async Asynchronous operations
We can use this operator if we want two requests to be in parallel, as follows:
// Obtain information about two students at the same time
suspend void initData() {
val asyncStudent1 = RxHttp.postForm("/service/...")
.toResponse<Student>()
.async(this) // This is a CoroutineScope object. Deferred
val asyncStudent2 = RxHttp.postForm("/service/...")
.toResponse<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
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/...")
.toResponse<Student>()
.delay(1000) // After the request returns, delay the return for 1s
.await()
val student = RxHttp.postForm("/service/...")
.toResponse<Student>()
.startDelay(1000) // Delay the request for 1s before sending it
.await()
Copy the code
OnErrorReturn, onErrorReturnItem Exception Default value
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/...")
.toResponse<Student>()
.timeout(100) // The timeout period is 100 milliseconds
.onErrorReturn {
// If there is a timeout exception, 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/...")
.toResponse<Student>()
.timeout(100) // The timeout period is 100 milliseconds
.onErrorReturnItem(Student())
.await()
Copy the code
AwaitResult return kotlin. The Result
I’ll just look at the code for this
val result: Result<Student> = RxHttp
.postForm("/service/...")
.toClass<Student>()
.awaitResult()
if (result.isSuccess) {
// The request succeeds and the Student object is obtained
val student = result.getOrThrow()
} else {
// An exception occurred in the request and the Throwable object was retrieved
val throwable = result.exceptionOrNull()
}
Copy the code
Once we have the kotlin.result object, we need to determine whether the request was successful or not and then execute the associated logic
TryAwait exception returns null
TryAwait will return null if an exception occurs, as follows:
val student = RxHttp.postForm("/service/...")
.toResponse<Student>()
.timeout(100) // The timeout period is 100 milliseconds
.tryAwait() // Return Student? Object, which is null if an exception occurs
Copy the code
Custom operators
RxHttp has a number of powerful and useful operators built in, but it is certainly not sufficient for all business scenarios, so it is time to consider custom operators
Custom takeLast operator
If we have such a requirement, the custom needs to fetch n pieces of data at the end of the list
Earlier we introduced the take operator, which starts at 0, takes n pieces of data, and returns them all if there are fewer than n pieces. Let’s look at the source code
fun <T> IAwait<out Iterable<T>>.take(
count: Int
): IAwait<List<T>> = newAwait {
await().take(count)
}
Copy the code
Code interpretation,
1. IAwait is an interface, as follows:
interface IAwait<T> {
suspend fun await(a): T
}
Copy the code
This interface has only an await() method, which returns the declared T
The newAwait operator simply creates an implementation of the IAwait interface as follows:
inline fun <T, R> IAwait<T>.newAwait(
crossinline block: suspend IAwait<T>. () - >R
): IAwait<R> = object : IAwait<R> {
override suspend fun await(a): R {
return this@newAwait.block()
}
}
Copy the code
3. Since we are extending the take method for an IAwait
> object, internally we call the await() method, which returns the Iterable
object, Finally implement Iterable < T > object extension methods take (Int) to get open yes from 0 n data, take (Int) is the system to provide the method, the source code is as follows:
public fun <T>ε―θΏδ»£<T>.take(n: Int): List<T> {
require(n >= 0) { "Requested element count $n is less than zero." }
if (n == 0) return emptyList()
if (this is Collection<T>) {
if (n >= size) return toList()
if (n == 1) return listOf(first())
}
var count = 0
val list = ArrayList<T>(n)
for (item in this) {
list.add(item)
if (++count == n)
break
}
return list.optimizeReadOnlyList()
}
Copy the code
Ok, back to the previous topic, how to customize an operation to achieve a list at the end of n pieces of data, less than n, return all
Look at the above take(int) source code, we can easily write the following code:
fun <T> IAwait<out List<T>>.takeLast(
count: Int
): IAwait<List<T>> = newAwait {
await().takeLast(count)
}
Copy the code
First, we extend the takeLast(Int) method on IAwait
>, then call newAwait to create an instance object of the IAwait interface, and then call await() to return the List
object. Finally, call the takeLast(Int) method that the system extends for List
Once defined, we can use it directly, as follows:
val students = RxHttp.postForm("/service/...")
.toList<Student>()
.takeLast(5) // Return all 5 pieces of data at the end of the list
.await()
Copy the code
The above operators are randomly combined
The above operators can be used in combination with each other at will, but they have different effects depending on the order in which they are called. Let me tell you that the above operators only affect upstream code.
Such as timeout and retry:
val student = RxHttp.postForm("/service/...")
.toResponse<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/...")
.toResponse<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/...")
.toResponse<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/...")
.toResponse<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.
3. Upload/download
RxHttp’s elegant manipulation of files is inherent, and still is in the context of coroutines. Nothing is more convincing than code
3.1 File upload
val result = RxHttp.postForm("/service/...")
.addFile("file", File("xxx/1.png")) // Add a single file
.addFile("fileList", ArrayList<File>()) // Add multiple files
.toResponse<String>()
.await()
Copy the code
You just need to add the File object through the addFile series method. That’s as simple as that. Want to listen to the upload progress? Add the upload operator as follows:
val result = RxHttp.postForm("/service/...")
.addFile("file", File("xxx/1.png"))
.addFile("fileList", ArrayList<File>())
.upload(this) { // This is the CoroutineScope object, i.e. the current coroutine object
//it is the Progress object
val process = it.progress // Progress 0-100 has been uploaded
val currentSize = it.currentSize // Uploaded size, unit: byte
val totalSize = it.totalSize // Total size to be uploaded. The unit is byte
}
.toResponse<String>()
.await()
Copy the code
Let’s look at the complete signature of the upload method as follows:
/** * Call this method to listen for upload progress *@paramCoroutine CoroutineScope object used to start a coroutine and to call back progress, depending on the thread of the coroutine *@paramNote: This method only works in coroutine environments */
fun RxHttpFormParam.upload(
coroutine: CoroutineScope? = null,
progress: (Progress) - >Unit
):RxHttpFormParam
Copy the code
3.2. File download
Let’s take a look at downloads, directly pasting code
val localPath = "sdcard//android/data/.... /1.apk"
val student = RxHttp.get("/service/...")
.toDownload(localPath) // Download requires passing in the local file path
.await()
Copy the code
Call toDownload(String), pass in the local file path, want to listen to the download progress? Also simple, as follows:
val localPath = "sdcard//android/data/.... /1.apk"
val student = RxHttp.get("/service/...")
.toDownload(localPath, this) { This is a CoroutineScope object
//it is the Progress object
val process = it.progress // Progress 0-100 has been downloaded
val currentSize = it.currentSize // Downloaded size, unit: byte
val totalSize = it.totalSize // The total size to be downloaded is in byte
}
.await()
Copy the code
Take a look at the toDownload method’s full signature
/ * * *@paramDestPath Local storage path *@paramCoroutine CoroutineScope object used to start a coroutine and to call back progress, depending on the thread of the coroutine *@paramProgress Progress callback */
fun IRxHttp.toDownload(
destPath: String,
coroutine: CoroutineScope? = null,
progress: (Progress) - >Unit
): IAwait<String>
Copy the code
If you need a breakpoint download, that’s ok, as a one-line code thing, as follows:
val localPath = "sdcard//android/data/.... /1.apk"
val student = RxHttp.get("/service/...")
.setRangeHeader(1000.300000) // Breakpoint download, set the start/end of the download location
.toDownload(localPath, this) { This is a CoroutineScope object
//it is the Progress object
val process = it.progress // Progress 0-100 has been downloaded
val currentSize = it.currentSize // Size, unit: byte
val totalSize = it.totalSize // The unit of the total size is byte
}
.await()
Copy the code
Old rule, look at setRangeHeader full signature
/** * Set breakpoint download start/end position *@paramStartIndex Start point of the breakpoint download *@paramEndIndex Indicates the end of the download. The default value is -1, that is, the default end value is * at the end of the file@paramConnectLastProgress Indicates whether to connect to the last download progress. This parameter is valid only for downloads with progress breakpoints */
fun setRangeHeader (
startIndex: Long,
endIndex: Long = 0L,
connectLastProgress: Boolean = false
)
Copy the code
This is the end of the basic Api for RxHttp coroutines, so the question is, all the apis that I’ve introduced are dependent on the coroutine environment, so how do I open the coroutine? In other words, I don’t know much about coroutines, you just need to ensure the premise of safety, tell how to use it, OK, then how to safely open a coroutine, do automatic exception catching, and page destruction, automatically close the coroutine and request
4. Coprogram opening and closing
Rxlife-coroutine, another open source library, will be introduced to enable/disable coroutines and automatically catch exceptions, depending on the following:
implementation 'com. LJX. Rxlife: rxlife - coroutine: 2.0.0'
Copy the code
In this article, we use the rxLifeScope property to open coroutines when we introduce uniform processing of business codes. What type is this? Look at the code
val ViewModel.rxLifeScope: RxLifeScope
get() {
val scope: RxLifeScope? = this.getTag(JOB_KEY)
if(scope ! =null) {
return scope
}
return setTagIfAbsent(JOB_KEY, RxLifeScope())
}
val LifecycleOwner.rxLifeScope: RxLifeScope
get() = lifecycle.rxLifeScope
Copy the code
As you can see, we’ve extended a property called rxLifeScope for both the ViewModel and the LifecycleOwner, of type rxLifeScope. I’m sure you all know about the ViewModel, so let’s just briefly talk about the LifecycleOwner interface. Our fragments and FragmentActivities both implement the LifecycleOwner interface, and our activities generally inherit from AppCompatActivity, AppCompatActivity inheritance in FragmentActivity, so we in FragmentActivity/fragments/ViewModel environment, can be used directly rxLifeScope open coroutines, as follows:
rxLifeScope.lanuch({
// Coroutine code block, run in the UI thread
}, {
// The exception callback. Any exception in the coroutine block will go directly here
})
Copy the code
Coroutines opened in this way will automatically close the coroutine when the page is destroyed, of course, if your coroutine code block also has RxHttp request code, when the coroutine is closed, it also closes the request, so in this case, all you need to know is how to open the coroutine, and nothing else.
Now, let’s look at the full signature of the rxlifescope.lanuch method
/ * * *@paramBlock Coroutine block of code that runs on the UI thread *@paramOnError exception callback, run on the UI thread *@paramThe onStart coroutine starts the callback, running on the UI thread *@paramThe onFinally coroutine end callback, which is called regardless of success/failure, runs on the UI thread */
fun launch(
block: suspend CoroutineScope. () - >Unit,
onError: ((Throwable) - >Unit)? = null,
onStart: (() -> Unit)? = null,
onFinally: (() -> Unit)? = null
): Job
Copy the code
As you can see, there are not only failure callbacks, but also start and end callbacks, which is really convenient for us to send requests, as follows:
rxLifeScope.launch({
// Coroutine code block
val students = RxHttp.postJson("/service/...")
.toResponse<List<Student>>()
.await()
// You can update the UI directly
}, {
// The exception callback is used to retrieve the Throwable object
}, {
// Start the callback, which can open the wait popover
}, {
// End the callback that can destroy the waiting popover
})
Copy the code
The above code runs in the UI thread, and when the request comes back, you can update the UI directly
Maybe you have doubt, I am not FragmentActivity/fragments/ViewModel environment, how to open coroutines, and how to shut down, actually is also very simple, as follows:
val job = RxLifeScope().launch({
val students = RxHttp.postJson("/service/...")
.toResponse<List<Student>>()
.await()
}, {
// The exception callback is used to retrieve the Throwable object
}, {
// Start the callback, which can open the wait popover
}, {
// End the callback that can destroy the waiting popover
})
job.cancel() // Close the coroutine
Copy the code
First, we need to manually create the RxLifeScope() object and then open the coroutine. Second, after opening the coroutine, we can get the Job object, which we need to use to manually close the coroutine. Nothing else is different.
Coroutine multitasking
As we know, the biggest advantage of coroutines is the ability to write asynchronous logic in seemingly synchronous code, which makes it very elegant to implement multi-tasking scenarios such as parallel/serial requests
5.1 coroutine serial multiple requests
Suppose we have a scenario where we first get the Student object and then get the Student’s list of family members through studentId, which depends on the former, which is a typical serial scenario
Let’s see how coroutines solve this problem, as follows:
class MainActivity : AppCompatActivity() {
// Start the coroutine and send the request
fun sendRequest(a) {
rxLifeScope.launch({
// Currently running in the coroutine, and running in the main thread
val student = getStudent()
val personList = getFamilyPersons(student.id) // Query family member information by student Id
// Update the UI directly after receiving the relevant information, such as:
tvName.text = student.name
}, {
// Throwable Throwable Throwable Throwable Throwable Throwable Throwable Throwable
it.show("Sending failed, please try again later!") // The show method is an extended method in Demo})}// Hang up method to get student information
suspend fun getStudent(a): Student {
return RxHttp.get("/service/...")
.add("key"."value")
.addHeader("headKey"."headValue")
.toClass<Student>()
.await()
}
// Hang up method to get the information of family members
suspend fun getFamilyPersons(studentId: Int): List<Person> {
return RxHttp.get("/service/...")
.add("studentId"."studentId")
.toClass<List<Person>>()
.await()
}
}
Copy the code
Let’s focus on the coroutine code block, so we get the Student object from the first request, and then we get the studentId, and we send the second request to get the list of learning family members, and when we get that, we can directly update the UI, ok, so if it looks synchronous, we write asynchronous logic.
In a serial request, whenever an exception occurs on one of the requests, the coroutine closes (along with the request), stops executing the rest of the code, and then goes through the exception callback
5.2 coroutine parallel multiple requests
Request parallelization, in real development, is also common, in an Activity, we often need to get a variety of data to show to the user, and these data, are sent by different interfaces.
If we have such a page, the top is a horizontally scrolling Banner bar, and the learning list is displayed below the Banner bar, then there are two interfaces, one is to get the list of Banner bars, the other is to get the list of learning bars. They are independent of each other and can be executed in parallel, as follows:
class MainActivity : AppCompatActivity() {
// Start the coroutine and send the request
fun sendRequest(a) {
rxLifeScope.launch({
// Currently running in the coroutine, and running in the main thread
val asyncBanner = getBanners(this) // The Deferred
> object is returned
val asyncPersons = getStudents(this) // Return the Deferred
> object
val banners = asyncBanner.await() // Return the List
object
val students = asyncPersons.await() // Return the List
object
// Start updating the UI
}, {
// Throwable Throwable Throwable Throwable Throwable Throwable Throwable Throwable
it.show("Sending failed, please try again later!") // The show method is an extended method in Demo})}// Hang up method to get student information
suspend fun getBanners(scope: CoroutineScope): Deferred<List<Banner>> {
return RxHttp.get("/service/...")
.add("key"."value")
.addHeader("headKey"."headValue")
.toClass<List<Banner>>()
.async(scope) // Note the use of the async operator
}
// Hang up method to get the information of family members
suspend fun getStudents(scope: CoroutineScope): Deferred<List<Student>> {
return RxHttp.get("/service/...")
.add("key"."value")
.toClass<List<Student>>()
.async(scope) // Note the use of the async operator}}Copy the code
The async asynchronous operator is used in both of the hang up methods in the code above, so the two requests are sent in parallel, then we get the Deferred
object, we call the await() method, we get the list of banners and the list of students, and we can update the UI directly.
To highlight
In parallel, as in serial, if an exception occurs on one of the requests, the coroutine closes (along with the request), stops executing the rest of the code, and then goes through the exception callback. You can use the onErrorReturn and onErrorReturnItem operators above to give a default object when an exception occurs, or use the tryAwait operator to return a value and return null when an exception occurs. This does not affect the execution of other requests.
6, summary
After reading this article, I believe you have understood the elegance and simplicity of RxHttp, unified processing of business code, retry failure, timeout, file upload/download and progress monitoring, to the rxLifeScope coroutine open/close/exception handling/multitasking, everything is so elegant.
In fact, RxHttp is much more than that. This article only explains the coroutine related things of RxHttp, more elegant features such as: public parameters/request header addition, request encryption and decryption, caching, and so on, please check out
RxHttp is an impressive Http request framework
RxHttp Optimal solution for network Http cache
Finally, open source is not easy, write less, still need to bother everybody to give this Wen Dian praise, if you can, give a star again, I would be grateful, π π π π π π π π π π π π