Chào mừng bạn đến với bài hướng dẫn về Retrofit trong Android. Hôm nay, chúng ta sẽ sử dụng thư viện do Square phát triển này để xử lý các yêu cầu REST API trong ứng dụng Android.
Retrofit trong Android
Retrofit là một REST client có tính an toàn kiểu (type-safe) dành cho Android và Java, giúp đơn giản hóa việc sử dụng các web service dạng RESTful. Chúng ta sẽ bỏ qua các phiên bản Retrofit 1.x và đi thẳng vào Retrofit 2 vì phiên bản này có nhiều tính năng mới và API nội bộ của nó đã thay đổi so với các phiên bản trước.
Retrofit 2 mặc định tận dụng OkHttp làm lớp mạng (networking layer) và xây dựng dựa trên nó. Retrofit tự động serialize (đổi đối tượng sang định dạng khác có tính tuần tự hóa) các response dạng JSON bằng một POJO (Plain Old Java Object) mà ta phải định nghĩa trước cho cấu trúc JSON. Để serialize JSON, chúng ta cần một bộ chuyển đổi như Gson.
Ta cần thêm các phụ thuộc sau vào file build.gradle
.
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.google.code.gson:gson:2.6.2'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
Các phụ thuộc của OkHttp đã được tích hợp sẵn trong phụ thuộc của Retrofit 2. Nếu muốn sử dụng một phụ thuộc OkHttp riêng, ta nên loại bỏ phụ thuộc OkHttp khỏi Retrofit 2 như sau:
compile ('com.squareup.retrofit2:retrofit:2.1.0') {
// exclude Retrofit’s OkHttp dependency module and define your own module import
exclude module: 'okhttp'
}
compile 'com.google.code.gson:gson:2.6.2'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'
compile 'com.squareup.okhttp3:okhttps:3.4.1'
logging-interceptor
sẽ tạo ra một chuỗi log ghi lại toàn bộ response trả về.- Ngoài ra, còn có các bộ chuyển đổi khác để parse JSON sang kiểu dữ liệu cần thiết. Dưới đây là một vài trong số đó:
- Jackson :
com.squareup.retrofit2:converter-jackson:2.5.0
- Moshi :
com.squareup.retrofit2:converter-moshi:2.5.0
- Protobuf :
com.squareup.retrofit2:converter-protobuf:2.5.0
- Wire :
com.squareup.retrofit2:converter-wire:2.5.0
- Simple XML :
com.squareup.retrofit2:converter-simplexml:2.5.0
- Jackson :
Thêm quyền truy cập internet vào file AndroidManifest.xml.
Các Interceptor của OkHttp
Interceptor là một cơ chế mạnh mẽ có trong OkHttp để theo dõi, viết lại và thử lại các lời gọi/yêu cầu. Interceptor có thể được chia thành hai loại chính:
- Interceptor ứng dụng: Để đăng ký một interceptor ứng dụng, ta cần gọi phương thức
addInterceptor()
trênOkHttpClient.Builder
. - Interceptor mạng: Để đăng ký một interceptor mạng, hãy gọi
addNetworkInterceptor()
thay vìaddInterceptor()
.
Thiết lập Interface cho Retrofit
package com.journaldev.retrofitintro;
import com.journaldev.retrofitintro.pojo.MultipleResource;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
class APIClient {
private static Retrofit retrofit = null;
static Retrofit getClient() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();
retrofit = new Retrofit.Builder()
.baseUrl("<https://reqres.in>")
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();
return retrofit;
}
}
Phương thức getClient()
trong đoạn code trên sẽ được gọi mỗi khi thiết lập một interface Retrofit. Retrofit cung cấp một danh sách các annotation cho mỗi phương thức HTTP: @GET
, @POST
, @PUT
, @DELETE
, @PATCH
và @HEAD
.
Đây là class APIInterface.java
package com.journaldev.retrofitintro;
import com.journaldev.retrofitintro.pojo.MultipleResource;
import com.journaldev.retrofitintro.pojo.User;
import com.journaldev.retrofitintro.pojo.UserList;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.Query;
interface APIInterface {
@GET("/api/unknown")
Call<MultipleResource> doGetListResources();
@POST("/api/users")
Call<User> createUser(@Body User user);
@GET("/api/users?")
Call<UserList> doGetUserList(@Query("page") String page);
@FormUrlEncoded
@POST("/api/users?")
Call<UserList> doCreateUserWithField(@Field("name") String name, @Field("job") String job);
}
Trong class trên, chúng ta đã định nghĩa một số phương thức thực hiện các yêu cầu HTTP với annotation. @GET("api/unknown")
sẽ gọi phương thức doGetListResources();
. doGetListResources()
là tên phương thức.
MultipleResource.java
là một class Model POJO cho đối tượng response, được dùng để map các tham số của response với các biến tương ứng. Các class POJO này đóng vai trò là kiểu trả về của phương thức. Dưới đây là một class POJO đơn giản cho MultipleResource.java
.
package com.journaldev.retrofitintro.pojo;
import com.google.gson.annotations.SerializedName;
import java.util.ArrayList;
import java.util.List;
public class MultipleResource {
@SerializedName("page")
public Integer page;
@SerializedName("per_page")
public Integer perPage;
@SerializedName("total")
public Integer total;
@SerializedName("total_pages")
public Integer totalPages;
@SerializedName("data")
public List<Datum> data = null;
public class Datum {
@SerializedName("id")
public Integer id;
@SerializedName("name")
public String name;
@SerializedName("year")
public Integer year;
@SerializedName("pantone_value")
public String pantoneValue;
}
}
Annotation @SerializedName
được dùng để chỉ định tên của trường trong phản hồi JSON.
Xem qua class POJO và sao chép nó vào cấu trúc project Android Studio của bạn. Các class POJO được bao bọc trong một class Call
của Retrofit. Lưu ý là một JSONArray
được serialize thành một List
chứa các đối tượng trong các class POJO.
Tham số phương thức: Có rất nhiều lựa chọn tham số để truyền vào một phương thức:
@Body
: Gửi các đối tượng Java dưới dạng body của request.@Path
: Sử dụng URL động.@Query
: Ta có thể thêm một tham số phương thức với@Query
và tên của tham số query đi kèm kiểu dữ liệu. Để mã hóa một query theo URL, sử dụng định dạng:@Query(value = "auth_token",encoded = true) String auth_token
.@Field
: Gửi dữ liệu dưới dạng form-urlencoded (định dạng dữ liệu biểu mẫu gửi qua HTTP). Việc này yêu cầu một annotation@FormUrlEncoded
được đính kèm với phương thức. Tham số@Field
chỉ hoạt động với POST.
Lưu ý: @Field
yêu cầu một tham số bắt buộc. Trong trường hợp @Field
có tính tùy chọn, ta có thể sử dụng @Query
và truyền giá trị null
.
Cấu trúc project ví dụ dùng Retrofit trên Android
Package pojo định nghĩa bốn class dữ liệu cho mỗi response từ điểm cuối (endpoint) API (được định nghĩa trong class APIInterface.java).
File User.java
:
package com.journaldev.retrofitintro.pojo;
import com.google.gson.annotations.SerializedName;
public class User {
@SerializedName("name")
public String name;
@SerializedName("job")
public String job;
@SerializedName("id")
public String id;
@SerializedName("createdAt")
public String createdAt;
public User(String name, String job) {
this.name = name;
this.job = job;
}
}
Class trên được sử dụng để tạo body của response cho phương thức createUser()
.
File UserList.java
:
package com.journaldev.retrofitintro.pojo;
import com.google.gson.annotations.SerializedName;
import java.util.ArrayList;
import java.util.List;
public class UserList {
@SerializedName("page")
public Integer page;
@SerializedName("per_page")
public Integer perPage;
@SerializedName("total")
public Integer total;
@SerializedName("total_pages")
public Integer totalPages;
@SerializedName("data")
public List<Datum> data = new ArrayList();
public class Datum {
@SerializedName("id")
public Integer id;
@SerializedName("first_name")
public String first_name;
@SerializedName("last_name")
public String last_name;
@SerializedName("avatar")
public String avatar;
}
}
File CreateUserResponse.java
:
package com.journaldev.retrofitintro.pojo;
import com.google.gson.annotations.SerializedName;
public class CreateUserResponse {
@SerializedName("name")
public String name;
@SerializedName("job")
public String job;
@SerializedName("id")
public String id;
@SerializedName("createdAt")
public String createdAt;
}
MainActivity.java
là nơi chúng ta gọi mỗi điểm cuối API đã được định nghĩa trong class Interface và hiển thị từng trường trong một Toast/TextView.
package com.journaldev.retrofitintro;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;
import com.journaldev.retrofitintro.pojo.CreateUserResponse;
import com.journaldev.retrofitintro.pojo.MultipleResource;
import com.journaldev.retrofitintro.pojo.User;
import com.journaldev.retrofitintro.pojo.UserList;
import java.util.List;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class MainActivity extends AppCompatActivity {
TextView responseText;
APIInterface apiInterface;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
responseText = (TextView) findViewById(R.id.responseText);
apiInterface = APIClient.getClient().create(APIInterface.class);
/**
GET List Resources
**/
Call<MultipleResource> call = apiInterface.doGetListResources();
call.enqueue(new Callback<MultipleResource>() {
@Override
public void onResponse(Call<MultipleResource> call, Response<MultipleResource> response) {
Log.d("TAG",response.code()+"");
String displayResponse = "";
MultipleResource resource = response.body();
Integer text = resource.page;
Integer total = resource.total;
Integer totalPages = resource.totalPages;
List<MultipleResource.Datum> datumList = resource.data;
displayResponse += text + " Page\\n" + total + " Total\\n" + totalPages + " Total Pages\\n";
for (MultipleResource.Datum datum : datumList) {
displayResponse += datum.id + " " + datum.name + " " + datum.pantoneValue + " " + datum.year + "\\n";
}
responseText.setText(displayResponse);
}
@Override
public void onFailure(Call<MultipleResource> call, Throwable t) {
call.cancel();
}
});
/**
Create new user
**/
User user = new User("morpheus", "leader");
Call<User> call1 = apiInterface.createUser(user);
call1.enqueue(new Callback<User>() {
@Override
public void onResponse(Call<User> call, Response<User> response) {
User user1 = response.body();
Toast.makeText(getApplicationContext(), user1.name + " " + user1.job + " " + user1.id + " " + user1.createdAt, Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(Call<User> call, Throwable t) {
call.cancel();
}
});
/**
GET List Users
**/
Call<UserList> call2 = apiInterface.doGetUserList("2");
call2.enqueue(new Callback<UserList>() {
@Override
public void onResponse(Call<UserList> call, Response<UserList> response) {
UserList userList = response.body();
Integer text = userList.page;
Integer total = userList.total;
Integer totalPages = userList.totalPages;
List<UserList.Datum> datumList = userList.data;
Toast.makeText(getApplicationContext(), text + " page\\n" + total + " total\\n" + totalPages + " totalPages\\n", Toast.LENGTH_SHORT).show();
for (UserList.Datum datum : datumList) {
Toast.makeText(getApplicationContext(), "id : " + datum.id + " name: " + datum.first_name + " " + datum.last_name + " avatar: " + datum.avatar, Toast.LENGTH_SHORT).show();
}
}
@Override
public void onFailure(Call<UserList> call, Throwable t) {
call.cancel();
}
});
/**
POST name and job Url encoded.
**/
Call<UserList> call3 = apiInterface.doCreateUserWithField("morpheus","leader");
call3.enqueue(new Callback<UserList>() {
@Override
public void onResponse(Call<UserList> call, Response<UserList> response) {
UserList userList = response.body();
Integer text = userList.page;
Integer total = userList.total;
Integer totalPages = userList.totalPages;
List<UserList.Datum> datumList = userList.data;
Toast.makeText(getApplicationContext(), text + " page\\n" + total + " total\\n" + totalPages + " totalPages\\n", Toast.LENGTH_SHORT).show();
for (UserList.Datum datum : datumList) {
Toast.makeText(getApplicationContext(), "id : " + datum.id + " name: " + datum.first_name + " " + datum.last_name + " avatar: " + datum.avatar, Toast.LENGTH_SHORT).show();
}
}
@Override
public void onFailure(Call<UserList> call, Throwable t) {
call.cancel();
}
});
}
}
apiInterface = APIClient.getClient().create(APIInterface.class);
được dùng để khởi tạo APIClient. Để map class Model với response, ta sử dụng: MultipleResource resource = response.body();
.
Ứng dụng trên khi chạy sẽ gọi từng endpoint và hiển thị một thông báo Toast tương ứng cho chúng.
Tổng kết
Bài hướng dẫn sử dụng Retrofit trong Android của chúng tôi đến đây là kết thúc. Bạn có thể tải xuống project ví dụ ở trên từ link này. Hãy áp dụng ngay những kĩ thuật này để tối ưu hóa việc quản lý các yêu cầu mạng và mở rộng khả năng kết nối hiệu quả với các API.