KUSITMS/학술제

[안드로이드 스튜디오] Retrofit2(레트로핏) 정리

gom1n 2021. 11. 30. 11:47

보통 레트로핏을 구글링해보면 다음과 같은 형식의 코드가 나온다.

RetrofitClient retrofitClient = new RetrofitClient();
Call<JsonArray> call = retrofitClient.apiService.getretrofitquery(string 변수);
call.enqueue(new Callback<JsonArray>() {
        @Override
        public void onResponse(Call<JsonArray> call, Response<JsonArray> response) {
            if (response.isSuccessful()) {
            //통신이 성공했을 때
            } else {
            //통신이 실패했을 때
            }
        }
 
        @Override
        public void onFailure(Call<JsonArray> call, Throwable t) {
        //연결이 실패했을 때
        }
    });

하지만 안드로이드 프로젝트를 하며 위의 레트로핏  통신함수를 호출할 일이 많을 땐?

추상화하여 코드를 최대한 간결히 줄이는 것이 좋은 개발이라고 할 수 있다.

 

추상화한 코드들을 활용해 레트로핏 통신을 하는 방법을 정리해보고자 한다. 

이번 플젝을 하며 나도 배운 점이 많았기에... 까먹지 않으려고 쓴다.

 

INDEX

1. MainNullOnEmptyConverterFactory.java

2. MainRetrofitCallback.java

3. MainRetrofitTool.java

4. RestAPI.java

5. RetrofitTool.java

6. 액티비티 내에서 Callback클래스 생성 후 호출


1. MainNullOnEmptyConverterFactory.java

public class MainNullOnEmptyConverterFactory extends Converter.Factory {
    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
        final Converter<ResponseBody, ?> delegate = retrofit.nextResponseBodyConverter(this, type, annotations);
        return (Converter<ResponseBody, Object>) body -> {
            if (body.contentLength() == 0) {
                return null;
            }
            return delegate.convert(body);
        };
    }
}

2. MainRetrofitCallback.java

public interface MainRetrofitCallback<T> {
        void onSuccessResponse(Response<T> response) throws IOException;
        void onFailResponse(Response<T> response) throws IOException, JSONException;
        void onConnectionFail(Throwable t);
    }

3. MainRetrofitTool.java

public class MainRetrofitTool {
    public static <T>Callback<T> getCallback(MainRetrofitCallback<T> callback){
        return new Callback<T>() {
            @SneakyThrows
            @Override
            public void onResponse(Call<T> call, Response<T> response) {
                if(response.isSuccessful()){ callback.onSuccessResponse(response); }
                else{ callback.onFailResponse(response); }
            }
            @Override
            public void onFailure(Call<T> call, Throwable t) { callback.onConnectionFail(t); }
        };
    }
}

어디서 많이 본 형태쥬? 앞으로는 이것을 활용할 것...


4. RestAPI (예시)

public interface RestAPI {

    @POST("signup")
    Call<SignUpResponse> signup(@Body SignUpRequest signUpRequest);
    @POST("login")
    Call<LoginResponse> login(@Body LoginRequest loginRequest);
    @POST("api/v1/user/details")
    Call<UserDetailsInfoResponse> userDetails(@Body UserDetailsInfoRequest userDetailsInfoRequest);
    @POST("api/v1/scrap")
    Call<ScrapRegisterResponse> createScrap(@Body ScrapRegisterRequest scrapRegisterRequest);
    @GET("api/v1/items/{id}")
    Call<ItemDetailsResponse> getItem(@Path("id") Long id);
    @DELETE("/api/v1/items/{id}")
    Call<DefaultResponse> deleteItem(@Path("id") Long id);
    
    // 등등등...본인이 필요한 GET, POST, DELETE 등 선언해주면 됨.
}

@POST : 데이터를 넣어줄 때

@GET : 저장된 데이터를 가져올 때

@DELETE : 저장된 데이터의 id로 데이터를 삭제할 때

@POST, @GET, @DELETE 뒤의 괄호()안에는 서버에서 명시된 주소의 뒷부분(?)을 쓴다. 

앞주소(BASE_URL)은 RetrofitTool에 선언되어있다.

Call<[response클래스]> [함수] (@객체) 의 형태이다.

위에서 데이터마다 id값이 달라질 때에, 주소에다가는 {id}로 표현해놓고 @Path뒤에 넣어줄 id값을 선언해준다.


5. RetrofitTool.java

public class RetrofitTool {
//    private static final String BASE_URL = "http://192.168.1.2:8080/"; // 내부IP(안드로이드휴대폰사용, cmd -> ipconfig IPv4주소)
    private static final String BASE_URL = "http://10.0.2.2:8080/"; // 안드로이드에뮬레이터IP

