Do you really use Retrofit 2? Retrofit 2 is a complete tutorial

Keywords: Retrofit Java JSON github

Do you really use Retrofit 2? Retrofit 2 is a complete tutorial

retrofit2 is based on okhttp package. It naturally supports responsive programming, simple and clear API calls, especially the adapters supporting various json frameworks, coupled with rxjava thread scheduling, can make your code instantly elegant. Let's take a look at the teenagers.

Links to the original text: http://www.jianshu.com/p/308f3c54abdd

Author: @ monster kidou
If you want to reproduce it, you should keep the author's information and links to the original text in a clear place.
Retrofit version: 2.0.2

Notes to this article are as follows:

  • Introduction to Retrofit
  • Retrofit annotations
  • Gson and Converter
  • RxJava and CallAdapter
  • Custom Converter
  • Custom CallAdapter
  • Other instructions

Preface

Retrofit in this article refers to Retrofit 2.0.
The code involved in this article and the interface used in the test are available in Github Find it.
The test interface server runs directly under the server project RESTServer.main() The test server can be started, and all the code examples used this interface (interface address). http://localhost:4567/ ).
Of course, you can also help yourself. json-server Or the latest open source Parse Build a REST API, but all need to install Node.js, interested can try.

Interface List:

address Request method parameter Explain
/blog GET page={page},sort=asc or desc Page to get a list of blogs, 10 items per page
/blog/{id} GET id Gets the Blog with the specified ID
/blog POST {"author":"","title":"","content":""} Create a new Blog
/blog/{id} PUT At least one of {"author":","title":", "content":"} Modify Blog
/blog/{id} DELETE id Delete a Blog
/form POST Arbitrary, eventually returned as Json Object Used to test Form forms and support file upload
/headers GET showAll=true or false, default false Return the custom request header, all=true is to display all

Note: The above interfaces {id} and {page} all represent a pure number, / blog/{id} can be replaced by / blog?id=XXX, page is the same.

It's written in front. You should know the basics of HTTP Introduced the relevant knowledge of HTTP, I wonder if those who want to know about Retrofit have visited Retrofit? rom Once I was Do you really use Gson? Gson Guidelines (4) When you understand the content of annotations, reflections, generics, HTTP, you just need to look at a code example of Retrofit to play Retrofit easily. I wonder if you have played it?
Of course, the content of annotations, reflections and generics has not been written yet, but the content of Retrofit has come first! After all, understanding Retrofit only needs to make it work. Are you ready?

1. Introduction to Retrofit

Retrofit is actually quite simple, simple to the source code only 37 files, 22 of which are annotations and related to HTTP, the real exposure to users is not many classes, so I looked at it once. rom Most scenarios are accessible. If you haven't seen them, you can go and see them first. Although English, the code is the best tutorial, isn't it? Of course, this article will be more detailed and can't write a hydrology. After all, I named it "Can you really use Retrofit 2? Retrofit 2 is a complete tutorial".

1.1. Create Retrofit instances

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("http://localhost:4567/")
        .build();

When creating a Retrofit instance, you need to set the URL through Retrofit.Builder and call the baseUrl method.
Note: Retrofit2's baseUlr must end with /(slash) or an Illegal ArgumentException will be thrown, so if you see that other tutorials do not end with /, most of them are directly from Retrofit. 1.X copied it.

1.2. Interface Definition

Take a Blog that gets the specified id as an example:

public interface BlogService {
    @GET("blog/{id}")
    Call<ResponseBody> getBlog(@Path("id") int id);
}

Note that interface is not class, so we can't call this method directly. We need to create a proxy object for BlogService with Retrofit.

BlogService service = retrofit.create(BlogService.class);

Once you have the proxy object, you can call this method.

1.3. Interface Call

Call<ResponseBody> call = service.getBlog(2);
// The usage is the same as OkHttp's call.
// The difference is that if the Android system callback method is executed in the main thread
call.enqueue(new Callback<ResponseBody>() {
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
        try {
            System.out.println(response.body().string());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {
        t.printStackTrace();
    }
});

Print results:

{"code":200,"msg":"OK","data":{"id":2,"date":"2016-04-15 03:17:50","author":"Monster kidou","title":"Retrofit2 Test 2","content":"Here is Retrofit2 Demo Test Server 2"},"count":0,"page":0}

See sample source code Example01.java

2. Detailed Annotations to Retrofit

The above mentioned Retrofit has 22 annotations. This section is devoted to these 22 annotations. In order to help you better understand, I divide these 22 annotations into three categories and present them in the form of tables. The tables are incomplete. See the example annotations on the source code for details.

Category 1: HTTP request methods

HTTP Request Method Annotation

Except HTTP, the table above corresponds to the request method in the HTTP standard. HTTP annotation can replace any annotation in the above method. There are three attributes: method, path,hasBody. The following is to implement the above with HTTP annotation. Example01.java Examples.

public interface BlogService {
    /**
     * method Representation of requests, case-sensitive
     * path Representation path
     * hasBody Indicates whether there is a requester
     */
    @HTTP(method = "GET", path = "blog/{id}", hasBody = false)
    Call<ResponseBody> getBlog(@Path("id") int id);
}

Note: The retrofit value of the method will not be processed, so it is necessary to ensure its accuracy. Previous use of lowercase can also be because the server in the sample source code is case-insensitive, so I hope you will pay attention to it. Thank you @Yan Qishi for discovering this problem.
See sample source code Example02.java

Category II: Markup classes

Markup class annotations

See sample source code Example03.java

Category 3: Parametric classes

Parametric class annotations

Note 1: {placeholder} and PATH are only used in the path part of the URL as far as possible. The parameters in the URL are replaced by Query and QueryMap to ensure that the interface definition is concise.
Note 2: Query, Field, and Part all support arrays and implement types of Iterable interfaces, such as List, Set, etc., to facilitate the transfer of arrays to the background.

Call<ResponseBody> foo(@Query("ids[]") List<Integer> ids);
//Results: IDS []= 0 & IDs []= 1 & IDs []= 2

See Path sample source code Example01.java
See Field, FieldMap, Part and PartMap sample source code Example03.java
See Header and Headers sample source code. Example04.java
See sample source code for Query, QueryMap, Url Example05.java

3. Gson and Converter

By default Retrofit only supports the conversion of HTTP response bodies to ResponseBody.
That's why the return value of the interface in the previous example is Call < ResponseBody>.
But why introduce generics if the response body only supports conversion to ResponseBody?
The return value is simply a Call. Since generics are supported, it means that generic parameters can be of other types.
Converter is Retrofit, which provides us with the type to convert ResponseBody to what we want.
With Converter, we can write the interface for our first example like this:

public interface BlogService {
  @GET("blog/{id}")
  Call<Result<Blog>> getBlog(@Path("id") int id);
}

Of course, it's not possible to just change the generic type. We need to explicitly inform Converter when creating Retrofit that we need to use when converting ResponseBody to the type in our generic type.

Introduce Gson support:

compile 'com.squareup.retrofit2:converter-gson:2.0.2'

Add Gson support for Retrofit through Gson Converter Factory:

Gson gson = new GsonBuilder()
      //Configure your Gson
      .setDateFormat("yyyy-MM-dd hh:mm:ss")
      .create();

Retrofit retrofit = new Retrofit.Builder()
      .baseUrl("http://localhost:4567/")
      //You can accept custom Gson, or you can not pass it on.
      .addConverterFactory(GsonConverterFactory.create(gson))
      .build();

See sample source code Example06.java

So Retrofit uses Gson to convert ResponseBody to the type we want.

This is the time to finally demonstrate how to create a Blog!

@POST("blog")
Call<Result<Blog>> createBlog(@Body Blog blog);

Blog s annotated by @Body will be converted by Gson into RequestBody and sent to the server.

BlogService service = retrofit.create(BlogService.class);
Blog blog = new Blog();
blog.content = "Newly-built Blog";
blog.title = "test";
blog.author = "Monster kidou";
Call<Result<Blog>> call = service.createBlog(blog);

Result:

Result{code=200, msg='OK', data=Blog{id=20, date='2016-04-21 05:29:58', author='Monster kidou', title='test', content='Newly-built Blog'}, count=0, page=0}

See sample source code Example07.java

If you are not familiar with Gson, you can refer to what I wrote. Do you really use Gson? Gson Guide Series.

4. RxJava and CallAdapter

When it comes to Retrofit, there is no need to mention another library, RxJava, which is so popular that many articles on the Internet have talked about how to integrate with Retrofit, but there will still be an example of RxJava, but the main purpose here is to introduce the effect of using CallAdapter.

Section 3 introduces Converter as a transformation of T in Call < T > and CallAdapter as a transformation of Call < T > so that Call in Call < T > can also be replaced, and the type of return value determines the logic of your subsequent processing program. Similarly, Retrofit provides multiple Call Adapters. Take RxJava as an example, using Observable instead of Call:

Introduce RxJava support:

compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2'
// For rxjava2.x (adapter-rxjava2 version to >= 2.2.0)
compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'

Add RxJava support for Retrofit through RxJavaCallAdapterFactory:

Retrofit retrofit = new Retrofit.Builder()
      .baseUrl("http://localhost:4567/")
      .addConverterFactory(GsonConverterFactory.create())
      .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
      // For rxjava2.x
      .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 
      .build();

Interface design:

public interface BlogService {
  @POST("/blog")
  Observable<Result<List<Blog>>> getBlogs();
}

Use:

BlogService service = retrofit.create(BlogService.class);
service.getBlogs(1)
  .subscribeOn(Schedulers.io())
  .subscribe(new Subscriber<Result<List<Blog>>>() {
      @Override
      public void onCompleted() {
        System.out.println("onCompleted");
      }

      @Override
      public void onError(Throwable e) {
        System.err.println("onError");
      }

      @Override
      public void onNext(Result<List<Blog>> blogsResult) {
        System.out.println(blogsResult);
      }
  });

Result:

Result{code=200, msg='OK', data=[Blog{id=1, date='2016-04-15 03:17:50', author='Monster kidou', title='Retrofit2 Test 1', content='Here is Retrofit2 Demo Test Server 1'},.....], count=20, page=1}

See sample source code Example08.java

"20160608 Supplement": In the end, we can't get the Header and response code returned in this case. If we need both, we can provide two solutions:
1. Replace Observable < Response < T > with Observable < Response < T >, where Response refers to retrofit 2. Response
2. Replace Observable <T> with Observable <Result<T>, where Result refers to retrofit2.adapter.rxjava.Result, which contains an example of Response.

5. Custom Converter

The content of this section is to teach you how to implement a simple Converter, where the return format is Call < String > for example.

Before that, let's look at the Converter interface and its role:

public interface Converter<F, T> {
  // Realizing the Conversion from F(rom) to T(o)
  T convert(F value) throws IOException;

  // The factory used to provide the corresponding Converter to Retrofit
  abstract class Factory {
    // Here we create Converter s of other types from ResponseBody, if we can't handle returning null
    // Mainly used for the processing of response volume.
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
    Retrofit retrofit) {
      return null;
    }

    // Create a Converter from a custom type to ResponseBody here, which returns null if it cannot be processed.
    // Mainly used for processing annotations of Parts, PartMap s and Body
    public Converter<?, RequestBody> requestBodyConverter(Type type,
    Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
      return null;
    }

    // This is used for processing annotations to Field, FieldMap, Header, Path, Query, QueryMap
    // Retrfofit defaults to calling the toString method for the above annotations
    public Converter<?, String> stringConverter(Type type, Annotation[] annotations,
    Retrofit retrofit) {
      return null;
    }

  }
}

If we want to change from Call < ResponseBody > to Call < String >, then corresponding F and T correspond to ResponseBody and String respectively. We define a String Converter and implement Converter interface.

public static class StringConverter implements Converter<ResponseBody, String> {

  public static final StringConverter INSTANCE = new StringConverter();

  @Override
  public String convert(ResponseBody value) throws IOException {
    return value.string();
  }
}

We need a Fractory to register StringConverter with Retrofit

public static class StringConverterFactory extends Converter.Factory {

  public static final StringConverterFactory INSTANCE = new StringConverterFactory();

  public static StringConverterFactory create() {
    return INSTANCE;
  }

  // We only implement the transition from ResponseBody to String, so other methods are not covered.
  @Override
  public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
    if (type == String.class) {
      return StringConverter.INSTANCE;
    }
    //If we don't deal with other types, just return null.
    return null;
  }
}

