이번 포스팅에서는 Retrofit 라이브러리로 HTTP 통신을 수행하는 법에 대해 알아보도록 하겠습니다.
안드로이드에서의 HTTP 통신에 대한 개괄적 설명과 Retrofit을 사용해야 하는 이유에 대해서는 안드로이드의 HTTP 통신 라이브러리 고찰에서 다루었으니 그것을 참조하시면 좋을 것 같습니다. 또한 Retrofit의 기술적 특징에 대해서는 라이브러리 제작자가 설명해 둔 Retrofit 2과 함께하는 정말 쉬운 HTTP 를 보는것이 가장 정확할 것 같습니다.
이 강의에서는 경기데이터드림에서 제공하는 코로나바이러스 감염증 선별진료소 운영 의료기관 현황 API를 통해 가져온 JSON 데이터를 Recyclerview에 표시해보면서 Retrofit을 어떻게 사용하는지 알아보도록 하겠습니다.
라이브러리 추가
build.gradle에 Retrofit과 JSON을 다루기 위한 Moshi 컨버터, 그리고 로그를 찍기위한 OkHttp 라이브러리를 추가해 줍니다.
그럼 이 JSON에 대응하는 DTO를 작성합니다. JSON To Kotlin Class 플러그인에 위의 JSON을 붙여넣고 EmgMedResponse라는 이름으로 변환하면 다음과 같이 5개의 데이터 클래스가 생성됩니다. 이 때 옵션은 Val, Nullable, MoShi (Reflect)를 선택해줍니다.
JSON은 객체를 중괄호{} 로 감싸서 표시하는데 이걸 DTO로 변환할 때에는 객체 하나를 데이터 클래스 하나로 대응시킵니다. Moshi로 객체 안의 Key-Value 쌍을 변환할 때는 @field:Json(name = "") 어노테이션을 사용합니다. name에 Key 값을 지정하면, 대응하는 Value를 val 프로퍼티에 매칭해 줍니다. 각 클래스의 내용이 짧기 때문에 여기서는 관리상 편의를 위해 EmgMedResponse.kt 안에 다른 클래스 내용을 다 붙여넣도록 하겠습니다.
DTO 중 Row 클래스의 내용을 받아 ViewHolder에 표시하는 Recyclerview 어댑터를 작성합니다. 데이터의 갱신은 ListAdapter를 사용하여 수행합니다. ListAdapter의 구체적인 설명에 대해서는 DiffUtil과 ListAdapter 이해하고 RecyclerView에 적용하기 강의를 참조하시기 바랍니다.
Retrofit은 HTTP API의 request를 인터페이스로 정의하여 사용합니다. 이 강의에서 사용할 선별진료소 API는 다음과 같은 인자를 받을 수 있게 만들어져 있습니다.
변수명
타입
변수 설명
설명
KEY
STRING
인증키
기본값 : sample key
Type
STRING
호출 문서(xml, json)
기본값 : xml
pIndex
INTEGER
페이지 위치
기본값 : 1(sample key는 1 고정)
pSize
INTEGER
페이지 당 요청 숫자
기본값 : 100(sample key는 5 고정)
그러면 이중 KEY와 Type 인자를 전달받아 GET 요청을 수행하는 Retrofit 서비스를 하나 만들어보겠습니다. API 요청주소가 https://openapi.gg.go.kr/EmgMedInfo 이므로 고정주소인 Base url은 https://openapi.gg.go.kr/ 이고, @GET요청시 인자는 Base url 뒤의 EmgMedInfo 입니다. KEY와 Type 인자는 @Query 어노테이션을 써서 전달해줍니다.
인터페이스의 메소드인 getEmgMedData는 Call 객체를 반환하도록 정의되었습니다. Call 은 서버에 request를 보내고 그 응답을 반환하는 1회용 객체입니다.
코드를 해석하면 getEmgMedData 는 BASE_URL/EmgMedInfo?KEY="KEY"&Type="Type"이라는 GET 요청을 보내고 Call<EmgMedResponse> 객체를 반환받는 동작을 하는 함수라는 뜻이 됩니다.
Retrofit 객체 준비
RetrofitApi 작성
Retrofit을 사용하기 위해서는 우선 객체를 만들어주어야 합니다. 이 때 여러개의 객체가 만들어지면 자원도 낭비되고 통신에 혼선도 발생할 수 있으므로 object 키워드를 써서 싱글톤으로 만들어지도록 합니다. 이 때 각 변수에는 lazy를 적용함으로써 실제 사용되는 순간이 와야 비로소 만들어지게 되고, 단 하나의 인스턴스만이 만들어지도록 하였습니다. 싱글톤에 대한 구체적인 설명은 알기쉬운 Singleton Pattern 강의를 참고하시기 바랍니다.
빌더 패턴을 통해 retrofit 객체를 만들어줍니다. addConverterFactory에서 DTO 변환에 사용할 JSON 컨버터를 Moshi로 지정하고, baseUrl 을 전달한 뒤 build()로 객체를 생성합니다. 그리고나서 retrofit 인스턴스의 create 명령을 이용해서 emgMedService의 인스턴스를 만들어줍니다.
전송패킷 모니터링
retrofit 객체를 생성할때 client 속성에 okHttpClient를 넘겨주면 로그캣에서 패킷내용을 모니터링할 수 있습니다. okHttpClient는 다음과 같이 서버와 코어 사이에서 데이터를 가로채는 Interceptor로서 기능하는 객체입니다.
Retrofit 구동
그럼 MainActivity에서 Retrofit을 사용해보겠습니다. 플로팅 버튼에 대해 다음 내용의 retrofitWork()를 실행하는 클릭리스너를 구성합니다.
privatefunretrofitWork(){valservice=RetrofitApi.emgMedServiceservice.getEmgMedData(getString(R.string.api_key),"json").enqueue(object: Callback<EmgMedResponse>{overridefunonResponse(call:Call<EmgMedResponse>,response:Response<EmgMedResponse>){if(response.isSuccessful){Log.d("TAG",response.body().toString())// head를 스킵하기 위해 index 1번을 가져옴
valresult=response.body()?.emgMedInfo?.get(1)?.rowmyRecyclerViewAdapter.submitList(result!!)}}overridefunonFailure(call:Call<EmgMedResponse>,t:Throwable){Log.d("TAG",t.message.toString())}})}
싱글톤 Retrofit으로 서비스 인스턴스를 생성하고 getEmgMedData를 실행해서 Call 객체를 획득합니다. 인증키는 R.string.api_key 를 이용해 입력했고 서버로부터 json 데이터를 반환받도록 하였습니다.
Call 작업은 두가지 방법으로 실행시킬 수 있습니다. execute를 사용하면 request를 보내고 response를 받는 행위를 동기적으로 행합니다. 그리고 enqueue 작업을 실행하면 request는 비동기적으로 보내고, response는 콜백으로 받게 됩니다. 여기서는 네트워크 작업을 안정적으로 수행하기 위해 enqueue로 실행하도록 하였습니다.
enqueue의 통신결과는 성공했을 때 onResponse, 실패했을 때 onFailure 를 리스너로 받을 수 있으므로 각 상황에 따른 처리를 해 줍니다. 통신에 성공하면 response.isSuccessful을 확인하여 사용할 수 있는 응답이 돌아왔는지 확인하고, 그렇다면 body에서 값을 꺼내어 Recyclerview에 넘겨주면 됩니다.
코루틴에서 사용하기
retrofit의 사양에 따르면 enqueue는 request를 백그라운드 스레드에서, response 콜백은 메인스레드에서 처리하게 됩니다. 그런데 코루틴을 사용하면 콜백을 쓰지 않아도 동일한 처리를 할 수 있습니다. 그래서 이번엔 코루틴 안에서 retrofit 처리를 수행하도록 해 보겠습니다. 코루틴에 대한 구체적인 설명에 대해서는 알기쉬운 코루틴 이론을 참조하시기 바랍니다.
다음으로 코루틴으로 작동하는 서비스를 인터페이스에 추가해 줍니다. 서비스가 코루틴 안에서 수행되어야 하므로 suspend 키워드를 붙여주고, 반환값은 Call 객체가 아니라 Response 객체로 받도록 변경하였습니다. 코루틴 자체적으로 비동기적인 처리를 수행하기 때문에, Call이 제공하는 흐름처리 기능은 더이상 필요가 없습니다.