1. 程式人生 > >Retrofit 2.0: The biggest update yet on the best HTTP Client Library for Android

Retrofit 2.0: The biggest update yet on the best HTTP Client Library for Android

Retrofit is one of the most popular HTTP Client Library for Android as a result of its simplicity and its great performance compare to the others.

Anyway its weakness is there is no any straight way to cancel the ongoing transaction in Retrofit 1.x. If you want to do that you have to call it on Thread and kill it manually which is quite hard to manage.

Square gave a promise years ago that this feature will be available on Retrofit 2.0 but years passed, there is still no updated news on this.

Until last week, Retrofit 2.0 just passed its Release Candidate stage to Beta 1 and has been publicly launched to everyone. After giving it a try, I must say that I am quite impressed on its new pattern and its new features. There are a lot of changes in the good way. I will describe those in this article. Let's get started !

Same Old Package with New Version

If you want to import Retrofit 2.0 into your project, add this line to your build.gradle in dependencies section.

compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4'

Sync your gradle files and you can now use Retrofit 2.0 =)

And as you see, Retrofit 2 package name is not the same as the previous version. It's now com.squareup.retrofit2

.

New Service Declaration. No more Synchronous and Asynchronous.

In regard to service interface declaration in Retrofit 1.9, if you want to declare a synchronous function, you have to declare like this:

/* Synchronous in Retrofit 1.9 */

public interface APIService {

    @POST("/list")
    Repo loadRepo();

}

And you have to declare an asynchronous one like this:

/* Asynchronous in Retrofit 1.9 */

public interface APIService {

    @POST("/list")
    void loadRepo(Callback<Repo> cb);

}

But on Retrofit 2.0, it is far more simple since you can declare with only just a single pattern.

import retrofit.Call;

/* Retrofit 2.0 */

public interface APIService {

    @POST("/list")
    Call<Repo> loadRepo();

}

The way to call a created service is also changed into the same pattern as OkHttp. To call is as a synchronous request, just call execute or call enqueue to make an asynchronous request.

Synchronous Request

// Synchronous Call in Retrofit 2.0

Call<Repo> call = service.loadRepo();
Repo repo = call.execute();

The source code above will block the thread so you cannot call it on Main Thread in Android or you will face NetworkOnMainThreadException. If you want to call execute method, you have to do it on background thread.

Asynchronous Request

// Asynchronous Call in Retrofit 2.0

Call<Repo> call = service.loadRepo();
call.enqueue(new Callback<Repo>() {
    @Override
    public void onResponse(Response<Repo> response) {
        // Get result Repo from response.body()
    }

    @Override
    public void onFailure(Throwable t) {

    }
});

The above code will make a request in the background thread and retreive a result as an Object which you can extract from response with response.body() method. Please note that those call methods: onResponse and onFailure will be called in Main Thread.

I suggest you to use enqueue. It fits Android OS behavior best.

Ongoing Transaction Cancellation

The reason behind the service pattern changing to Call is to make the ongoing transaction be able to be cancelled. To do so, just simply call call.cancel()

call.cancel();

The transaction would be cancelled shortly after that. Easy, huh? =D

New Service Creation. Converter is now excluded from Retrofit.

In Retrofit 1.9, GsonConverter is included in the package and is automatically initiated upon RestAdapter creation. As a result, the json result from server would be automatically parsed to the defined Data Access Object (DAO).

But in Retrofit 2.0, Converter is not included in the package anymore. You need to plug a Converter in yourself or Retrofit will be able to accept only the String result. As a result, Retrofit 2.0 doesn't depend on Gson anymore.

If you want to accept json result and make it parse into DAO, you have to summon Gson Converter as a separate dependency.

compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2'

And plug it in through addConverterFactory. Please note that RestAdapter is now also renamed to Retrofit.

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://api.nuuneoi.com/base/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();

        service = retrofit.create(APIService.class);

Here is the list of official Converter modules provided by Square. Choose one that fits your requirement best.

Gson: com.squareup.retrofit:converter-gson
Jackson: com.squareup.retrofit:converter-jackson
Moshi: com.squareup.retrofit:converter-moshi
Protobuf: com.squareup.retrofit:converter-protobuf
Wire: com.squareup.retrofit:converter-wire
Simple XML: com.squareup.retrofit:converter-simplexml