Register our String Converter Factory with Retrofit. Builder. addConverter Factory:

Retrofit retrofit = new Retrofit.Builder()
      .baseUrl("http://localhost:4567/")
      // If you have Converter like Gson, you have to put it in front of the others.
      .addConverterFactory(StringConverterFactory.create())
      .addConverterFactory(GsonConverterFactory.create())
      .build();

Note: Additional Converter Factory is sequential. If multiple Converter Factories support the same type, only the first one will be used. Gson Converter Factory does not judge whether it supports or not, so there will be an exception thrown when the order is exchanged here because the type does not match.

As long as the generic parameter of the return value type is handled by our StringConverter, whether it's Call < String > or Observable < String >

Is it simple? If you have other requirements to deal with, do it yourself.

See sample source code Example09.java

6. Customize CallAdapter

This section describes how to customize a CallAdapter and verify that all String s will use the Converter we customized in Section 5.

Let's first look at the definition of the CallAdapter interface and the role of each method:

public interface CallAdapter<T> {

  // Types of integrity data such as T in Call < T >
  // This T will be the first parameter of Converter.Factory.responseBodyConverter
  // Refer to the custom Converter above.
  Type responseType();

  <R> T adapt(Call<R> call);

  // Factory Class for Providing CallAdapter to Retrofit
  abstract class Factory {
    // In this method, we determine whether the type we support is returnType, namely Call < Requestbody > and `Observable < Requestbody >'?
    // RxJavaCallAdapterFactory is to determine whether the return type is an Observable <?> type or not.
    // Return null when not supported
    public abstract CallAdapter<?> get(Type returnType, Annotation[] annotations,
    Retrofit retrofit);

