Retrofit
Retrofit은 OkHttp와 동일하게 Square에서 만들어졌으며 OkHttp를 기반으로 더 Type-safe하고, 더 직관적으로 사용할 수 있는 라이브러리입니다.
OkHttp는 AsyncTask로 서버와 통신을 하였는데 이는 시간이 많이 소요되고 구현이 어렵습니다.
하지만 Retrofit은 AsyncTask 없이 Background Thread 실행 -> Callback을 통해 서버와 통신을 하기 때문에 성능이 좋고 간편하게 사용할 수 있는 장점이 있습니다.
Retrofit에서는 Annotation을 사용하기 때문에 코드 가독성이 뛰어납니다.
또한, 반복된 작업은 다른 라이브러리에 넘겨서 처리하기 때문에 구현이 간단합니다.
- HttpUrlConnection의 Connection / Input&OutputStream / URL Encoding 생성 및 할당
- OkHttp의 쿼리스트링, Request / Response 설정
Retrofit의 3가지 구성 요소
- DTO : 'Data Transfer Object' 형태의 모델로서 데이터 클래스
- JSON 타입 변환에 사용
- Interface : 사용할 HTTP 통신의 CRUD 동작들을 정의해놓은 인터페이스
- ( CREATE / READ / UPDATE / DELETE ) -> ( POST / GET / PUT / DELETE )
- Object : Retrofit.Builder를 통해 Retrofit 인스턴스 생성
- Interface 객체 구현
Retrofit 구현하기
1. Manifest에 권한 선언
Retrofit을 사용하기 위해서 인터넷에 대한 권한을 추가합니다.
<uses-permission android:name="android.permission.INTERNET" />
2. build.gradle에 의존성 추가
Retrofit을 사용하기 위해서 build.gradle에 Retrofit 라이브러리를 추가합니다.
JSON 타입의 응답결과를 객체로 매핑해주는 Gson Converter 라이브러리도 추가합니다.
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
3. DTO 클래스 선언
REST API로 받아올 데이터를 변환하여 매핑할 DTO 클래스를 선언합니다.
이때, REST API의 응답 데이터 구조에 맞게 클래스를 작성해야 합니다.
data class RequestSignup(
@SerializedName("email") var email: String,
@SerializedName("password") var password: String
)
data class ResponseSignup(
@SerializedName("message") val message: String
)
data class ErrorContent(
@SerializedName("errorMessage") val errorMessage: String
)
4. Interface 정의
사용할 기능에 따라 메소드를 @POST, @GET, @PUT, @DELETE 어노테이션과 함께 정의합니다.
요청 인자는 필요에 따라 @Body, @Field, @Query, @Path 어노테이션을 사용하여 보낼 수 있습니다.
- @Body : JSON 형태의 하나의 객체만 전달 (key: value, key: value)
- @Field : @FormUrlEncoded 어노테이션 사용 (key=value&key=value)
- @Query : URL 뒤에 전달되는 파라미터 (?key=value&key=value)
- @Path : @Query와는 다르게 경로 자체를 변수로써 사용 (/value/value)
interface RetrofitService {
@Headers("Content-Type: application/json")
@POST("/users/signup")
fun signup(@Body body: RequestSignup): Call<ResponseSignup>
@POST("users/login")
@FormUrlEncoded
fun login(@Field("email") email: String, @Field("password") password: String)
: Call<ResponseLogin>
@GET("/users/albums")
fun getUserAlbum(@Query("id") userId: String): Call<UserAlbum>
@GET("user/info/{id}")
fun getUserInfo(@Path("id") userId: String): Call<UserInfo>
}
5. Retrofit 객체 생성
호출이 필요할 때마다 객체를 생성하는 것은 비효율적이기 때문에 Object로 Retrofit 객체를 생성합니다.
만약 서버 요청 시 응답받는 데까지 시간이 조금 걸린다면, OkHttpClient의 connectTimeout()을 호출하여 최대 요청 시간을 정의해 줄 수 있습니다.
// Singleton
object RetrofitClient {
private const val BASE_URL = "https://.../"
private const val MY_TOKEN = API_TOKEN
// 서버 요청시간 정의
val okHttpClient = OkHttpClient.Builder()
.addInterceptor {
val request = it.request()
.newBuilder()
.addHeader("Authorization", "Bearer $MY_TOKEN")
.build()
it.proceed(request)
}
.connectTimeout(120, TimeUnit.SECONDS)
.readTimeout(120, TimeUnit.SECONDS)
.writeTimeout(120, TimeUnit.SECONDS)
.build()
var gson = GsonBuilder().setLenient().create()
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
val retrofitService: RetrofitService by lazy { retrofit.create(RetrofitService::class.java) }
}
6. 커스텀 Callback 설정
Retrofit을 통한 통신 성공 여부에 따른 처리를 다르게 하고 싶다면 커스텀으로 callback을 만듭니다.
interface RetrofitCallback {
fun onSuccess(message: String)
fun onError(errorMessage: String, errorCode: Int)
fun onFailure(t: Throwable)
}
7. HTTP 요청과 응답
Retrofit은 execute(동기), enqueue(비동기)의 2가지 통신 방식을 지원합니다.
통신에 성공했을 경우에는 onResponse(), 통신에 실패했다면 onFailure()가 호출됩니다.
통신에 성공한 뒤 정상적으로 응답을 받으면 reponse.body()에 요청했던 데이터가 들어옵니다.
연결은 되었으나 잘못된 요청 인자를 보냈거나 주소를 잘못 작성하는 등의 실수로 에러가 발생한다면 아래의 else 구문이 실행됩니다.
여기서 response.errorBody()를 사전에 정의해놓은 ErrorContent로 convert 해줍니다.
fun signup(email: String, password: String, callback: RetrofitCallback) {
val requestSignup = RequestSignup(email, password)
val call: Call<ResponseSignup> = RetrofitClient.retrofitService.signup(requestSignup)
call.enqueue(object : Callback<ResponseSignup> {
override fun onResponse (call: Call<ResponseSignup>, response: Response<ResponseSignup>) {
if (response.isSuccessful) {
val body = response.body()
val messgae = body?.message ?: "빈 메세지"
callback.onSuccess(messgae)
}
else {
val errorBody = RetrofitClient.retrofit.responseBodyConverter<ErrorContent>(
ErrorContent::class.java,
ErrorContent::class.java.annotations
).convert(response.errorBody())
try {
val errorMessage = errorBody?.errorMessage ?: "빈 메세지"
callback.onError(errorMessage, response.code())
} catch (e: IOException) {
e.printStackTrace()
}
}
}
override fun onFailure(call: Call<ResponseSignup>, t: Throwable) {
Log.e("signup_onFailure", t.localizedMessage)
callback.onFailure(t)
}
})
}
'안드로이드 > 활용' 카테고리의 다른 글
[Android] Jsoup으로 HTML 파싱하기 (0) | 2023.08.21 |
---|---|
[Android] Tikxml로 XML 파싱하기 (0) | 2023.08.20 |
[Android] HttpUrlConnection과 OkHttp (0) | 2023.08.18 |
[Android] Socket 통신 (0) | 2023.08.17 |
[Android] MediaPlayer와 MediaRecoder (0) | 2023.08.02 |