preface

In the article, we do to RxHttp are an integral part of the introduction, the article is published, it received many different voices, the readers have to my affirmation, also have to suggest improvements RxHttp, more readers directly pointed out the deficiency of the I, therefore, I harvest a lot, I had the new cognition to a lot of things, I think that’s why a lot of people keep writing, because here, you can learn from each other and communicate with each other to make up for your shortcomings. So I want to thank you, you give me the motivation to continue to write, I will keep writing some nutritious articles.

Introduction to the

Data Parser plays a very important role in RxHttp. It parses the data returned from Http into any object we want, which can be used by Gson, SampleXml, ProtoBuf, FastJson and other third-party data parsing tools. Currently, RxHttp provides five parsers, namely SimpleParser, ListParser, MapParser, BitmapParser and DownloadParser. If these five parsers cannot meet our business development, we can also customize the parser, which I will introduce in detail below.

First let’s look at the Parser internals

public interface Parser<T> {

    /** * data parsing *@paramResponse Http execution result *@returnThe parsed object type *@throwsIOException Network exceptions or parsing exceptions */
    T onParse(@NonNull Response response) throws IOException;

}
Copy the code

Parser is an interface class that has only one method in it. It inputs the Response object returned by the Http request and outputs the generic T that we pass in. If we want to customize the Parser, we have to implement this interface.

Parser was briefly introduced in the previous article, so let’s review it.

Built-in parser

SimpleParser

We used the taobao access IP interface as test interface http://ip.taobao.com/service/getIpInfo.php?ip=63.223.108.42 corresponding data structure is as follows

public class Response {
    private int     code;
    private Address data;
    // omit the set and get methods

    class Address {
        // Some fields have been omitted for simplicity
        private String country; / / country
        private String region; / / region
        private String city; / / the city
        // omit the set and get methods}}Copy the code

Start sending the request

  RxHttp.get("http://ip.taobao.com/service/getIpInfo.php") / / Get request
        .add("ip"."63.223.108.42")// Add parameters
        .addHeader("accept"."* / *") // Add the request header
        .addHeader("connection"."Keep-Alive")
        .addHeader("user-agent"."Mozilla / 4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)")
        .asObject(Response.class)  // Returns an Observable
      
        object
      
        .as(RxLife.asOnMain(this))  // Sense the life cycle and call back on the main thread
        .subscribe(response -> {
            // Successful callback
        }, throwable -> {
            // Failed callback
        });
Copy the code

The above code uses the asObject operator and passes response. class. Then the observer can get the Response object. It uses the SimpleParser internally, so let’s click on it

  public <T> Observable<T> asObject(Class<T> type) {
     return asParser(SimpleParser.get(type));
  }
Copy the code

Sure enough, it uses the SimpleParser, so let’s look at the source code for SimpleParser

public class SimpleParser<T> extends AbstractParser<T> {

    // omit the constructor
    
    @Override
    public T onParse(Response response) throws IOException {
        returnconvert(response, mType); }}Copy the code

The forehead.. The onParser method only calls the convert method of the parent class.

public interface Parser<T> {

    default <R> R convert(Response response, Type type) throws IOException {
        ResponseBody body = ExceptionHelper.throwIfFatal(response);
        boolean onResultDecoder = isOnResultDecoder(response);
        LogUtil.log(response, onResultDecoder, null);
        IConverter converter = getConverter(response);
        returnconverter.convert(body, type, onResultDecoder); }}Copy the code

If we look at the onParser method, we can see that.

First we get the ResponseBody through the Response object, then we get an IConverter interface object, and then we convert the ResponseBody object into the generic object we want.

At this point, I’m sure you know the purpose of the SimpleParser class, which parses Http requests directly into any object we want.

If we want to get a List

object, we can simply new a SimpleParser object as follows:

  RxHttp.get("/service/...")
        .asParser(new SimpleParser<List<Student>>() {})
        .as(RxLife.asOnMain(this))  // Sense the life cycle and call back on the main thread
        .subscribe(students -> { // Students = List
            // Successful callback
        }, throwable -> {
            // Failed callback
        });
Copy the code

As you can see, we use the asParser operator directly, pass in our new SimpleParser object, and then the observer gets the List

object.

At this point, some people might say, well, this new way of writing it is not nice at all, the code doesn’t look neat, is there a simpler way to write it?

Yes, through the ListParser

ListParser

ListParser parses Http data into a List

object.

public class ListParser<T> extends AbstractParser<List<T>> {
    
    // omit the constructor

    @Override
    public List<T> onParse(Response response) throws IOException {
        final Type type = ParameterizedTypeImpl.get(List.class, mType); // Get the generic type
        returnconvert(response, type); }}Copy the code

The code is similar to SimpleParser and I won’t go into detail. The difference here is the use of the ParameterizedTypeImpl class to wrap a List

type. See also my article on generic literacy for Android and Java

How do we get a List

object from ListParser

