Android(Java) - Retrofit - 사용법, API서버와 통신하기

반응형

 

Retrofit

  • 안드로이드에서 서버와 클라이언트간 http 통신을 위한 라이브러리
  • Okhttp 라이브러리를 더 편히 사용하고자 만들어진 라이브러리
    • 파이썬의 리스트를 편히 사용하고자 넘파이, 판다스가 나온 것과 비슷한 맥락

모듈 추가

  • build.gradle (Module)
dependencies {
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation("com.squareup.okhttp3:logging-interceptor:4.9.0")
}

서버와의 통신을 위한 인터넷 허용 권한 설정

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

Retrofit 사용을 위한 기본 설정

이건 이해하는 것보다는 복사해서 쓰는 것이 편하다.

주의해야 할 부분은 Config.BASE_URL 부분만 자신의 API URL로 변경하면 된다.

보통은 보안상의 이유로(키 값, 시크릿 키 값 노출) 다른 클래스에 저장하여 비공개로 두는 경우가 많다.

API 서버를 설정하는 단계라고 보면 된다.

public class NetworkClient {

    public static Retrofit retrofit;

    public static Retrofit getRetrofitClient(Context context) {
        if(retrofit == null) {
            // TODO : 데이터 통신의 로그를 Logcat에서 확인할 수 있다.
            HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
            loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);

            OkHttpClient httpClient = new OkHttpClient.Builder().connectTimeout(1, TimeUnit.MINUTES)
                    .readTimeout(1, TimeUnit.MINUTES)
                    .writeTimeout(1, TimeUnit.MINUTES)
                    .addInterceptor(loggingInterceptor)
                    .build();

            retrofit = new Retrofit.Builder().baseUrl(Config.BASE_URL)
                    .client(httpClient)
                    .addConverterFactory(GsonConverterFactory.create()) // 데이터를 주고 받을 때 모델에 만든 클래스로 사용
                    .build();
        }
        return retrofit;
    }
}

API 요청 방법

서버와 통신을 하려면 서버에서 요구하는 파라미터와 키 값을 맞춰주어야 한다.

아래의 예시는 내가 만든 테스트용 SNS 서버, GET 요청으로 돌아오는 키 값을 확인해보자.

  • result_list
    • id, imageUrl, userId, content, createAt, updatedAt, likes

 

예시에서 보여진 API서버에서는 GET요청을 정상적으로 응답받으려면,

인증키(Authorization)와 파라미터로 페이지의 번호를 필요로 한다.

이 두 개의 조건이 충족되어야만 GET요청에 대한 응답을 받을 수 있다.

이는 서버마다 다르며 개발시 유의해야 한다.


Retrofit을 이용한 API 요청

(예시로 보여준 API서버를 기준으로 작성하였습니다.)

 

API 요청 명령어

@메소드("경로")

Call<클래스> 사용 할 메소드명 (파라미터)

  • 메소드 : GET / POST / PUT / DELETE
  • 경로 : API 서버 URL의 하위 경로, 예시에서는 /posting
  • Call<클래스> : API 요청 응답에 성공 할 경우, 전달 받을 데이터를 저장 할 클래스
  • 사용 할 메소드명 : API 요청시 사용 할 메소드의 이름 정의
  • 파라미터 : API 서버에서 요구하는 파라미터 정의, 예시에서는 Header(Authorization), Query(page)
    • 반드시 응답받은 키 값으로 기입하여야 한다. "Authrozation", "page" 이런 식.

POSTMAN 프로그램을 통한 API 서버와의 GET 통신

GET

  • 파라미터에 쿼리(@Query)를 사용, GET/DELETE는 쿼리스트링 사용
    • 하위 경로 뒤에 '?키=밸류' 로 구성, 예시) /posting?page=1
  • 보통은 게시판의 목록을 보여주는 기능으로 사용
  • 예시 API서버에서의(내 포스팅 보기 기능) GET 메소드 필수 파라미터
    • Header : 인증키(Authorization)
    • Query : 페이지 번호
    // 포스팅 리스트 호출 API
    // GET은 Query String 사용
    @GET("/posting")
    Call<SnsListRes> getPostingList(@Header("Authorization") String token, @Query("page") int page);

POSTMAN 프로그램을 통한 API 서버와의 POST 통신