    // Parameters used to obtain generics such as Requestbody in Call < Requestbody >
    protected static Type getParameterUpperBound(int index, ParameterizedType type) {
      return Utils.getParameterUpperBound(index, type);
    }

    // The original type used to obtain generics such as Call in Call < Requestbody >
    // The get method above requires this method.
    protected static Class<?> getRawType(Type type) {
      return Utils.getRawType(type);
    }
  }
}

After understanding the structure and function of CallAdapter, we can begin to customize our CallAdapter. This section takes CustomCall < String > as an example.

We need to define a CustomCall here, but CustomCall here as a demonstration is just a wrapping of Call, and it has no practical use.

public static class CustomCall<R> {

  public final Call<R> call;

  public CustomCall(Call<R> call) {
    this.call = call;
  }

  public R get() throws IOException {
    return call.execute().body();
  }
}

With CustomCall, we also need a CustomCall Adapter to implement the conversion from Call < T > to CustomCall < T >. Here we need to note that the last generic type is the type we want to return.

public static class CustomCallAdapter implements CallAdapter<CustomCall<?>> {

  private final Type responseType;

  // The following responseType method requires the type of data
  CustomCallAdapter(Type responseType) {
    this.responseType = responseType;
  }

  @Override
  public Type responseType() {
    return responseType;
  }

