1, the preface

In previous versions, RxHttp has provided RxHttp + Await coroutine and RxHttp + RxJava two request modes. This time, RxHttp seamlessly ADAPTS Flow, and RxHttp + Flow coroutine is used together to make requests simpler. RxHttp has collected 3 carriages (Flow, Await, RxJava), and each carriage follows the request trilogy. To master the request trilogy, you have mastered the essence of RxHttp.

RxHttp&RxLife exchange group (group number: 378530627, there will be frequent technical exchange, welcome to join the group)

This article only introduces the use of RxHttp + Flow, more functions please see

RxHttp is an amazing Http request framework (Basics)

RxHttp, a more elegant coroutine experience than Retrofit (RxHttp + Await)

RxHttp is perfect for Android 10/11 upload/download/progress monitoring

RxHttp Optimal solution for Http caching on the whole network

Gradle rely on

1, will be selected

Add the jitpack to your project’s build.gradle file as follows:

allprojects {
    repositories {
        maven { url "https://jitpack.io"}}}Copy the code
// Required when using the RxHTTP-Compiler with kapt
apply plugin: 'kotlin-kapt'

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

dependencies {
    implementation 'com. Squareup. Okhttp3: okhttp: 4.9.1'
    implementation 'com. Making. Liujingxing. RXHTTP: RXHTTP: 2.7.0'
    kapt 'com. Making. Liujingxing. RXHTTP: RXHTTP - compiler: 2.7.0' // generate RxHttp class, pure Java project, please use annotationProcessor instead of kapt
 }
Copy the code

2, optional

RxHttp has GsonConverter built in by default
implementation 'com. Making. Liujingxing. RXHTTP: converter - fastjson: 2.7.0'
implementation 'com. Making. Liujingxing. RXHTTP: converter - Jackson: 2.7.0'
implementation 'com. Making. Liujingxing. RXHTTP: converter - moshi: 2.7.0'
implementation 'com. Making. Liujingxing. RXHTTP: converter - protobuf: 2.7.0'
implementation 'com. Making. Liujingxing. RXHTTP: converter - simplexml: 2.7.0'
Copy the code

2. Use RxHttp + Flow

2.1 Request Trilogy

Those of you who have used RxHttp know that RxHttp sends any request following the request trilogy, as follows:The code says

 RxHttp.get("/service/...")  PostForm, postJson, etc
    .add("key"."value")
    .toFlow<Student>()       // The second step is to call the toFlow method and enter the generic type to get the Flow object
    .catch {
        // Exception callback
        val throwable = it
    }.collect {              Call collect to initiate a request
        // Successful callback
        val student = it
    }                
Copy the code

Coroutine request trilogy in detail

  • First, select methods like Get, postForm, postJson, postBody to determine the request type, and then add, addFile, addHeader, and other methods to add parameters, files, requests, and other information

  • Step 2: Call toFlow/toFlowXxx and pass in a generic type to get the Flow object. ToFlow has a series of overloaded methods for monitoring uploads/downloads and progress, as described in this article. After this step, You can call methods like Catch, onStart, and onCompletion to listen for exceptions and start and end callbacks, just like you would with a Flow object

  • In the third step, calling the collect method will start sending requests and, if something is normal, will receive a success callback

This is the most common operation of RxHttp in coroutine, master the request trilogy, master the essence of RxHttp

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 parameter generates the RxGoogleHttp class at this point, optionally named
    @Domain(name = "Google", className = "Google")
    public static String GOOGLE = "https://www.google.com";
}
Copy the code

In the preceding configuration, www.wanandroid.com is the default domain name, and www.google.com is a non-default domain name

More BaseUrl processing

//1. Use the default domain name
/ / url for https://www.wanandroid.com/service/ at this time...
RxHttp.get("/service/...")...//2, use Google domain name method 1: pass absolute path
RxHttp.get("https://wwww.google.com/service/...").../ / 3, using Google domain method 2: call setDomainToGoogleIfAbsent method
// This method is generated by the name field of the @domain annotation named setDomainTo{name}IfAbsent
RxHttp.get("/service/...")
    .setDomainToGoogleIfAbsent()
    ...
 
//4, Use Google domain name method 3: directly use RxGoogleHttp class to send a request,
// The class is generated by the className field of the @domain annotation and is named Rx{className} HTTP
RxGoogleHttp.get("/service/...")...Copy the code

