본문 바로가기
개발일기/스타일판다

retrofit 적용하기

by 유주원 2016. 6. 27.

기존의 개인 프로젝트였던 '싸다구'에서는 네트워크 통신을 위한 라이브러리를 volley를 사용해왔다. 하지만 이번 '스판다'는 기존 걸 그대로 copy 해서 쓰는 건 성격 상 맞지 않아서 많이들 쓰고 있는 retrofit을 적용해 보기로 했다.


우선 retrofit과 volley의 큰 차이를 두자면 retrofit은 restful api에 좀 더 초점을 둔 방식이라고 할까?

retrofit에서는 해당 프로토콜과 그에 따르는 파라미터를 interface로 명시를 해줘야 하는 특징을 가지고 있다. 


그럼 이제 retrofit을 한 번 적용해서 데이터를 가져와 보자.


아래와 같이 gradle에 라이브러리를 추가해 준다. retrofit에서는 수신된 JSON을 객체로 자동 변환해주는 GSON도 많이 쓰는데 이것도 같이 추가해 주자.


dependencies {

    ....

    compile 'com.squareup.retrofit2:retrofit:2.0.2'

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

    ....

}


proguard를 사용한다면 아래와 같이 추가해주자.


-dontwarn retrofit2.**

-keep class retrofit2.** { *; }

-keepattributes Signature

-keepattributes Exceptions


잠깐 위에서 언급했던 대로 retrofit에서는 호출하려는 네트워크 서비스에 대한 interface를 맞춰주어야 한다.


아래와 비슷한 형태로 interface를 만들자.


public interface testInterface{

    

    @GET("test")

    Call<TestItem> getInfo(@Query("limit") int limit,

                              @Query("query") String query);

}


위의 interface는 get에 대한 호출을 interface로 정의한 것이다. 만약에 POST 호출일 경우에는 GET 대신 POST라고 써주면 된다. GET 옆의 주소 url은 base 주소와의 결합 형태로 생각하면 된다.


만약 base 주소가 "http://yujuwon.tistory.com/post/1111" 이고 @GET("test")라고 선언이 되었다면 이 함수에 해당하는 주소는 "http://yujuwon.tistory.com/post/test"가 된다.


만약에 base 주소를 "http://yujuwon.tistory.com/post/1111/"라고 명시했을 경우에는 결과 주소는 "http://yujuwon.tistory.com/post/1111/test"가 됨을 주의하자. "/"의 유무에 따라 결과 url 주소는 확연히 달라진다.


만약 @GET("/test")라고 명시가 되어 있다면, 결과 주소는 "http://yujuwon.tistory.com/test"가 된다.


적절한 상화에 맞춰서 주소 매핑을 잘 하자.


위에서 보면 Call이란 객체를 볼수가 있다. Call은 retrofit에서 정의한 HTTP request와 response의 쌍을 지닌 객체라 생각하면 쉬울 듯 하다. Call 에서는 TestItem이라는 객체를 쓰고 있는데 이것은 이제 실제로 우리가 HTTP 통신을 통해 수신한 JSON 등이 변환될 객체이다.


아래와 같이 수신될 JSON 포멧에 맞추어서 TestItem 객체를 정의해 주자.




public class TestItem {

    @SerializedName("data")

    public List<DataList> mDatalist;

}


public class DataList {

    @SerializedName("column1")

    private String column1 = "";


    @SerializedName("column2")

    private String column2 = "";

}    


JSON 포멧


{"data": [{"column1" : "1"}, {"column2" : "2"}]}


만약에 JSON 포멧과 해당 객체의 형식이 일치하지 않는다면 에러가 발생하게 되니 다시 한번 주의하자.


이렇게 Interface와 JSON 변환 객체를 정의하고 나면 아래와 같이 사용할 수가 있다.


Retrofit client = new Retrofit.Builder().baseUrl("baseurl").addConverterFactory(GsonConverterFactory.create()).build();


testInterface service = client.create(testInterface.class);

Call<TestItem> repos = service.getInfo(100, "today");


repos.enqueue(new Callback<TestItem>() {

    @Override

    public void onResponse(Call<TestItem> call, Response<TestItem> response){

TestItem item = (TestItem)response.body();

....

    }


@Override

public void onFailure(Call<TestItem> call, Throwable t) {

    ....

}

});


이슈 1


onFailure()의 Throwable t.getMessage()에서 아래와 같은 메시지를 발생시키고 데이터 못 가져옴.


Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $


이 문제는 아까 위에서 언급한 것처럼 JSON 포멧이 제대로 맞춰지지 않아서 생기는 문제이다. JSON 데이터를 보면 해당 배열안에 값이 1개만 있는데 나 같은 경우에는 Call<List<TestItem>>으로 맞춰주다 보니 위와 같은 에러를 발생 시켰다. Call<TestItem>으로 코드를 변경하면 정상적으로 데이터를 가져온다.


이슈 2


위의 코드에서는 비동기로 동작하게 해놨는데, 만약 동기로 데이터를 받도록 코드를 짤 경우 아래와 같은 에러 메시지가 발생한다.


동기로 호출 시, 작업 코드


try{

    repos.execute();

}catch(IOException e){

    e.printStackTrace();

}


android.os.NetworkOnMainThreadException


에러 메시지의 내용인 즉, 메인 쓰레드에서 네트워크 작업을 하지 말라는 소리다. 결과적으로 execute를 실행하고 싶다면 또 다른 쓰레드를 생성하거나 비동기로 호출해서 사용해야 한다. 아마 이런 규약은 android 6부터 적용되는 듯 하며 이전 버전의 코드를 사용할 경우에는 아무 문제 없을 것으로 생각된다.