Retrofit

강현성·2022년 9월 2일
0

android

목록 보기
6/18
post-thumbnail

1. Retrofit 이란?

Retrofit은 Square에서 만든 서버와 클라이언트간 http 통신을 간편하게 해주는 라이브러리로 OkHttp 라이브러리를 기반으로 만들어졌다. (Retrofit은 OkHttp를 네트워크 계층으로 활용하고 그 위에 구축된다.)
Retrofit 공식사이트

2. Retrofit 사용이유

초기 안드로이드 통신은 HttpClient를 사용했다. 그러다 안드로이드 5.1에서 HttpClient가 Deprecated 된 후, HttpClient에 의존하던 Volley도 Deprecated되면서 Square에서 만든 라이브러리인 OkHttp와 그 기반으로 만들어진 Retrofit이 사용되고 있다.

2-1. OkHttp & Retrofit 차이점

Retrofit

  1. JSON 데이터 모델 객체 변환
    OkHttp, Retrofit으로 JSON 데이터를 받아 온다면 JSON 데이터를 사용할 모델(VO 클래스) 객체로 변환해줘야 하고 이때 GsonConverterFactory를 사용한다면 Retrofit은 통신 성공 시 응답 객체의 body를 별도의 변환 과정 없이 모델(Vo 클래스) 객체로 바로 사용할 수 있지만 OkHttp에서는 반환받은 JSON 데이터를 모델 객체로 바로 변환해 주지 못하기 때문에, 별도로 변환해야 한다.

  2. 결과값 메인 스레드에서 바로 사용
    OkHttp, Retrofit 모두 enqueue를 사용해 네트워크 통신을 백그라운드에서 수행할 수 있다.
    하지만 Retrofit은 결과가 메인스레드에 전달되기 때문에 UI 관련 메서드에 결괏값을 사용할 수 있다. 반면에 OkHttp는 결과가 반환되어도 여전히 백그라운드에 남아있기 때문에 결괏값을 메인스레드에서 상용하기 위해서는 runOnUiThread를 사용해야 한다.

  3. Annotation 사용으로 코드의 가독성이 뛰어나 직관적인 설계 가능

OkHttp

  1. OkHttp Client에 네트워크 intercepter를 통해 API가 통신되는 모든 활동을 모니터링 가능

  2. 서버 통신 시간 조절 가능

3. Retrofit 사용법

Retrofit은 네트워크 통신 정보만 주면 정보를 토대로 그대로 프로그래밍을 대신 구현해준다. 다시 말해 통신하기 위한 interface를 작성한 뒤 retrofit에게 전달해주면 retrofit은 interface의 정보를 보고 실제 구현하여 서비스 객체를 돌려준다.(여기서 서비스는 안드로이드 컴포넌트 아님) 이 서비스 객체의 함수를 호출하면 Call 객체를 반환 하는데, 이 Call 객체의 enqueue() 메서드를 호출하는 순간 통신을 수행한다. (enqueue -> 백그라운드)

retrofit 동작 방식 정리

  1. 통신용 메서드 선언한 interface 작성
  2. retrofit에게 interface 전달
  3. retrofit이 통신용 서비스 객체 반환
  4. 서비스의 통신용 메서드를 호출한 후 Call 객체 반환
  5. Call 객체의 enqueue() 메서드를 호출하여 네트워크 통신 수행

실습

3-1. Manifest.xml 권한 추가

<application
....
android:usesCleartextTraffic="true">
....
<uses-permission android:name="android.permission.INTERNET" />

3-2. 라이브러 추가

	//retrofit
    def retrofit_version = "2.9.0"
    implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
    
    //Gson -> json 변환기
    implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"

3-3. DTO Model 생성

public class SchoolResponse {

    @SerializedName("data")
    private List<School> schools;

    public List<School> getSchools() {
        return schools;
    }
}
public class School {

    @SerializedName("name")
    private String name;
    
    @SerializedName("location")
    private String location;
    
    @SerializedName("server")
    private String server;

    public String getName() {
        return name;
    }

    public String getLocation() {
        return location;
    }

    public String getServer() {
        return server;
    }

}

@SerializedName("key name")
위 어노테이션의 역할은 Gson이 JSON 객체의 키들을 Java 클래스 필드(변수)에 매핑하는 것을 도와준다.(클래스 필드와 JSON 객체의 키들의 이름이 동일하면 동작하는 데는 문제 없음 but 권장)
@SerializedName("key name")을 사용하면 JSON 객체의 키와 Java 클래스 필드 변수명이 달라도 매핑 된다. 예를 들어
@SerializedName("server")
String server;
해당 코드는 JSON 객체의 server key를 자바 클래스 필드 server에 매핑시켜 사용할 수 있다.