POST

  • 파라미터에 바디(@Body)를 사용, PUT/POST는 바디를 사용
    • 주의! 요청과 응답도 모두 바디로 사용하므로 혼동하면 안된다!!! (내가 그랬음)
  • 사용자가 서버에 데이터를 추가하거나 정보를 입력 할 때 사용 (회원가입, 글쓰기 등)
  • 예시 API서버에서의(로그인 기능) POST 메소드 필수 파라미터
    • Body : email, password
      • 소스 코드에서는 User클래스의 user객체를 이용하여 email과 password의 값을 대체하였다.
    // 로그인 API
    // POST는 Body 사용
    @POST("/users/login")
    Call<UserRes> login(@Body User user);

만약 하위 경로 뒤에 추가 경로가 필요하다면?

  • 파라미터에 패스(@Path) 사용
  • 경로의 하위 경로 뒤에 '/{키}' 로 정의, 예시) /posting/{postingId} 
  • DELETE, PUT 또한 바디를 사용하기에 사용 방법은 POST 메소드와 같다.
  • 예시 API서버에서의(포스팅 삭제 기능) DELETE 메소드 필수 파라미터
    • Header : Authorization
    • Path : postingId (특정 포스팅을 삭제해야하므로 포스팅의 id가 필요하기 때문, 포스팅 수정도 같은 설계 방식) 
// 포스팅 삭제 API
// 추가 경로가 필요하면 @Path 사용
@DELETE("/posting/{postingId}")
Call<PostRes> deletePosting(@Header("Authorization") String token, @Path ("postingId") int postingId);

설정한 Retrofit을 이용하여 실제 서버와 통신 요청

  • Retrofit 객체 생성
    • Retrofit 생성할 객체명 = Retrofit 설정 클래스.getRetrofitClient(요청 할 액티비티 이름)
      • 예) Retrofit retrofit = NetworkClient.getRetrofitClient(LoginActivity.this);
  • API 객체 생성
    • API 생성할 객체명 = retrofit.create(API.class)
      • 예) UserApi api = retrofit.create(UserApi.class)
  • 클래스의 멤버 변수를 저장할 객체 생성
    • 클래스 생성할 객체명 = new 클래스(데이터1, 데이터2, 데이터3......)
      • 예) User user = new User (email, password)
  • API 요청 메소드 객체 생성
    • Call<멤버 변수를 저장할 클래스> 생성할 객체명 = API객체명.API설정메소드(클래스 객체명)
      • 예) Call<UserRes> call = api.login(user)
  • API 요청
    • call.enqueue (new Callback<멤버 변수를 저장할 클래스>() { onResponse메소드, onFailure 메소드})
      • onResponse : 요청에 성공 할 경우 / onFailure : 요청에 실패 할 경우 
// 네트워크로 데이터 전송, Retrofit 객체 생성
// NetworkClient : 위에서 Retrofit 기본 설정한 클래스 파일
// LoginActivity.this : API서버와 통신 할 액티비티 이름
Retrofit retrofit = NetworkClient.getRetrofitClient(LoginActivity.this);

// API 요청 메소드 객체 생성
// UserApi : 설정한 GET, POST 메소드를 정의해둔 클래스 파일 (회원가입, 로그인, 로그아웃)
UserApi api = retrofit.create(UserApi.class);

// POST 요청의 반환 값 저장 변수 선언
User user = new User(email, password);

// API POST 요청 설정
// UserRes : 요청 받을 데이터를 저장 할 변수들을 가지는 클래스
// api.login(user) : UserApi에서 설정한 login 메소드 사용
// user은 API 서버에서 요구하는 파라미터를 담은 변수
Call<UserRes> call = api.login(user);

// API 요청
call.enqueue(new Callback<UserRes>() {
    @Override
    public void onResponse(Call<UserRes> call, Response<UserRes> response) { 
        if(response.isSuccessful()) {
			// 200 OK, 네트워크 정상 응답 할 경우, 코드 작성
        }
    }
    @Override
    public void onFailure(Call<UserRes> call, Throwable t) {
        // API 응답 요청이 실패 할 경우, 코드 작성

    }
});

안드로이드 스튜디오로 실제 서버와 통신을 하는 앱 개발

Retrofit 기능 설명을 하기 위한 것이므로 UI 설계는 생략합니다.

전체 소스 코드를 공유하기에는 파이썬으로 작성된 API 서버와,

자바로 작성된 안드로이드 스튜디오 프로젝트를 공유하기에는

설명이 너무 길어질 것 같아서 위에서 예시로 보여준 기능만 설명합니다. 

 


내 포스팅 목록 보기 (GET)