  RxHttp.get("/service/...")
        .asList(Student.class)
        .as(RxLife.asOnMain(this))  // Sense the life cycle and call back on the main thread
        .subscribe(students -> {    // Students = List
            // Successful callback
        }, throwable -> {
            // Failed callback
        });
Copy the code

As you can see, just use the asList operator and pass in the Student.class, which contains the ListParser object obtained via ListParser.get(student.class).

MapParser

In addition to getting a List object directly, MapParser can also get a Map object directly, as follows:

  RxHttp.get("/service/...")
        .asMap(String.class, Student.class)
        .as(RxLife.asOnMain(this))  // Sense the life cycle and call back on the main thread
        .subscribe(map -> {    // Map = map 
      ,>
            // Successful callback
        }, throwable -> {
            // Failed callback
        });
Copy the code

As you can see, using the asMap(Class

kType, Class

vType) method, you can not only get a map object, but also specify the generic type in the map.

At this point, you can guess how BitmapParser and DownloadParser are used. Yes, with the asBitmap and asDownload operators, you can get a Bitmap object and perform the download operation, which is not explained here.

Custom parsers

Of the three parses outlined above, SimpleParser does everything. Any data structure can be parsed correctly if you build the corresponding Bean class, which requires us to build n Bean classes, many of which can be abstracted. For example, most of the data structures returned from Http can be abstracted into the following Bean class

public class Response<T> {
    private int    code;
    private String msg;
    private T      data;
    // omit the get and set methods
}
Copy the code

Let’s say that T in Response is a student object, and we want to get the student information, we can do this

  RxHttp.get(http://www.......) // Get, for get request
        .asParser(new SimpleParser<Response<Student>>() {}) // Here the generic passes Response
      
        .observeOn(AndroidSchedulers.mainThread()) // Main thread callback
        .map(Response::getData) // Use the map operator to get the data field in Response
        .as(RxLife.asOnMain(this))
        .subscribe(student -> {
            // Student = data in Response
        }, throwable -> {
            // The Http request is abnormal
        });
Copy the code

The above code has three drawbacks

  • This is implemented through SimpleParse anonymous inner classes, which, as mentioned earlier, can cause memory leaks and is not very elegant to write
  • The first thing downstream gets is oneResponse<Student>Object, which is then usedmapThe operator fromResponse<Student>Get the Student object and pass it to the downstream observer
  • Can’t unifyResponseInside the code field to do the verification

ResponseParser

So what’s the elegant solution? The answer is a custom parser. Let’s specify a ResponseParser parser as follows:

@Parser(name = "Response")
public class ResponseParser<T> extends AbstractParser<T> {

    // omit the constructor
    @Override
    public T onParse(okhttp3.Response response) throws IOException {
        final Type type = ParameterizedTypeImpl.get(Response.class, mType); // Get the generic type
        Response<T> data = convert(response, type);
        T t = data.getData(); // Get the data field
        if (t == null && mType == String.class) {
            /* * Consider that sometimes the server will return: {"errorCode":0,"errorMsg":" focus on success "} {"errorCode":0,"errorMsg":" focus on success "} {"errorCode":0,"errorMsg":" focus on success "} And make sure the assignment is not null */
            t = (T) data.getMsg();
        }
        if(data.getCode() ! =0 || t == null) {// If code is not equal to 0, the data is incorrect and an exception is thrown
            throw new ParseException(String.valueOf(data.getCode()), data.getMsg(), response);
        }
        returnt; }}Copy the code

The code is similar to the SimpleParser class, with the following benefits

  • ResponseParser automatically does a layer of filtering for us, so we can get the T object directly instead of using the map operator
  • Internal can make unified judgment on the code field, according to different codes, different exceptions are thrown, to achieve a unified error handling mechanism (exceptions thrown here will be received by the downstream onError observer)
  • When coDO is correct, the data is correct and the downstream onNext observer receives the event
  • The use of anonymous inner classes is avoided

At this point, we can implement as follows:

  RxHttp.get("http://www...") // Get, for get request
        .asResponse(Student.class)  // This method is generated by annotations
        .as(RxLife.asOnMain(this))
        .subscribe(student -> {
            // Student = data in Response
        }, throwable -> {
            // The Http request is abnormal
            String msg = throwable.getMessage(); //Response Indicates the MSG field or other exception information
            String code = throwable.getLocalizedMessage(); // The code field in Response, if passed in
        });
Copy the code

Note: We define ResponseParser with the @parser annotation (name = “Response”), so we have asResponse in the RxHttp class, RxHttp: Generated API (4)

Then, what if T in Data is a List

? We might go something like this:

  RxHttp.get("http://www...") // Get, for get request
        .asParser(new ResponseParser<List<Student>>() {})  
        .as(RxLife.asOnMain(this))
        .subscribe(students -> {
            // Students = List
        }, throwable -> {
            // The Http request is abnormal
            String msg = throwable.getMessage(); //Response Indicates the MSG field or other exception information
            String code = throwable.getLocalizedMessage(); // The code field in Response, if passed in
        });
Copy the code

Again via anonymous inner class implementation, heart tired, is there a more elegant way? Okay, so again, custom parser, let’s define a ResponseListParser

ResponseListParser

@Parser(name = "ResponseList")
public class ResponseListParser<T> extends AbstractParser<List<T>> {
 
    // omit the constructor

    @Override
    public List<T> onParse(okhttp3.Response response) throws IOException {
        final Type type = ParameterizedTypeImpl.get(Response.class, List.class, mType); // Get the generic type
        Response<List<T>> data = convert(response, type);
        List<T> list = data.getData(); // Get the data field
        if(data.getCode() ! =0 || list == null) {  // If code is not equal to 0, the data is incorrect and an exception is thrown
            throw new ParseException(String.valueOf(data.getCode()), data.getMsg(), response);
        }
        returnlist; }}Copy the code

The code is similar, I will not explain, directly see how to use:

  RxHttp.get("http://www...") // Get, for get request
        .asResponseList(Student.class)  // This method is generated by annotations
        .as(RxLife.asOnMain(this))
        .subscribe(students -> {
            // Students = List
        }, throwable -> {
            // The Http request is abnormal
            String msg = throwable.getMessage(); //Response Indicates the MSG field or other exception information
            String code = throwable.getLocalizedMessage(); // The code field in Response, if passed in
        });
Copy the code

Note: The asResponseList method is also generated by annotations and we have one last question

{
    "code": 0."msg": ""."data": {
        "totalPage": 0."list": []}}Copy the code

How do we interpret this data? First, let’s name another Bean class PageList like this:

public class PageList<T> {
    private int     totalPage;
    private List<T> list;
    // omit the get/set method
}
Copy the code

ResponseParser (); ResponseParser (); ResponseParser ();

  RxHttp.get("http://www...") // Get, for get request
        .asParser(new ResponseParser<PageList<Student>>(){})
        .as(RxLife.asOnMain(this))
        .subscribe(pageList -> {
            // There is a pageList of type pageList 
      
        }, throwable -> {
            // The Http request is abnormal
            String msg = throwable.getMessage(); //Response Indicates the MSG field or other exception information
            String code = throwable.getLocalizedMessage(); // The code field in Response, if passed in
        });
    }
Copy the code

Okay, anonymous inner class again, so go ahead and customize the parser. We define a ResponsePageListParser as follows:

ResponsePageListParser

@Parser(name = "ResponsePageList")
public class ResponsePageListParser<T> extends AbstractParser<PageList<T>> {