3-4. interface 정의

public interface ICheckooService {

    @Headers("x-app-token: tokenValue")
    @GET("api/schools")
    Call<SchoolResponse> getSchoolList();

    @Headers("x-app-token: tokenValue")
    @POST("{server}/api/pc/auth/signin")
    Call<LoginResponse> login(
            @Body Login password,
            @Path("server") String server);
}

retrofit을 사용할 때 가장 중요한 부분이 서비스 interface를 작성하는 것이다.
interface를 작성해 retrofit에게 넘기면 retrofit은 interface의 annotation을 보고 interface를 구현해준다. 즉 메서드에 선언한 annotation을 보고 그 정보대로 네트워크 통신을 할 수 있는 코드를 자동으로 만들어 준다.

public class Login {
    @SerializedName("password")
    private String password;

    public Login(String password) { this.password = password; }

    public String getPassword() {
        return password;
    }
}
    @Headers("x-app-token: tokenValue") // 헤더값을 설정하는 annotation
    @POST("{server}/api/pc/auth/signin") // post 방식으로 서버 연동
    Call<LoginResponse> login(
            @Body Login password, 
            @Path("server") String server);
            
    /*
    @Body Login password 객체의 프로퍼티명을 키로, 
    프로퍼티의 데이터를 값으로 해서 JSON 문자열을 만들어 서버에 전송, 
    이때 JSON 문자열은 데이터 스트림으로 전송하므로 
    @Body는 @GET에서 사용할 수 없고 @POST에서 사용해야 한다.

    input
    BASEURL = "www.retrofitexam.com/"
    login(new Login("1234"), "serverURL");

    output
    최종 서버 요청 URL: www.retrofitexam.com/serverURL

    서버에 스트림으로 전송되는 데이터: {"password":"1234"}

    */

3-5. retrofit 객체 생성

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://checkoo.co.kr")
                .addConverterFactory(GsonConverterFactory.create())
                .build();

3-6. interface 서비스 객체 얻기

		RetrofitAPI retrofitAPI = retrofit.create(RetrofitAPI.class);

3-7. 통신 수행

        retrofitAPI.getSchoolList().enqueue(new Callback<Data>() {
            @Override
            public void onResponse(Call<Data> call, Response<Data> response) {
                if (response.isSuccessful()) {
                    Log.d(TAG, "Success");
                    Data data = response.body();
                    for (School school: data.getData()) {
                        System.out.println(school.getName());
                        System.out.println(school.getLocation());
                        System.out.println(school.getServer());
                    }
                }
            }

            @Override
            public void onFailure(Call<Data> call, Throwable t) {
                Log.d(TAG, "Failure");
                t.printStackTrace();
            }
        });

4. Retrofit 사용 예제(동행로또 데이터 받아오기)

4-1. libaray 추가

build.grade(Moudle: AppName.app) -> dependence ->

	//retrofit
    def retrofit_version = "2.9.0"
    implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
    implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"

4-2. Manifest.xml 권한추가

<uses-permission android:name="android.permission.INTERNET" />

4-3. DTO Model 생성

import com.google.gson.annotations.SerializedName;

public class LottoDataResponse {
    @SerializedName("totSellamnt")
    String totSellamnt;

    @SerializedName("returnValue")
    String returnValue;

    @SerializedName("drwNoDate")
    String drwNoDate;

    @SerializedName("firstWinamnt")
    String firstWinamnt;

    @SerializedName("drwtNo6")
    String drwtNo6;

    @SerializedName("drwtNo4")
    String drwtNo4;

    @SerializedName("firstPrzwnerCo")
    String firstPrzwnerCo;

    @SerializedName("drwtNo5")
    String drwtNo5;

    @SerializedName("bnusNo")
    String bnusNo;

    @SerializedName("firstAccumamnt")
    String firstAccumamnt;

    @SerializedName("drwNo")
    String drwNo;

    @SerializedName("drwtNo2")
    String drwtNo2;

    @SerializedName("drwtNo3")
    String drwtNo3;

    @SerializedName("drwtNo1")
    String drwtNo1;

    public String getTotSellamnt() {
        return totSellamnt;
    }

    public String getReturnValue() {
        return returnValue;
    }

    public String getDrwNoDate() {
        return drwNoDate;
    }

    public String getFirstWinamnt() {
        return firstWinamnt;
    }

    public String getDrwtNo6() {
        return drwtNo6;
    }

    public String getDrwtNo4() {
        return drwtNo4;
    }

    public String getFirstPrzwnerCo() {
        return firstPrzwnerCo;
    }