  @Override
  public <R> CustomCall<R> adapt(Call<R> call) {
    // CustomCall decides how to use it
    return new CustomCall<>(call);
  }
}

Provide a CustomCallAdapter Factory for providing CustomCallAdapter to Retrofit:

public static class CustomCallAdapterFactory extends CallAdapter.Factory {
  public static final CustomCallAdapterFactory INSTANCE = new CustomCallAdapterFactory();

  @Override
  public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
    // Get the original type
    Class<?> rawType = getRawType(returnType);
    // The return value must be CustomCall with generics
    if (rawType == CustomCall.class && returnType instanceof ParameterizedType) {
      Type callReturnType = getParameterUpperBound(0, (ParameterizedType) returnType);
      return new CustomCallAdapter(callReturnType);
    }
    return null;
  }
}

Register CustomCallAdapterFactory with Retrofit using addCallAdapterFactory

Retrofit retrofit = new Retrofit.Builder()
      .baseUrl("http://localhost:4567/")
      .addConverterFactory(Example09.StringConverterFactory.create())
      .addConverterFactory(GsonConverterFactory.create())
      .addCallAdapterFactory(CustomCallAdapterFactory.INSTANCE)
      .build();

Note: The addCallAdapterFactory is the same as the addConverterFactory in sequence.