    public static RestAPI getAPI(){
        return new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create(new GsonBuilder().create()))
                .build()
                .create(RestAPI.class);
    }
    public static RestAPI getAPIWithNullConverter(){
        return new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(new MainNullOnEmptyConverterFactory())
                .addConverterFactory(GsonConverterFactory.create(new GsonBuilder().create()))
                .build()
                .create(RestAPI.class);
    }
    public static RestAPI getAPIWithAuthorizationToken(String token){
        Interceptor interceptor = chain -> {
            Request newRequest  = chain.request().newBuilder()
                    .addHeader("Authorization", "Bearer " + token)
                    .build();
            return chain.proceed(newRequest);
        };
        OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();
        return new Retrofit.Builder()
                .client(client)
                .baseUrl(BASE_URL)
                .addConverterFactory(gsonConverterFactory())
                .build()
                .create(RestAPI.class);
    }


    private static GsonConverterFactory gsonConverterFactory() {
        Gson gson = new GsonBuilder()
                .registerTypeAdapter(LocalDateTime.class, new JsonDeserializer<LocalDateTime>() {
                    @Override
                    public LocalDateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
                        return LocalDateTime.parse(json.getAsString(), DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"));
                    }
                })
                .registerTypeAdapter(LocalDate.class, new JsonDeserializer<LocalDate>() {
                    @Override
                    public LocalDate deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
                        return LocalDate.parse(json.getAsString(), DateTimeFormatter.ofPattern("yyyy-MM-dd"));
                    }
                })
                .registerTypeAdapter(LocalTime.class, new JsonDeserializer<LocalTime>() {
                    @Override
                    public LocalTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
                        return LocalTime.parse(json.getAsString(), DateTimeFormatter.ofPattern("HH:mm:ss"));
                    }
                })
                .setLenient()
                .create();

        return GsonConverterFactory.create(gson);
    }

}

레트로핏에서 사용될 기본 주소(BASE_URL)을 명시해준다.

레트로핏 객체를 생성하는데, 아래와 같이 세가지 방법으로 짜준다.

getAPI() : 일반적으로 쓰이는 통신함수

getAPIWithNullConverter() : Token이 필요하지 않을 때에 쓰이는 통신함수

getAPIWithAuthorizationToken(String token) : 사용자의 Token이 필요할 때에 쓰는 함수 (보안을 위해서?... 잘 모르겠다.)


6. 액티비티 내에서 Callback클래스 생성 후 호출

이제 액티비티에서 상품의 내용을 가져오고자 한다. (예시)

상품의 내용을 가져오고싶을 땐,

1. 앞서 RestAPI에서 @GET방식으로 함수가 선언되어있어야 하고, 

2. ItemDetailsResponse 클래스가 짜여져있어야 한다.

더보기

RestAPI

@GET("api/v1/items/{id}")
Call<ItemDetailsResponse> getItem(@Path("id") Long id);

ItemDetailsResponse (DTO)

*Lombok 플러그인에서 제공하는 @Getter 를 활용해 쉽게 선언할 수 있다.

@Getter
@Builder
@AllArgsConstructor
public class ItemDetailsResponse {

    private Long id;
    private Long userId;
    private String itemName;
    private ItemConstants.EItemCategory category;
    private int initPrice;
    private int soldPrice;
    private LocalDateTime buyDate;
    private int itemStatePoint;
    private String description;
    private ItemConstants.EItemSoldStatus soldStatus;
    private List<String> fileNames;
    private LocalDateTime auctionClosingDate;

}

6-1) 클래스 뒷부분에 Callback 클래스를 짜준다.

public class getItemDetailsCallback implements MainRetrofitCallback<ItemDetailsResponse> {
        @Override
        public void onSuccessResponse(Response<ItemDetailsResponse> response) {
            //통신이 성공했을 때
            //TODO: retrofit2 methods
            Log.d(TAG, "retrofit success, idToken: " + response.body().toString());
        }
        @Override
        public void onFailResponse(Response<ItemDetailsResponse> response) throws IOException, JSONException {
            //통신이 실패했을 때
            System.out.println("errorBody"+response.errorBody().string());
            Log.d(TAG, "onFailResponse");
        }
        @Override
        public void onConnectionFail(Throwable t) {
            Log.e("연결실패", t.getMessage());
        }
    }

 

6-2) 호출한다.

RetrofitTool.getAPIWithAuthorizationToken(Constants.token).getItem(itemId)
               .enqueue(MainRetrofitTool.getCallback(new getItemDetailsCallback()));

// Constatns.token은 사용자의 토큰

// itemId는 상세정보를 불러올 아이템의 ID값

위와같이 아주 간결한 코드로 호출이 가능하다. 와우


느낀점)

내가 써놓고도 이걸 보는 사람들이 이해할 수 있나싶다.

이번 플젝을 하며 많은 걸 배웠다. 특히 레트로핏 부분이 너무나 유용했다. 그러나 힘들었다.