    public String getDrwtNo5() {
        return drwtNo5;
    }

    public String getBnusNo() {
        return bnusNo;
    }

    public String getFirstAccumamnt() {
        return firstAccumamnt;
    }

    public String getDrwNo() {
        return drwNo;
    }

    public String getDrwtNo2() {
        return drwtNo2;
    }

    public String getDrwtNo3() {
        return drwtNo3;
    }

    public String getDrwtNo1() {
        return drwtNo1;
    }
}

4-4. Interface 정의

import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;

public interface ILottoServiceAPI {
    @GET("common.do?method=getLottoNumber")
    Call<LottoDataResponse> getLotto(@Query("drwNo") String drwNo);
}

4-5. Retrofit Instance 생성

import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

// singleton pattern
public class LottoClient {
    private static final String BASE_URL =  "https://www.dhlottery.co.kr/";
    private static Retrofit retrofitClient;

    public static ILottoServiceAPI getAPIService() { return getRetrofit().create(ILottoServiceAPI.class);}

    public static Retrofit getRetrofit() {
        if (retrofitClient == null) {
            retrofitClient = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }

        return retrofitClient;
    }
}

4-5. callback 메서드 작성(model.class)

    public void requestLottoData(int num) {
        Call<LottoDataResponse> retrofitCall = LottoClient.getAPIService().getLotto(String.valueOf(num));
        retrofitCall.enqueue(new Callback<LottoDataResponse>() {
            @Override
            public void onResponse(Call<LottoDataResponse> call, retrofit2.Response<LottoDataResponse> response) {
                if (!response.isSuccessful()) {
                    Log.d(TAG, String.valueOf(response.code()));
                    return;
                }

                LottoDataResponse lottoDataResponse = response.body();

                String temp_totSellamnt = lottoDataResponse.getTotSellamnt();
                String returnValue = lottoDataResponse.getReturnValue();
                String drwNoDate = lottoDataResponse.getDrwNoDate();
                String temp_firstWinamnt =lottoDataResponse.getFirstWinamnt();
                String drwtNo6 = lottoDataResponse.getDrwtNo6();
                String drwtNo4 = lottoDataResponse.getDrwtNo4();
                String firstPrzwnerCo= lottoDataResponse.getFirstPrzwnerCo();
                String drwtNo5 = lottoDataResponse.getDrwtNo5();
                String bnusNo = lottoDataResponse.getBnusNo();
                String temp_firstAccumamnt = lottoDataResponse.getFirstAccumamnt();
                String drwNo = lottoDataResponse.getDrwNo();
                String drwtNo2 = lottoDataResponse.getDrwtNo2();
                String drwtNo3 = lottoDataResponse.getDrwtNo3();
                String drwtNo1 = lottoDataResponse.getDrwtNo1();

                if (returnValue.equals("success")) {
                    long b = Long.parseLong(temp_firstAccumamnt);
                    long d = Long.parseLong(temp_firstWinamnt);
                    long e = Long.parseLong(temp_totSellamnt);
                    DecimalFormat dc = new DecimalFormat("###,###");
                    String firstAccumamnt = dc.format(b)+"원";
                    String firstWinamnt = dc.format(d)+"원";
                    String totSellamnt = dc.format(e)+"원";

                    int drwtNo1_background = ballBackground(Integer.parseInt(drwtNo1));
                    int drwtNo2_background = ballBackground(Integer.parseInt(drwtNo2));
                    int drwtNo3_background = ballBackground(Integer.parseInt(drwtNo3));
                    int drwtNo4_background = ballBackground(Integer.parseInt(drwtNo4));
                    int drwtNo5_background = ballBackground(Integer.parseInt(drwtNo5));
                    int drwtNo6_background = ballBackground(Integer.parseInt(drwtNo6));
                    int bnusNo_background = ballBackground(Integer.parseInt(bnusNo));

                    Lotto lotto = new Lotto(drwNo, totSellamnt, drwNoDate, firstWinamnt, firstPrzwnerCo, firstAccumamnt,
                            drwtNo1, drwtNo2, drwtNo3, drwtNo4, drwtNo5, drwtNo6, bnusNo,
                            drwtNo1_background, drwtNo2_background, drwtNo3_background, drwtNo4_background, drwtNo5_background, drwtNo6_background, bnusNo_background);

                    lottoData.setValue(lotto);
                } else {
                    weeklyTurn -= 1;
                    requestLottoData(weeklyTurn);
                }
            }

            @Override
            public void onFailure(Call<LottoDataResponse> call, Throwable t) {
                    Log.d(TAG, t.getMessage());
            }
        });

    }
profile
Hello!

0개의 댓글