1.Retrofit 2 편 : 코드리뷰

김영우 (AvocadoSmasher)·2022년 7월 29일
0

Capstone 2

목록 보기
2/2

1-1.Retrofit 2의 사용 이유.

Retrofit 2는 REST API 통신을 위한 통신 라이브러리로 Android Studio에서 사용이 가능하다. 본 프로젝트를 진행하면서 Flask를 사용하여 REST API로 동작하는 서버를 구축하였었다. 그렇기에 프론트엔드에서도 해당 서버와 통신하기 위해 REST API 통신 라이브러리 중 가장 많이 사용되는 Retrofit 2를 사용하였다.

1-2. Retrfrofit2 의 준비.

Retrofit 2의 사용을 위해서는 사용 전 다음과 같은 준비가 필요하다.

  • manifests에 permission 추가.
  • depedency 추가.

그 이후에 일반적으로 사용하는 방법은 다음과 같다.

  • HTTP API를 대체할 interface를 생성한다.
  • RetrfrofitClient class를 작성한다.( 재사용성을 위해 )
  • Request와 Response를 작성한다.

가장 보편적인 사용방법들은 이런 순서로 진행되고 해당 프로젝트에서도 이렇게 사용했었다.

이후의 프로젝트에서는 음악 파일을 주고 받기 위해서 Multipart를 사용했었는데 본 프로젝트에서는 그런 유형의 정보는 다루지 않았다.

1-2. Retrfrofit 사용 예시.

해당 프로젝트의 사용을 기준으로 설명하겠다.

  1. dependency
  2. manifest
  3. initMyApi
  4. RetrofitClient
  5. Request and Response
  6. 진짜 사용

dependency

우선 dependency는 다음과 같다.

implementation 'com.squareup.retrofit2:retrofit:2.6.2'
implementation 'com.squareup.retrofit2:converter-gson:2.6.2'
implementation 'com.google.code.gson:gson:2.8.5'

통신을 할때 JSON 형태의 파일을 주고 받을텐데 이때 JSON 타입으로 넘어온 정보를 객체나 다른 타입으로 파싱할 수 있도록 Converter를 사용한다.

manifest

이후에 manifest도 internet 사용을 위한 permission을 추가한다. usesClearTextTraffic 플레그 또한 true로 변경해 준다.

<manifest>
    <uses-permission android:name='android.permission.INTERNET'/>
    <application
                 ...
                 android:usesClearTextTraffic="true">
    </application>
</manifest>

uses-permission의 internet 부분은 인터넷 사용을 하기위한 퍼미션을 얻는다고 가시적으로 드러나지만 usesClearTextTraffic은 또 무엇인가? 이는 Android API 28 이후부터 https가 아닌 http의 접근을 default로 허용하지 않는다고 한다. 이를 사용을 위해서는 사용자가 선택적으로 허용을 할 수 있다. 문서를 보면 다음과 같이 서술한다.

보안 연결만 사용하여 대상에 연결하는 애플리케이션은 해당 대상에 대해 일반 텍스트를 지원하는 기능(HTTPS 대신 암호화되지 않은 HTTP 프로토콜 사용)을 선택 해제할 수 있습니다.

본 프로젝트에서도 https가 아닌 http 서버를 사용하기에 usesClearTextTraffic값을 true로 변경해 주었다.

initMyApi

이제 http api를 대신할 interface를 설정하자.

public interface initMyApi {
    @POST("/user/register")
    Call<RegisterResponse> getRegisterResponse(@Body Register registerRequest);
}

해당 코드는 본 프로젝트에서 사용했던 부분의 일부로 회원가입 요청과 응답을 위한 부분이다.

간단한 설명은 다음과 같다.

  1. 우선 @POST처럼 Annotation을 통해서 해당 함수가 어떤 메소드(POST, GET, UPDATE, PUT 등) 를 사용하는지 설정한다. 그 후 ()안에 어떤 라우터를 사용하는지 경로를 설정해준다.
  2. 반환형으로 Call 부분은 JSON 형태로 서버로부터 전달받은 정보를 직접 작성한 Reponse 객체를 참조하여 파싱해 사용할 수 있도록 한다.
  3. 함수의 매개변수 부분에 @Body는 request body에 해당 인자로 받은 객체를 참조하여 넣어주겠다는 것이다.