See sample source code Example10.java

7. Other Notes

7.1 Retrofit.Builder

The baseUrl, addCallAdapterFactory, addConverterFactory, build methods in Retrofit.Builder are used before, and the four methods of callbackExecutor, callFactory, client and validateEagerly are not used. Here is a brief introduction.

Method purpose
callbackExecutor(Executor) Executor is used when specifying Call.enqueue, so this setting is only valid for methods with a return value of Call
callFactory(Factory) Setting up a custom okhttp3.Call.Factory, what is Factory? OkHttpClient implements the okhttp3.Call.Factory interface. The following client(OkHttpClient) finally calls this method, that is to say, they can not be shared.
client(OkHttpClient) Set up a custom OkHttpClient. In the previous version of Retrofit, different Retrofit objects share the same OkHttpClient. In 2.0, each object has different instances of OkHttpClient. So when you need to share OkHttpClient or need to customize, you can use this method, such as: processing Cookie, using stetho Mode and so on
validateEagerly(boolean) Whether the interface definition is correct when creating (Class) is invoked, rather than invoking methods, is suitable for development and testing.

7.2 Url Combination Rules of Retrofit

BaseUrl Values provided in annotations related to URL s Final result
http://localhost:4567/path/to/other/ /post http://localhost:4567/post
http://localhost:4567/path/to/other/ post http://localhost:4567/path/to/other/post
http://localhost:4567/path/to/other/ https://github.com/ikidou https://github.com/ikidou

It is not difficult to see the following rules from the above:

  • If the URL provided in the comment is the complete url, the URL will be used as the URL of the request.
  • If the url provided in the comment is incomplete and does not start with / then the requested url is the value provided in the baseUrl + comment
  • If the url provided in the comment is incomplete and starts with / the requested url is the host part of baseUrl + the value provided in the comment

7.3 Converter provided by Retrofit

Converter Gradle dependence
Gson com.squareup.retrofit2:converter-gson:2.0.2
Jackson com.squareup.retrofit2:converter-jackson:2.0.2
Moshi com.squareup.retrofit2:converter-moshi:2.0.2
Protobuf com.squareup.retrofit2:converter-protobuf:2.0.2
Wire com.squareup.retrofit2:converter-wire:2.0.2
Simple XML com.squareup.retrofit2:converter-simplexml:2.0.2
Scalars com.squareup.retrofit2:converter-scalars:2.0.2

7.4 CallAdapter provided by Retrofit:

CallAdapter Gradle dependence
guava com.squareup.retrofit2:adapter-guava:2.0.2
Java8 com.squareup.retrofit2:adapter-java8:2.0.2
rxjava com.squareup.retrofit2:adapter-rxjava:2.0.2

7.5 About Source Code

See here may be a small partner to ask why the source code does not split the class into separate files, naming can not reflect its purpose, mainly because it is convenient for you to see the source code, rather than focusing on repeated jumps, on the other hand, because the same example is inevitable to use other sections to introduce the content, so it directly uses the form of ExampleXX. Nevertheless, do not use this method in the project, we must name it well, so that we can see the name and know it well.

epilogue

The content of other blogs has been completed long ago, but at that time there was no HTTP, reflection, commentary blog, so it has not been sent, during which many bloggers wrote Retrofit2 blog, but there is no custom related content and no detailed explanation of each comment, so I decided to send out to help retrofit2. Shoes.

That's all for Retrofit2 this time. See you next time.

Posted by dragonusthei on Fri, 24 May 2019 16:11:42 -0700