Note: The priorities of the preceding four domain name configuration methods are as follows: 2 > 3 > 4 > 1

Dynamic Domain name processing

// Reassign the url immediately
Url.BASE_URL = "https://www.baidu.com";
RxHttp.get("/service/...").../ / url for https://www.baidu.com/service/ at this time...
Copy the code

2.3 Unified judgment of business code

I think most people’s interface returns in this format

class BaseResponse<T> {
    var code = 0
    var msg : String? = null
    var data : T 
}
Copy the code

The first step in getting the object is to check the code if the code! If = 200(assuming 200 means correct data), the user will be given an error message in the MSG field. If it is 200, the user will be given a data field to update the UI

RxHttp.get("/service/...")
    .toFlow<BaseResponse<Student>>()
    .collect {
        if (response.code == 200) {
            // Get the data field (Student) and refresh the UI
        } else {
            val msg = it.msg // get the MSG field and give an error message}}Copy the code

Imagine a project with at least 30+ such interfaces. It would be inelegant and disastrous for each interface to read this judgment, and no one would do it. And for the UI, just need the data field, I don’t care what error message.

Is there any way to get the data field directly and make a unified judgment on code? Yes, go straight to the code

RxHttp.get("/service/...")
    .toFlowResponse<Student>() // Call this method to get the data field directly, which is the Student object
    .catch {
        // if code is not 200, call back to MSG and code
        val msg = it.msg
        val code = it.code    
    }.collect {
        // Get the data field directly, in this case the Student object
        val student = it
    }
Copy the code

As you can see, a successful callback to the toFlowResponse() method directly fetches the data field, the Student object.

At this point, I’m sure many of you will be wondering,

  • Where is the business code judged?

  • What is the it object in the exception callback and why can get MSG and code fields?

To answer the first question, where is the business code judged?

The toFlowResponse() method is not provided internally by RxHttp, but is automatically generated by the annotation processor RxHTTP-Compiler using the @parser annotation. It doesn’t matter. Just look at the code

@Parser(name = "Response")
open class ResponseParser<T> : TypeParser<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 data: BaseResponse<T> = response.convertTo(BaseResponse::class.*types)
        val t = data.data     // Get the data field
        if (data.code ! =200 || t == null) { // If code does not equal 200, the data is incorrect and an exception is thrown
            throw ParseException(data.code.toString(), data.msg, response)
        }
        return t  // Finally returns the data field}}Copy the code

The code above only needs to focus on two things,

First, rxHTTP-Compiler generates the toFlowResponse

() method named toFlow{name} when we use the @parser annotation at the beginning of the class and name the Parser Response.

Second, we’re in the if statement, code! If = 200 or data == null, ParseException will be thrown with the MSG and code fields, so we can get these two fields by strong-casting in the exception callback

Then answer the second question: what is the it object in the exception callback? Why can we get the MSG and code fields?

MSG and code are Throwable extension fields, which need to be extended by ourselves. The code is as follows:

val Throwable.code: Int
    get() =
        when (this) {
            is HttpStatusCodeException -> this.statusCode // The Http status code is abnormal
            is ParseException -> this.errorCode.toIntOrNull() ? : -1     // The service code is abnormal
            else -> -1
        }

val Throwable.msg: String
    get() {
        return if (this is UnknownHostException) { // The network is abnormal
            "No network at present, please check your network Settings."
        } else if (
            this is SocketTimeoutException  // Okhttp global Settings timed out
            || this is TimeoutException     // The timeout method in rxJava timed out
            || this is TimeoutCancellationException  // The coroutine timed out
        ) {
            "Connection timed out, please try again later"
        } else if (this is ConnectException) {
            "Network is not available, please try again later!"
        } else if (this is HttpStatusCodeException) {               // The request failed
            "Http status code exception"
        } else if (this is JsonSyntaxException) {  // The request succeeded, but the Json syntax is abnormal, resulting in parsing failure
            "Data parsing failed, please check data is correct"
        } else if (this is ParseException) {       // ParseException indicates that the request was successful, but the data is incorrect
            this.message ? : errorCode// if MSG is empty, display code
        } else {
            "Request failed, please try again later"}}Copy the code

The ResponseParser parser is the ResponseParser parser, and the ResponseParser parser is the ResponseParser parser, and the ResponseParser parser is the ResponseParser parser

3. Upload/download

The elegant manipulation of files is inherent in RxHttp, and can be added to Flow

3.1 File upload

RxHttp.postForm("/service/...")  
    .addFile("file", File("xxx/1.png"))         // Add a single file
    .addFiles("fileList", ArrayList<File>())    // Add multiple files
    .toFlow<String>()
    .catch { // exception callback}
    .collect { // Successful callback}
Copy the code

ToFlow: toFlow: toFlow: toFlow: toFlow: toFlow: toFlow: toFlow: toFlow: toFlow:

RxHttp.postForm("/service/...")      
    .addFile("file", File("xxx/1.png"))    
    .addFiles("fileList", ArrayList<File>())      
    .toFlow<String> {  // You can also choose to customize the toFlowXxx method for the parser
        val process = it.progress         The upload progress ranges from 0 to 100
        val currentSize = it.currentSize  // Size has been uploaded, in bytes
        val totalSize = it.totalSize      // Total size to upload in bytes
    }.catch { // exception callback}
    .collect { // Successful callback}
Copy the code

3.2 file download

Next, let’s look at downloads. Post code directly

val localPath = "sdcard//android/data/.... /1.apk"  
RxHttp.get("/service/...")      
    .toFlow(localPath) {       
        //it is the Progress object
        val process = it.progress        // The download progress is 0-100
        val currentSize = it.currentSize // Download size, in bytes
        val totalSize = it.totalSize     // Total size to download in bytes}.catch { // exception callback}
    .collect { // Successful callback, here we can get the local storage path, that is, localPath}
Copy the code

If you do not need to listen for progress, the progress callback is also not passed. Let’s look at the toFlow method signature used for downloading

/ * * *@paramDestPath Local storage path *@paramAppend whether to append the download, that is, whether to download at breakpoint *@paramCapacity Queue size, effective only when listening for progress callback *@paramProgress Progress callback */
fun CallFactory.toFlow(
    destPath: String,
    append: Boolean = false,
    capacity: Int = 1,
    progress: (suspend (Progress) - >Unit)? = null
): Flow<String>
Copy the code

If you want to download the breakpoint, append is passed true. If you want to listen to the progress, pass the progress callback. If you want to listen to the progress, pass the progress callback.

As for the capacity parameter, this one needs to be added. It specifies the cache size of the queue. What queue? Progress callback queue, purpose is to discard to consumption, in the real scene, there might be downstream upstream production rate, consumption rate is less than this will lead to accumulation of events, download quickly, is the translation but you deal with very slow progress callback, it may appear you are still in the progress of 10 events, But actual download progress may to 50 or more, capacity is set to 1, 10-50 between events is discarded, the downstream is likely to be received by the progress of 50 events, this ensures the downstream always received the latest events, which is the most timely download progress, of course, if you want to receive the full progress callback event, Set capacity to 100.

3.3. Pause/resume the download

Many will have a need to suspend/resume downloads, but for download, and not the real meaning of suspension and recovery, the so-called pause, but is to stop the downloading and interrupt request, and recovery, is a request from the last time the location of the interrupt again continue to download, namely breakpoint downloading, all, you just need to know how to cancel the request and the breakpoint to download

Cancel the request

The cancellation of Flow is the closure of the external coroutine

val job = lifecycleScope.launch {
    val localPath = "sdcard//android/data/.... /1.apk"  
    RxHttp.get("/service/...")      
        .toFlow(localPath) {       
            //it is the Progress object
            val process = it.progress        // The download progress is 0-100
            val currentSize = it.currentSize // Download size, in bytes
            val totalSize = it.totalSize     // Total size to download in bytes}.catch { // exception callback}
        .collect { // Successful callback, here we can get the local storage path, that is, localPath}
}
// When needed, calling job.cancel() cancels the request
job.cancel()
Copy the code

Breakpoint downloading

As described above, to download a breakpoint, simply set the second toFlow parameter, append, to true, as follows:

val localPath = "sdcard//android/data/.... /1.apk"  
RxHttp.get("/service/...")      
    .toFlow(localPath, true) {       
        //it is the Progress object
        val process = it.progress        // The download progress is 0-100
        val currentSize = it.currentSize // Download size, in bytes
        val totalSize = it.totalSize     // Total size to download in bytes}.catch { // exception callback}
    .collect { // Successful callback, here we can get the local storage path, that is, localPath}
Copy the code

Note: Breakpoint download requires server interface support

For Android 10 file upload/download, please click RxHttp perfect for Android 10/11 upload/download/progress monitor

4, turn LiveData

Flow depends on coroutines environment, if you don’t want to use it, and want to use the Flow, the LiveData is a good choice, in the official androidx. Lifecycle: lifecycle – LiveData – KTX: X.X.X asLiveData method was provided in the library, Flow can be easily converted to LiveData objects, with LiveData objects, there is no need for coroutine environment

4.1 Ordinary requests are forwarded to LiveData

// Currently in the FragmentActivity environment
 RxHttp.get("/service/...")  
    .toFlow<Student>()
    .catch { }
    .asLiveData()
    .observe(this) {
       val student = it;
       / / update the UI
    }
Copy the code

Because asLiveData is called, the above code can be executed without the coroutine environment;

4.2 Upload LiveData with progress

RxHttp.postForm("/service/...")      
    .addFile("file", File("xxx/1.png"))    
    .addFiles("fileList", ArrayList<File>())      
    .toFlow<Student> {  // You can also choose to customize the toFlowXxx method for the parser
        val process = it.progress         The upload progress ranges from 0 to 100
        val currentSize = it.currentSize  // Size has been uploaded, in bytes
        val totalSize = it.totalSize      // Total size to upload in bytes}.catch { // exception callback}
    .asLiveData()
    .observe(this) {
       val student = it;
       / / update the UI
    }
Copy the code

In the code above, downstream Observe can only receive the uploaded callback after switching to LiveData. If you want to receive all events including the progress callback, you need to use toFlowProgress instead of toFlow. Interested to see the source code), as follows:

RxHttp.postForm("/service/...")      
    .addFile("file", File("xxx/1.png"))    
    .addFiles("fileList", ArrayList<File>())      
    .toFlowProgress<Student>()  // This method has no progress callback parameters
    .catch { // exception callback}
    .asLiveData()
    .observe(this) {
        // All events will be received here, where it is the ProgressT
      
        object
      
        val process = it.progress         The upload progress ranges from 0 to 100
        val currentSize = it.currentSize  // Size has been uploaded, in bytes
        val totalSize = it.totalSize      // Total size to upload in bytes
        val student = it.result           // The object returned by the interface
        if(student ! =null) {
            // If the value is not null, the upload is complete and the interface request ends}}Copy the code

4.3 Transfer LiveData with progress download

RxHttp provides a corresponding toFlowProgress method for downloading, as follows:

fun CallFactory.toFlowProgress(
    destPath: String,
    append: Boolean = false,
    capacity: Int = 1
): Flow<ProgressT<String>>
Copy the code

With the above introduction to download the corresponding toFlow method, less a progress callback parameters, here quietly tell you, download toFlow method, internal is through toFlowProgress method implementation, want to know their own to view the source code, here is not introduced

Combined with the asLiveData method, the following is used:

val localPath = "sdcard//android/data/.... /1.apk"  
RxHttp.get("/service/...")      
    .toFlowProgress(localPath)
    .catch { // exception callback}
    .asLiveData() 
    .observe(this) {
        // All events will be received here, where it is the ProgressT
      
        object
      
        val process = it.progress         // The download progress is 0-100
        val currentSize = it.currentSize  // Download size, in bytes
        val totalSize = it.totalSize      // Total size to download in bytes
        val path = it.result              // Local storage path
        if(path ! =null) {
            // If the value is not null, the download is complete and the interface request ends}}Copy the code

5, summary

After reading this article, I believe you have understood the elegance of RxHttp, whether upload/download, or progress monitoring, all three steps to understand, master the request trilogy, grasp the essence of RxHttp.

In fact, RxHttp is far more than that. This article only introduces the use of RxHttp + Flow, more functions, such as: public parameters/request header addition, request encryption and decryption, caching and so on, please check

RxHttp is an amazing Http request framework (Basics)

RxHttp, a more elegant coroutine experience than Retrofit (RxHttp + Await)

RxHttp is perfect for Android 10/11 upload/download/progress monitoring

RxHttp Optimal solution for Http caching on the whole network

Writing articles and finally, open source is not easy, more is not easy, you also need to bother everybody to give this Wen Dian praise, if you can, give a star again, I would be grateful, 🙏 🙏 🙏 🙏 🙏 🙏 🙏 🙏 🙏 🙏 🙏 🙏