Using retrofit2 in Android Studio - 51bootcamp/common GitHub Wiki
1. Why retrofit2?
REST API์ ์๋นํ ์ ์ฌํ๊ฒ interface๋ฅผ ๋ง๋ค์ด์ ์๋์ผ๋ก Json์ parsing์ ํ ์ ์๋ค.
2. retrofit2 official tutorial
add this line in build.gradle
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
make interface
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
make retrofit object which implements of GitHubService interface
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
GitHubService service = retrofit.create(GitHubService.class);
Each Call from the created GitHubService can make a synchronous or asynchronous HTTP request to the remote webserver.
Call<List<Repo>> repos = service.listRepos("Ricci0708");
3. ์ค์ ๋ก ์ฌ์ฉํ๊ธฐ
์์ tutorial๋ง์ผ๋ก๋ REST API๋ฅผ ์ด์ฉํ JSONFILE์ ํ์ฑํ๊ฑฐ๋ ์๋์ผ๋ก ํด๋์ค object๋ฅผ ๋ง๋๋ ๊ฒ์ ์ด๋ ต๋ค. ์ด๋ฅผ ํ๊ธฐ ์ํด์๋ build.gradle์
//for manage json file
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
//okhttp
implementation "com.squareup.okhttp3:okhttp:3.12.1"
implementation 'com.squareup.okhttp3:logging-interceptor:3.12.1'
gson์ google์์ ๋ง๋ Java serialization/deserialization library์ด๊ณ okhttp๋ retrofit์ด http๋ฅผ ์ด์ฉํด์ ํต์ ์ ํ๋๋ฐ ๊ทธ๋ฅ retrofit2๋ก ๋ง์ผ๋ก๋ ๋ก๊น ์ ํ๋ ๊ฒ์ด ๋ถ๊ฐ๋ฅํด์ logging์ ์ํด์ implement๋ฅผ ํ๋ค.
RetrofitInstance๋ผ๋ ์๋ฐ ํ์ผ์ ๋ง๋ ํ ์๋์ ๋ด์ฉ์ ๋ถ์ฌ๋ฃ๋๋ค.
public class RetrofitInstance {
private static Retrofit retrofit;
private static OkHttpClient client;
//URL can be easily changed
private static final String BASE_URL = "http://10.0.2.2:8000/api/v1/";
public static Retrofit getRetrofitInstance() {
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(getOkhttpClient())
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
private static OkHttpClient getOkhttpClient(){
if (client == null){
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
client = new OkHttpClient.Builder().addInterceptor(interceptor).build();
}
return client;
}
}
์ด ์๋ฐ ํ์ผ๊ณผ ๊ฐ์ ๊ฒฝ์ฐ์๋ retrofit object๋ฅผ ๋ฐํํด์ฃผ๋ ์๋ฐ ํ์ผ์ด๋ค. getRetrofitInstance๋ retrofit object๊ฐ ์กด์ฌํ๋ค๋ฉด ๊ทธ object๋ฅผ ๋ฐํํ๊ณ ์๋ค๋ฉด ์์ฑ์ ํํ์ returnํ๋ค. ์ด์ ๊ฐ์ด ํ๋ ์ด์ ๋ retrofit object๋ฅผ ํ์์์ด ๋ง์ด ์์ฑํ์ง ์๊ธฐ ์ํด์์ด๋ค. ๋ํ BASE_URL์ด๋ผ๋ String๋ง์ ๋ฐ๊ฟ์ผ๋ก์จ ์์ฒญํ๋ ์๋ฒ์ ์ฃผ์๋ฅผ ์ฝ๊ฒ ๋ฐํ๊ฐ๋ฅํ๋ค. ์ฃผ์!BASE_URL๊ณผ ๊ฐ์ ๊ฒฝ์ฐ์๋ ๋ฐ๋์ 'http://'๋ก ์์ํ๊ณ '/'๋ก ๋์ด ๋์ผํ๋ค. ๋ํ ์๋๋ก์ด๋ ์คํ๋์ค ์๋ฎฌ๋ ์ดํฐ์ loopback ipaddress๋ 10.0.2.2์ด๋ค. localhost๋ฅผ ์๋ฒ์ ํต์ ์ ํ๊ธฐ ์ํด์๋ 10.0.2.2๋ก ์ค์ ์ ํด์ผ ๊ฐ๋ฅํ๋ค.
๊ทธ ํ project์ interface๋ฅผ ํ๋ ์์ ํ๋ค. common๊ณผ ๊ฐ์๊ฒฝ์ฐ์๋
public interface ApiInterface {
@GET("class/{date}/")
Call<ClassList> getClassList(@Path("date") String date);
@GET("class/{date}/{classID}")
Call<Class> getClassInfo(@Path("date") String date,
@Path("classID") Integer classID);
@POST("login/")
Call<User>login(@Body User user);
}
์ด interface๋ฅผ ๋ณด๋ฉด REST API์ ์๋นํ ์ ์ฌํ ๊ตฌ์กฐ๋ฅผ ๊ฐ์ง๊ณ ์์์ ํ์ธํ ์ ์๋ค. ์ด๋ฅผ ํตํด์ REST API๋ฌธ์๋ฅผ ๋ณด๋ฉด์ ์ฝ๊ฒ ์ ์ฉ์ด ๊ฐ๋ฅํ๋ค.
@GET("class/{date}/")
Call<ClassList> getClassList(@Path("date") String date);
์์ API๋ get method๋ก class/{date}์ request๋ฅผ ๋ณด๋ธ๋ค๋ ์๋ฏธ์ด๋ค. {date}์ ๊ฒฝ์ฐ์๋ @Path("date") String date์ ์ํด์ ์ฃผ์ด์ง๋ ์ธ์์ date๊ฐ ๋ค์ด๊ฐ๋ค๋ ์๋ฏธ์ด๋ค.Call๋ ์ด Call์ return type์ ClassList๋ผ๋ ์๋ฏธ์ด๋ค. ๋ง์ฝ ๋จ์ํ ์ธ์๋ฅผ ํ๋๋ง์ ๋ฐ์ง ์๊ณ ์ฌ๋ฌ๊ฐ๋ฅผ ๋ฐ๋๋ค๋ฉด
@GET("class/{date}/{classID}")
Call<Class> getClassInfo(@Path("date") String date,
@Path("classID") Integer classID);
์์ ๊ฐ์ ๋ฐฉ๋ฒ์ผ๋ก ์์ฒญ์ ๋ณด๋ด๋ฉด ๋๋ค.
@POST("login/")
Call<User>login(@Body User user);
์์ API๋ POST method๋ก login/์ request๋ณด๋ธ ๋ค๋ ๊ฒ์ ์๋ฏธํ๋ ๊ฒ์ด๊ณ , @Body์๋ User user๋ฅผ ๋ฃ์ด์ ์์ฒญ์ ํ๋ค๋ ์๋ฏธ์ด๋ค. retrun type๋ํ User๋ผ๋ ์๋ฏธ์ด๋ค.
์ด๋ฅผ ์ํด์๋ ๊ฐ๊ฐ API๋ฅผ ํตํด์ ๋ฐ๋ JSON Object๋ฅผ ํ์ฑํด์ ๋ด์ ํด๋์ค๊ฐ ํ์ํ๋ค.
์๋ฅผ ๋ค์๋ฉด common api์ค ํ๋์ธ @GET api/v1/class/$date/$classID/ ์ ๊ฒฝ์ฐ์๋ return ๋๋ Json object๋
{
"classID": 1,
"className": "Booktalking with Author",
"expertName": "lkh",
"minGuestCount": 4,
"maxGuestCount": 8,
"availableTimeTable": [
{
"timeTableIdx": 11,
"startTime": "03 : 00 PM",
"endTime": "05 : 00 PM",
"isBooked": false
},
{
"timeTableIdx": 12,
"startTime": "05 : 00 PM",
"endTime": "07 : 00 PM",
"isBooked": false
}
]
}
์ ํํ๋ฅผ ๊ฐ์ง๊ณ ์๋ค. ์ด๋ฅผ ๋ด์ Class๋ฅผ ๋ง๋๋ ๊ฒ์ ๋งค์ฐ ๊ท์ฐฎ๊ณ ์ง์ฆ๋๋ ์ผ์ด๋ค. ์ด๋ฅผ ์๋์ผ๋ก ํด์ฃผ๋ site๊ฐ ์๋ค! http://www.jsonschema2pojo.org/ ์ ๋ค์ด๊ฐ์ ๋ฐํ๋ฐ๋ json response๋ฅผ ๋ฃ์ผ๋ฉด java class๋ฅผ ์๋์ผ๋ก ์์ฑํด์ค๋ค. ์ด๋ฅผ ํตํด์ ์์ฑ๋ Class ํด๋์ค๋
public class Class implements Parcelable {
@SerializedName("classID")
@Expose
private Integer classID;
@SerializedName("className")
@Expose
private String className;
@SerializedName("expertName")
@Expose
private String expertName;
@SerializedName("minGuestCount")
@Expose
private Integer minGuestCount;
@SerializedName("maxGuestCount")
@Expose
private Integer maxGuestCount;
@SerializedName("availableTimeTable")
@Expose
private List<TimeTable> availableTimeTable = new ArrayList<TimeTable>();
@SerializedName("coverImage")
@Expose
private List<String> coverImage = new ArrayList<String>();
์ ๊ฐ์ ๊ตฌ์กฐ๋ฅผ ๊ฐ์ง๊ณ ์๋ค. ์ด๋ฅผ ํตํด์ API๋ก ์์ฒญ์ ํ๊ณ ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํ๋ฉด ์ ๋๋ก ์ฃผ๊ณ ๋ฐ์์ ํ์ธํ ์์๋ค!
์ค์ Common์์ retrofit์ ํตํด์ ์ฃผ๊ณ ๋ฐ๋ code์ ์์์ด๋ค.
ApiInterface service = RetrofitInstance.getRetrofitInstance()
.create(ApiInterface.class);
//TODO(woonjin): get the today's date and change the arguments
Call<Class> request = service.getClassInfo(selectedDate,
classList.getClassList().get(position).getClassID());
request.enqueue(new Callback<Class>() {
@Override
public void onResponse(Call<Class> call, Response<Class> response) {
Context context = container.getContext();
selectedClass = response.body();
Log.d("ClassName", selectedClass.getClassName());
}
@Override
public void onFailure(Call<Class> call, Throwable t) {
//TODO (woongjin) : how to deal with failure
}
});
D/OkHttp: --> GET http://10.0.2.2:8000/api/v1/class/2019-01-17/4
D/OkHttp: --> END GET
D/OkHttp: <-- 200 OK http://10.0.2.2:8000/api/v1/class/2019-01-17/4 (10ms)
Date: Thu, 17 Jan 2019 20:54:03 GMT
Server: WSGIServer/0.2 CPython/3.7.0
Content-Type: application/json
X-Frame-Options: SAMEORIGIN
Content-Length: 223
{"classID": 4, "className": "A Wine Tasting", "expertName": "jmj", "minGuestCount": 6, "maxGuestCount": 10, "availableTimeTable": [{"timeTableIdx": 9, "startTime": "09 : 00 AM", "endTime": "11 : 00 AM", "isBooked": false}]}
<-- END HTTP (223-byte body)
D/ClassName: A Wine Tasting
์ด๋ฐ์์ผ๋ก ๋ก๊ทธ๊ฐ ์ฐํ๋ค!
4. ๋ ์๊ฐํด๋ณผ๊ฒ
๋ฌผ๋ก ์ด๊ฒ์ ๊ฑฐ์ ๊ฐ๋จํ๊ฒ๋ง ์ฌ์ฉํ๊ธฐ ์ํด์ ํ๊ฒ์ด๋ค. ์๋๋ผ๋ฉด on failure์์ ์์ธ์ฒ๋ฆฌ๋ฅผ ํด์ฃผ์ด์ผํ๋ค. On failure์ ๊ฒฝ์ฐ์๋ ์๋ฒ๊ฐ ์์ ์๋ต์ด ์๊ฑฐ๋, jsonํ์ผ์ ํ์ฑ์ ์คํจํ์๋, ๋ค์ด๊ฐ๋ ๋ธ๋ญ์ด๋ค. ์๋ฒ์์ 400๋ฒ๋์ response๋ฅผ ๋ณด๋ด๋๋ผ๋ on success๋ก ๋์ด๊ฐ๊ธฐ ๋๋ฌธ์ on success๋ด์์ ์์ธ์ฒ๋ฆฌ๋ฅผ ํด์ฃผ์ด์ผ ํ๋ค. ๋ํ enqueue๋ ๋น๋๊ธฐํ ๋ฐฉ์์ ์ฌ์ฉํจ์ผ๋ก ๋ง์ฝ ๋๊ธฐํ ๋ฐฉ๋ฒ์ ์ฌ์ฉํด์ผ ํ๋ค๋ฉด ์กฐ๊ธ๋ ์์๋ด์ผํ ํ์๊ฐ ์๋ค. ๊ฐ์ด ๊ณต๋ถํด๋ด์~