    // omit the constructor

    @Override
    public PageList<T> onParse(okhttp3.Response response) throws IOException {
        final Type type = ParameterizedTypeImpl.get(Response.class, PageList.class, mType); // Get the generic type
        Response<PageList<T>> data = convert(response, type);
        PageList<T> pageList = data.getData(); // Get the data field
        if(data.getCode() ! =0 || pageList == null) {  // If code is not equal to 0, the data is incorrect and an exception is thrown
            throw new ParseException(String.valueOf(data.getCode()), data.getMsg(), response);
        }
        returnpageList; }}Copy the code

Let’s see how it works. Okay

  RxHttp.get("http://www...") // Get, for get request
        .asResponsePageList(Student.class)  // This method is generated by annotations
        .as(RxLife.asOnMain(this))
        .subscribe(pageList -> {
            // There is a pageList of type pageList 
      
        }, throwable -> {
            // The Http request is abnormal
            String msg = throwable.getMessage(); //Response Indicates the MSG field or other exception information
            String code = throwable.getLocalizedMessage(); // The code field in Response, if passed in
        });
Copy the code

Note: The asResponsePageList method is still generated by annotations.

And if you look at it carefully, you’ll see that we’ve got three parsers ResponseParser, ResponseListParser, and ResponsePageListParser, and the code is pretty much the same, and the usage is pretty much the same, but the output is different.

summary

RxHttp: SimpleParser, ListParser, DownloadParser RxHttp: SimpleParser, ListParser, And DownloadParser ResponseParser, ResponseListParser, and ResponsePageListParser. With these six parsers, we should be able to handle most scenarios. If there are still scenarios that cannot be implemented, it will be very easy for you to customize the parsing after reading this article.

Ask yourself: why not package the three custom parsers ResponseParser, ResponseListParser, and ResponsePageListParser into RxHttp? Answer: Because all three parsers address specific business requirements, each developer’s business logic may be different and therefore cannot be encapsulated in the RxHttp library.

Finally, if there is something wrong in this article, please point out the readers. Give me a thumbs up on RxHttp if you think it’s good

See other articles in the RxHttp series for more details

RxHttp one chain send request powerful Param class (3)

RxHttp Generated API (4)

Reprint please indicate the source, thank you 🙏