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 one
Response<Student>
Object, which is then usedmap
The operator fromResponse<Student>
Get the Student object and pass it to the downstream observer - Can’t unify
Response
Inside 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 🙏