You also can create a custom converter yourself by implementing a Converter.Factory interface.

I support this new pattern. It makes Retrofit more clear what it actually does.

Custom Gson Object

In case you need to adjust some format in json, for example, Date Format. You can do that by creating a Gson object and pass it to GsonConverterFactory.create()

        Gson gson = new GsonBuilder()
                .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
                .create();

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://api.nuuneoi.com/base/")
                .addConverterFactory(GsonConverterFactory.create(gson))
                .build();

        service = retrofit.create(APIService.class);

Done.

New URL resolving concept. The same way as <a href>

Retrofit 2.0 comes with new URL resolving concept. Base URL and @Url have not just simply been combined together but have been resolved the same way as what <a href="..."> does instead. Please take a look for the examples below for the clarification.

Here is my suggestion on the new URL declaration pattern in Retrofit 2.0:

- Base URL: always ends with /

- @Url: DO NOT start with /

for instance

public interface APIService {

    @POST("user/list")
    Call<Users> loadUsers();

}

public void doSomething() {
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("http://api.nuuneoi.com/base/")
            .addConverterFactory(GsonConverterFactory.create())
            .build();

    APIService service = retrofit.create(APIService.class);
}

loadUsers from code above will fetch data from http://api.nuuneoi.com/base/user/list

Moreover we also can declare a full URL in @Url in Retrofit 2.0:

public interface APIService {

    @POST("http://api.nuuneoi.com/special/user/list")
    Call<Users> loadSpecialUsers();

}

Base URL will be ignored for this case.

You will see that there is a major change on URL resolving. It is totally different from the previous version. If you want to move your code to Retrofit 2.0, don't forget to fix those URLs part of code.

OkHttp is now required

OkHttp is set to optional in Retrofit 1.9. If you want to let Retrofit use OkHttp as HTTP connection interface, you have to manually include okhttp as a dependency yourself.

But in Retrofit 2.0, OkHttp is now required and is automatically set as a dependency. The code below is snapped from pom file of Retrofit 2.0. You have no need to do anything.

  <dependencies>
    <dependency>
      <groupId>com.squareup.okhttp</groupId>
      <artifactId>okhttp</artifactId>
    </dependency>

    ...
  </dependencies>

OkHttp is automatically used as a HTTP interface in Retrofit 2.0 in purpose to enabling the OkHttp's Call pattern as decribed above.

onResponse is still called eventhough there is a problem with the response

In Retrofit 1.9, if the fetched response couldn't be parsed into the defined Object, failure will be called. But in Retrofit 2.0, whether the response is be able to parse or not, onResponse will be always called. But in the case the result couldn't be parsed into the Object, response.body() will return as null. Don't forget to handle for the case.

If there is any problem on the response, for example, 404 Not Found. onResponse will also be called. You can retrieve the error body from response.errorBody().string().

Response/Failure logic is quite different from Retrofit 1.9. Be careful on handling for all the cases if you decide to move to Retrofit 2.0.

Missing INTERNET Permission cause SecurityException throwing

In Retrofit 1.9, if you forget to add INTERNET permission into your AndroidManifest.xml file. Asynchronous request will immediately fall into failure callback method with PERMISSION DENIED error message. None of exception is thrown.

But in Retrofit 2.0, when you call call.enqueue or call.executeSecurityException will be immediately thrown and may cause crashing if you do not handle the case with try-catch.

The behavior is just like the same when you manually call HttpURLConnection. Anyway this issue is not a big deal since when INTERNET permission is added into AndroidManifest.xml, there is nothing to concern anymore.

Use an Interceptor from OkHttp

On Retrofit 1.9 you could use RequestInterceptor to intercept a Request but it is already removed on Retrofit 2.0 since the HTTP connection layer has been moved to OkHttp.

As a result, we have to switch to an Interceptor from OkHttp from now on. First you have to create a OkHttpClient object with an Interceptor like this:

        OkHttpClient client = new OkHttpClient();
        client.interceptors().add(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Response response = chain.proceed(chain.request());

                // Do anything with response here

                return response;
            }
        });