// 포스팅 목록 호출
void getNetworkData() {
    // 데이터 초기화
    snsList.clear();
    count = 0;
    page = 1;

    // 네트워크로 데이터 전송, Retrofit 객체 생성
    Retrofit retrofit = NetworkClient.getRetrofitClient(MainActivity.this);
    SnsApi api = retrofit.create(SnsApi.class);

    // API 요청
    // 헤더에 설정 할 데이터 확인, 변수로 저장되어있는 토큰 호출
    Call<SnsListRes> call = api.getPostingList("Bearer "+accessToken, page);
    
    call.enqueue(new Callback<SnsListRes>() {
        @Override
        public void onResponse(Call<SnsListRes> call, Response<SnsListRes> response) {
            // 200 OK, 네트워크 정상 응답
            // 응답 결과를 data에 저장
            if(response.isSuccessful()) {
                SnsListRes data = response.body();
                // 응답은 되었으나 데이터가 존재하지 않을 경우
                if (data.getResult_list() == null) {
                    Toast.makeText(getApplicationContext(), "포스팅이 존재하지 않습니다.", Toast.LENGTH_SHORT).show();
                    return;
                }
                // 기존의 데이터에서 추가
                snsList.addAll(data.getResult_list());
                // 응답받은 데이터 리사이클러뷰 어댑터에 리스트 목록 출력
                // Retrofit 설명 포스팅이기 때문에 어댑터 설정은 생략
                adapter = new SnsAdapter(MainActivity.this, snsList);
                recyclerView.setAdapter(adapter);
            }
        }
        @Override
        public void onFailure(Call<SnsListRes> call, Throwable t) {
            // API 응답에 실패 할 경우 코드 작성
        }
    });
}

로그인하기 (POST)

// 네트워크로 데이터 전송, Retrofit 객체 생성
Retrofit retrofit = NetworkClient.getRetrofitClient(LoginActivity.this);
UserApi api = retrofit.create(UserApi.class);

// POST URL의 반환 값 저장 변수 선언
User user = new User(email, password);

// API POST 요청
Call<UserRes> call = api.login(user);

call.enqueue(new Callback<UserRes>() {
    @Override
    public void onResponse(Call<UserRes> call, Response<UserRes> response) {
        // 200 OK, 네트워크 정상 응답
        if(response.isSuccessful()) {
            // 응답 결과를 userRes에 저장
            UserRes usersRes = response.body();
            // API 호출시 AccessToken 헤더에 추가
            // 반환받은 AccessToken은 SharedPreferences에 저장
            SharedPreferences sp = getApplication()
                    .getSharedPreferences(Config.PREFERENCES_NAME, MODE_PRIVATE);
            SharedPreferences.Editor editor = sp.edit();
            editor.putString("accessToken", usersRes.getAccess_token());
            editor.apply();

            Toast.makeText(getApplicationContext(), "로그인되었습니다.", Toast.LENGTH_SHORT).show();

            // MainActivity 이동, 해당 액티비티는 종료
            Intent intent = new Intent(LoginActivity.this, MainActivity.class);
            startActivity(intent);
            finish();
        }
    }
    @Override
    public void onFailure(Call<UserRes> call, Throwable t) {
    	// API 응답에 실패 할 경우 코드 작성
    }
});

UserApi.java / SnsApi.java

public interface UserApi {
    // 로그인 API
    @POST("/users/login")
    Call<UserRes> login(@Body User user);
}

public interface SnsApi {
    // 포스팅 리스트 호출 API
    // GET은 Query String 사용
    @GET("/posting")
    Call<SnsListRes> getPostingList(@Header("Authorization") String token, @Query("page") int page);
}

User.java / UserRes.java / SnsListRes.java

// User.java
import java.io.Serializable;
public class User implements Serializable {
    private String email;
    private String password;

    public User(String email, String password) {
        this.email = email;
        this.password = password;
    }

    public User() {
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

// UserRes.java
import java.io.Serializable;
public class UserRes implements Serializable {

    private String result;
    private String accessToken;

    public String getResult() {
        return result;
    }

    public void setResult(String result) {
        this.result = result;
    }

    public String getAccess_token() {
        return accessToken;
    }

    public void accessToken(String accessToken) {
        this.accessToken = accessToken;
    }
}

// SnsListRes.java
import java.io.Serializable;
public class SnsListRes implements Serializable{
    private List<Sns> result_list;

    public SnsListRes(List<Sns> result_list) {
        this.result_list = result_list;
    }

    public List<Sns> getResult_list() {
        return result_list;
    }

    public void setResult_list(List<Sns> result_list) {
        this.result_list = result_list;
    }
}

실제 모바일 화면

  • 로그인하기

 

 

  • 로그인 성공 모습 + 나의 포스팅 목록 보기
    • 로그인 후에 나의 포스팅 목록을 바로 보여지게 설계하였음

반응형