Module단위 build.gradle 파일을 열어 dependencies에 아래 라인을 추가해주자!
dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:okhttp:4.8.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.8.0'
}
각각 버전 2.9.0과 4.8.0으로 추가했지만 Retrofit 공식문서, OKHttp 공식문서에서 확인한 후 안정된 버전으로 작성하는 것이 좋다.
API가 다음과 같이 정의되어 있다고 하면
{
"status": 201,
"success": true,
"message": "Created",
"data": {
"id": 1,
"author": "String",
"title": "String",
"location": "Seoul",
"content": "String",
"category": [
"law",
"English"
],
"thumbnailImageUrl": null
}
}
이에 맞춰 클래스를 만든다.
data class PolicyResponse(
@SerializedName("status") val status: Int,
@SerializedName("success") val success: Boolean,
@SerializedName("message") val message: String,
@SerializedName("data") val data: List<Data>
) {
data class Data(
@SerializedName("id") val id: Int,
@SerializedName("author") val author: String,
@SerializedName("title") val title: String,
@SerializedName("location") val location: String,
@SerializedName("content") val content: String,
@SerializedName("category") val category: List<String>,
@SerializedName("thumbNailImageUrl") val thumbNailImageUrl: String?
)
}
코드상 변수명과 API에 정의된 이름을 달리하고 싶다면 @SerealizedName 어노테이션을 사용한다. 또, 추후 Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 38 path $.data
과 같은 에러가 뜬다면 서버로부터 전달받은 타입과 내가 예상한 타입이 맞지 않아서 발생하는 문제이므로 이 PolicyResponse에서 타입을 잘못 작성하지는 않았는지 살펴보는 것이 좋다.
interface RetrofitInterface {
@GET("policy")
fun getPolicy(
@Query("location") location: String,
@Header("Authorization") authToken: String
): Call<PolicyResponse>
}
서버주소를 baseUrl
이라 하고,
baseUrl/policy?location=서울
로부터 데이터를 전달받아야 한다고 하자.
@Get("policy")
: baseUrl 뒤에 /policy가 붙는다.@Query("location")
: 주소에 들어가는 파라미터인 location을 @Query로 지정한다.@Header("Authorization")
: 서버가 토큰 인증을 사용중이고 헤더에 토큰을 담아 넘겨줘야 하는 경우 @Header("Authorization")을 추가한다.Call<PolicyResponse>
: 서버로부터 데이터를 PolicyResponse 타입으로 받아온다.RetrofitClient는 싱글톤으로 구현하기 위해 object 키워드를 사용한다. (코틀린에서의 object는 클래스를 싱글톤으로 구현한다.)
* 싱글톤: 객체의 인스턴스를 1개만 생성하고 계속 재사용하는 패턴
object RetrofitClient {
private var instance: Retrofit? = null
private const val CONNECT_TIMEOUT_SEC = 20000L
fun getInstance() : Retrofit {
if(instance == null){
// 로깅인터셉터 세팅
val interceptor = HttpLoggingInterceptor()
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
// OKHttpClient에 로깅인터셉터 등록
val client = OkHttpClient.Builder()
.addInterceptor(interceptor)
.connectTimeout(CONNECT_TIMEOUT_SEC, TimeUnit.SECONDS)
.build()
instance = Retrofit.Builder()
.baseUrl("baseUrl을 여기 작성")
.addConverterFactory(GsonConverterFactory.create())
.client(client) // Retrofit 객체에 OkHttpClient 적용
.build()
}
return instance!!
}
}
나의 경우 구현 과정에서 자꾸 에러가 나서 무슨 에러인지 정확히 보기 위해 LoggingInterceptor
를 추가했는데, 굳이 에러가 나는 상황이 아니더라도 Response와 Request에 대한 정보를 다음과 같이 한눈에 볼 수 있기 때문에 추후 디버깅을 위해서라도 추가해두는 편이 좋다.
API 11부터는 네트워크 통신은 무조건 작업 스레드에서 해야 한다. 그렇지 않으면 Exception이 발생한다고 한다. 즉, onCreate()에 모든 코드를 때려박았다가는 문제가 된다. 따라서 스레드에서 통신 작업을 돌렸다.
class PolicyFragment : Fragment() {
private val retrofit: Retrofit = RetrofitClient.getInstance() // RetrofitClient의 instance 불러오기
private val api: RetrofitInterface = retrofit.create(RetrofitInterface::class.java) // retrofit이 interface 구현
private val authToken = "토큰값을 여기 작성"
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
// retrofit setting
Runnable {
api.getPolicy("서울", "Bearer $authToken").enqueue(object : Callback<PolicyResponse>{
// 전송 실패
override fun onFailure(call: Call<PolicyResponse>, t: Throwable) {
Log.d("태그", t.message!!)
}
// 전송 성공
override fun onResponse(call: Call<PolicyResponse>, response: Response<PolicyResponse>) {
Log.d("태그", "response : ${response.body()?.data}") // 정상출력
// 전송은 성공 but 서버 4xx 에러
Log.d("태그: 에러바디", "response : ${response.errorBody()}")
Log.d("태그: 메시지", "response : ${response.message()}")
Log.d("태그: 코드", "response : ${response.code()}")
}
})
}.run()
}
}
response로 받은 PolicyResponse 객체에서 data를 출력해보면 이렇게 정상적으로 들어왔음을 확인할 수 있다!