해당 request와 response 객체 내부는 이후에 소개하겠다.

추가적으로 본 프로젝트에서 사용자의 인증을 위해 JWT 토큰을 발급하고 사용하는 방식을 채택하였었고 이를 Header에 실어서 보내는 식으로 사용했었는데 다음과 같은 식으로 사용한다.

@GET("/service/profile")
Call<ProfileResponse> getProfileResponse(@Header("Authorization") String header);

P.S @POST("경로")경로부분에 { 경로이름 }를 사용하고 밑의 함수의 매개변수로 @Path("[경로이름]") 변수명 : 타입 과 같이 사용하여 동적으로 경로를 지정해 줄 수 도 있다. 이외에도 @Query, @Header등등 다양한 Annotation과 사용방법이 있으나 모두 기억하진 않고 기억할 필요도 없다고 생각한다. 검색을 통해서 다른 사용법들도 찾아 사용해 보는걸 권장한다.

RetrofitClient

우선 코드는 다음과 같다.

public class RetrofitClient {
    final private static String baseUrl = "http://주소";
    private static initMyApi initMyApi;
    private static RetrofitClient instance = null;
    
    public static initMyApi getRetrofitInterface(){ return initMyApi }
    public static RetrofitClient getInstance(){
        if(instance == null){
            instance = new RetrofitClient();
        }
        return instance;
    }
    
    private RetrofitClient(){
        /**
        OkHttpClient client = new OkHttpClient.Builder()
                .addInterceptor(interceptor)
                .connectTimeout(1, TimeUnit.MINUTES)
                .readTimeout(30,TimeUnit.SECONDS)
                .writeTimeout(15,TimeUnit.SECONDS)
                .build(); **/
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .build();//.client(client)을 build()전에 추가하여 로그 기능을 사용할 수 있다.

        initMyApi = retrofit.create(initMyApi.class);
    }
}

본 프로젝트의 코드에서 일부를 가져와 다시 작성했는데 이런 느낌이었다. 간단한 설명은 다음과 같다.

  • 우선 하나의 서버를 사용하였기에 주소가 변경될 일이 없어 final 선언으로 baseUrl을 설정했었다.
  • 당시에 메모리의 절약을 위해 싱글톤 패턴을 적용하려고 했었다. 그래서 getInstance()함수를 사용하여 객체가 존재하면 재사용 해주는 방식을 채택했었다.
  • 주석 처리된 부분은 http 통신의 로그를 보기 위해서 추가했던 부분인데 해당 코드를 사용하기 위해서는 다른 dependency를 추가해줘야한다. 서버랑 통신 테스트를 할때 사용했던 코드인데 이를 통해 어느쪽에 문제가 있는지 어떤 문제가 있는지 확인할때 유용하게 사용했었기에 주석으로 추가해줬다.
  • Retrofit2가 Builder 패턴을 사용하여 위와 같이 사용했는데 baseUrl과 JSON의 객체로의 변환을 위해 Converter를 추가해준다.
  • 그 후 해당 retrofit을 생성하여 initMyApi에 넣어주면 이제 본 코드에서 사용 가능하다.

Request and Response

Request와 Response 코드는 다음과 같다.

public class RegisterRequest {

    @SerializedName("email")
    public String inputEmail;

    @SerializedName("password")
    public String inputPw;

    @SerializedName("name")
    public String inputName;

    public String getInputEmail() { return inputEmail; }
    public String getInputPw() { return inputPw; }
    public String getInputName(){ return inputName; }
    public void setInputEmail(String inputEmail) { this.inputEmail = inputEmail; }
    public void setInputPw(String inputPw) { this.inputPw = inputPw; }
    public void setInputName(String inputName){ this.inputName = inputName; }

    public RegisterRequest(String inputEmail, String inputPw, String inputName) {
        this.inputEmail = inputEmail;
        this.inputPw = inputPw;
        this.inputName = inputName;
    }
}
public class RegisterResponse {
    @SerializedName("message")
    public String message;

    public String getMessage() { return message; }
    public void setMessage(String message) { this.message = message; }
}

