YU Market에서 구현한 위도,경도를 주소로 반환하기를 따로 코드로 구현해보려고 합니다.
이전에 작성한 위도 경도 받아오기 글 입니다. 위 글을 읽고 위도 경도부터 이해 한 뒤 오셔야 글이 이해됩니다.
개념-Retrofit2-사용법
레트로핏에 대한 이해도가 부족하다면 이 글도 읽고 오셔야 합니다.API 와 EndPoint 란?
API에 대한 이해도가 부족하다면 이 글도 읽고 오셔야 합니다.
위와 같이 Endpoint interface를 만들어서 API에 접근해서 통신할 수 있게 해줍니다. Endpoint에서 URL에 대한 결과값을 반환 받을 메서드는 GET으로 하였습니다.
@GET은 HTTP 통신 방식으로 Parameter들이 URL에 추가되는 GET 방식으로 하겠다는 의미이고 @Header는 Request Header에 TMAP_API의 Key값을 추가한다는 의미입니다. @Query는 Request시 담을 Parameter들이며 아래와 같이 URL이 구성되며 Request를 보내게 됩니다.
https://apis.openapi.sk.com/tmap/geo/reversegeocoding?version={version}&lat={lat}&lon={lon}&coordType={coordType}&addressType={addressType}&callback={callback}&appKey={appKey}
object RetrofitUtil {
private var instance: Retrofit? = null
val mapApiService: MapApiService by lazy {
getRetrofit().create(MapApiService::class.java)
}
private fun getRetrofit(): Retrofit {
if(instance == null) {
Retrofit.Builder()
.baseUrl(Url.TMAP_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(buildOkHttpClient())
.build()
}
return instance!!
}
private fun buildOkHttpClient(): OkHttpClient {
val interceptor = HttpLoggingInterceptor()
if (BuildConfig.DEBUG) {
interceptor.level = HttpLoggingInterceptor.Level.BODY
} else {
interceptor.level = HttpLoggingInterceptor.Level.NONE
}
return OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.addInterceptor(interceptor)
.build()
}
}
RetrofitUtil은 기존의 객체를 재활용 하기 위해서 싱글턴으로 구현하였습니다. 그리고 Response와 Request에 대한 정보를 보기 위해서 OkHttpClient를 사용하여 val interceptor = HttpLoggingInterceptor() 객체를 생성해서 Retrofit에 추가해 줍니다.
Request를 해서 API에서 Response를 받으면 GsonConverter를 이용하여 AddressInfoResponse 객체로 변환(JSON -> 객체)하게 됩니다. ReverseGeo의 Response JSON은 아래와 같습니다.
{"addressInfo": {
"fullAddress": "서울시 마포구 상수동",
"addressKey": "3004545827",
"roadAddressKey": "4001807949",
"addressType": "A10",
"city_do": "서울시",
"gu_gun": "마포구",
"eup_myun": "",
"adminDong": "망원2동",
"adminDongCode": "1150056000",
"legalDong": "상수1동",
"legalDongCode": "1132010500",
"ri": "죽전리",
"roadName": "강남대로",
"buildingIndex": "11-11",
"buildingName": "영진그린빌라",
"mappingDistance": 0,
"roadCode": "261104175064",
"bunji": "121-12",
"adminDongCoord":{
"lat": "37.56624167",
"lon": "126.90194167",
"latEntr": "37.56610000",
"lonEntr": "126.90173889"
},
"legalDongCoord":{
"lat": "37.56624167",
"lon": "126.90194167",
"latEntr": "37.56610000",
"lonEntr": "126.90173889"
},
"roadCoord":{
"lat": "37.56631111",
"lon": "126.90161389",
"latEntr": "37.56610000",
"lonEntr": "126.90173889"
}}
위와 같은 형식의 JSON이 AddressInfo를 담고 있는 AddressInfoResponse로 변환이 됩니다. AddressInfoResponse 내부에 존재하는 AddressInfo의 구조는 아래와 같습니다.
data class AddressInfo(
@SerializedName("fullAddress")
@Expose val fullAddress: String?,
@SerializedName("addressType")
@Expose val addressType: String?,
@SerializedName("city_do")
@Expose val cityDo: String?,
@SerializedName("gu_gun")
@Expose val guGun: String?,
@SerializedName("eup_myun")
@Expose val eupMyun: String?,
@SerializedName("adminDong")
@Expose val adminDong: String?,
@SerializedName("adminDongCode")
@Expose val adminDongCode: String?,
@SerializedName("legalDong")
@Expose val legalDong: String?,
@SerializedName("legalDongCode")
@Expose val legalDongCode: String?,
@SerializedName("ri")
@Expose val ri: String?,
@SerializedName("bunji")
@Expose val bunji: String?,
@SerializedName("roadName")
@Expose val roadName: String?,
@SerializedName("buildingIndex")
@Expose val buildingIndex: String?,
@SerializedName("buildingName")
@Expose val buildingName: String?,
@SerializedName("mappingDistance")
@Expose val mappingDistance: String?,
@SerializedName("roadCode")
@Expose val roadCode: String?
)
JSON의 구조와 같은 것을 확인해 볼 수 있습니다. @SerializedName은 Annotation내의 문자열을 Key로 가지는 JSON의 값을 할당하라는 의미입니다. 즉 val roadCode의 경우 roadCode를 Key로 가지는 JSON의 값이 할당이 됩니다.
위의 코드중 API 부분만 다뤄보겠습니다.
inner class MyLocationListener : LocationListener {
override fun onLocationChanged(location: Location) {
//binding.locationTitleTextView.text = "${location.latitude}, ${location.longitude}"
getReverseGeoInformation( LocationEntity(
latitude = location.latitude,
longitude = location.longitude
)
)
removeLocationListener()
}
private fun removeLocationListener() {
if (::locationManager.isInitialized && ::myLocationListener.isInitialized) {
locationManager.removeUpdates(myLocationListener)
}
}
}
위 코드에서 getReverseGeoInformation() 함수에 위도 경도를 넘겨주는 것을 알 수 있습니다. 이 위도 경도를 넘겨주고 우리는 콜백으로 주소값을 반환 받을 수 있는 것 입니다.
fun getReverseGeoInformation(locationLatLngEntity: LocationEntity) {
uiScope.launch {
withContext(Dispatchers.IO) {
val response = RetrofitUtil.mapApiService.getReverseGeoCode(
lat = locationLatLngEntity.latitude,
lon = locationLatLngEntity.longitude
)
if (response.isSuccessful) {
val body = response.body()
withContext(Dispatchers.Main) {
binding.locationTitleTextView.text = "${body?.addressInfo?.fullAddress}"
}
}
else {
null
}
}
}
}
getReverseGeoInformation()에서는 RetrofitUtil.mapApiService.getReverseGeoCode()에 다시 위도 경도값을 선언하는 것을 볼 수 있습니다. 위 과정에서 코루틴을 사용하여 비동기적으로 진행하고 있습니다.
과정 요약
- MapApiService Interface의 End point를 생성
- MapApiService End point의 메서드 및 파라미터 URL 구성
- Retrofit을 이용하여 TMAP API에 HTTP Request 전송
- TMAP API가 Response(JSON)를 반환
- 반환된 Response를 GsonConverter를 이용하여 객체로 생성
- 생성된 객체를 받아 사용