And the pass the created client into Retrofit's Builder chain.

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://api.nuuneoi.com/base/")
                .addConverterFactory(GsonConverterFactory.create())
                .client(client)
                .build();

That's all.

To learn more about what OkHttp Interceptor can do, please browse into OkHttp Interceptors.

Certificate Pinning

As same as an Interceptor, creation of an OkHttp client instance is required if you want to apply a Certificate Pinning with your connection. Here is the example code snippet. First, defines an OkHttp client instance with Certificate Pinning information:

OkHttpClient client = new OkHttpClient.Builder()
        .certificatePinner(new CertificatePinner.Builder()
                .add("publicobject.com", "sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=")
                .add("publicobject.com", "sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=")
                .add("publicobject.com", "sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=")
                .add("publicobject.com", "sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=")
                .build())
        .build();

Assign the OkHttp client created within Retrofit builder chain.

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("http://api.nuuneoi.com/base/")
        .addConverterFactory(GsonConverterFactory.create())
        .client(client)
        .build();

For more information about sha1 hash for Certificate Pinning ... Google would help a lot, just simple search for it how to achieve that piece of data.

RxJava Integration with CallAdapter

Beside declaring interface with Call<T> pattern, we also could declare our own type as well, for example, MyCall<T>. The mechanic is called "CallAdapter" which is available on Retrofit 2.0

There is some ready-to-use CallAdapter module available from Retrofit team. One of the most popular module might be CallAdapter for RxJava which will return as Observable<T>. To use it, two modules must be included as your project's dependencies.

    compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2'
    compile 'io.reactivex:rxandroid:1.0.1'

Sync Gradle and add addCallAdapterFactory in Retrofit Builder chain like this:

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://api.nuuneoi.com/base/")
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build();

Your Service interface is now able to return as Observable<T>!

public interface APIService {

    @POST("list")
    Call<DessertItemCollectionDao> loadDessertList();

    @POST("list")
    Observable<DessertItemCollectionDao> loadDessertListRx();

}

You can use it in the exact same RxJava way. In addition, if you want to let code inside subscribe part called on Main Thread, observeOn(AndroidSchedulers.mainThread()) is needed to be added to the chain.

        Observable<DessertItemCollectionDao> observable = service.loadDessertListRx();

        observable.subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .unsubscribeOn(Schedulers.io())
            .subscribe(new Subscriber<DessertItemCollectionDao>() {
                @Override
                public void onCompleted() {
                    Toast.makeText(getApplicationContext(),
                            "Completed",
                            Toast.LENGTH_SHORT)
                        .show();
                }

                @Override
                public void onError(Throwable e) {
                    Toast.makeText(getApplicationContext(),
                            e.getMessage(),
                            Toast.LENGTH_SHORT)
                        .show();
                }

                @Override
                public void onNext(DessertItemCollectionDao dessertItemCollectionDao) {
                    Toast.makeText(getApplicationContext(),
                            dessertItemCollectionDao.getData().get(0).getName(),
                            Toast.LENGTH_SHORT)
                        .show();
                }
            });

Done ! I believe that RxJava fan is very satisfying with this change =D

Conclusion

There are also some other changes, you can check for the official Change Log for more details. Anyway I believe that I have already covered the main issues in this article.

You may be curious that is it time to move to Retrofit 2.0 yet? Since it is still in the beta stage so you may want to stay with 1.9 first except you are an early adopter like me, Retrofit 2.0 works pretty great and there is no any bug found yet based on my own experiment.

Please note that Retrofit 1.9 official document was already removed from Square github website. I suggest you to start studying for Retrofit 2.0 right now and consider moving to the latest version in very near future. =D

Author: nuuneoi (Android GDE, CTO & CEO at The Cheese Factory)
A full-stack developer with more than 6 years experience on Android Application Development and more than 12 years in Mobile Application Development industry. Also has skill in Infrastucture, Service Side, Design, UI&UX, Hardware, Optimization, Cooking, Photographing, Blogging, Training, Public Speaking and do love to share things to people in the world!