잘 보면 둘다 형태가 거의 유사하다. 우선 객체에 담기는 변수를 @SerializedName()으로 되어있는 것을 확인할 수 있는데 이는 서버와 프론트에서 같이 맞춰놓은 이름이고 그 밑에는 Json에서 해당 데이터를 실제로 객체내의 어떤 변수랑 매칭할 것인가 이다.

getter와 setter 를 설정해 놓으면 JSON의 데이터를 객체에 넣고 빼고 알아서 해준다.(편리한 Retrofit)

진짜 사용

실제로 코드내에서 사용했을때는 다음과 같았다.

RegisterRequest registerRequest = new RegisterRequest(userEmail, userPwd, userName);

//retrofit 생성
RetrofitClient retrofitClient = RetrofitClient.getInstance();
initMyApi = RetrofitClient.getRetrofitInterface();

initMyApi.getRegisterResponse(registerRequest).enqueue(new Callback<RegisterResponse>() {
    // 성공시.
    @Override
    public void onResponse(Call<RegisterResponse> call, Response<RegisterResponse> response) {
        //통신이 성공함.
        if (userPwd.equals(PassCK) && validate==true && return_name == true && return_pw_check == true) {
            if (response.isSuccessful() && response.body() != null) {
                RegisterResponse result = response.body();
                String message = result.getMessage();
                ...
                Toast.makeText(getApplicationContext(),"회원 등록에 성공하였습니다.",Toast.LENGTH_SHORT).show();}
        } else {
            Toast.makeText(getApplicationContext(),"다시 입력해주세요.",Toast.LENGTH_SHORT).show();
        }
    }
    //실패시.
    @Override
    public void onFailure(Call<RegisterResponse> call, Throwable t) {
        Toast.makeText(getApplicationContext(),"회원 등록에 실패하였습니다.",Toast.LENGTH_SHORT).show();
    }
});

Request 객체를 생성하고 retrofit instance를 받은 후 initMyApi의 해당 요청을 해 주고 (initMyApi 해당 통신 함수명을 왜 Response로 통일 했었을까 싶다... 무튼 그땐 그렇게 했었다.) enqueue해주면 이제 통신이 비동기적으로 수행된다. 그 후 Callback으로 넘어오는 응답을 받아 성공시에는 onResponse를 실패시에는 onFailure를 호출하게 된다.

  • 성공 부분을 보면 response.body()를 통해서 응답을 확인할 수 있고 성공 처리(Toast 메시지로 성공 출력)를 해주었다..
  • 실패 부분에서는 실패 처리를 해주었다.

1-3. 후기

Retrofit 2 사용부분은 이렇게 마친다. 처음으로 사용해본 통신 라이브러리가 Retrofit이라서 처음에는 많이 삐걱거렸지만 사용하다보니 좀 익숙해져서 싱글톤 패턴도 적용하고 하는 등의 처리를 하였었다. 또한 다른 프로젝트에서도 REST API 서버와 통신할 일이 많을것이라 생각하여 Retrofit2를 사용했었던 경험은 좋았다고 생각한다.

사실 본 글에 작성했던 내용들 말고 다른 Retrofit 관련 소스 부분을 보면 코드가 상당히 난잡하게 적혀있다. 또한 통일화 하지 않은 부분들도 부분부분 있는데 이는 프론트 엔드를 필자외 1명이 담당하였기 때문인데 의견이 갈리는 부분도 있었고 다른 담당자가 필자가 느끼기에 좀 열심히 하지 않는다? 라고 느껴 필자가 맞춰주려고 코드를 이것저것 수정하다 보니 기존의 legacy(레거시라 하기에도 민망한 작은 프로젝트지만)의 코드를 고치려면 해당 부분들을 모두 고쳐야 했고, 시간적 여유가 없던 당시에 이를 보완하고자 하다보니 그래되었다... 또한 그 팀원이 수정할때 나의 의견을 물어보지 않고 마음대로 수정하거나 하여 필자가 진짜진짜 Retrofit 부분에서만 해도 많은 고생을 하였었다.

해당 부분들의 코드는 여기서 확인 가능하다.

profile
Android Studio 공부 중

0개의 댓글