프로젝트(나모)의 팀 블로그에서 작성한 내용을 가지고 왔습니다.
🔗 원본 링크: https://namo-log.vercel.app/android-map
* 작성일: 24.06.09
안녕하세요, 나모 안드로이드 개발자 코코아입니다!
오늘은 카카오맵 SDK 버전을 업데이트 했던 경험에 대해 이야기해보려 합니다.
기존 코드에서 코드가 어떻게 변경되었는지에 대해 다뤄볼게요!
지도를 표시하고, 지도에 마커를 나태내는 것까지 모두 다룰 예정입니다.
~~ 사건의 발단 ~~
https://devtalk.kakao.com/t/android-ios-sdk-2024-6-30/134180
kakao developers에서 위와 같은 글을 보게 되었습니다.
구 버전의 SDK를 지원 종료할 예정이라고 하니, 이 지도 때문에 앱 사용에 지장이 가면 안 되잖아요?
그래서 지원 종료 전에 v1 → v2로 버전 업데이트를 진행하기로 했습니다.
기존에 지도 SDK는 많이 사용해봐서 어려움이 크게 없으리라고 생각했는데,
예상치 못하게 구현에 어려움을 맞닥뜨리게 되었습니다.
비로 신규 SDK라서 그런지 관련 자료가 몹시 적었고,
카카오 측의 가이드는 모두 자바로 작성되어 있었다는 점이었는데요,
참고한 공식 가이드라인 링크입니다.
KakaoMaps SDK for Android
안드로이드 개발을 코틀린으로 시작해 코틀린밖에 모르는 저로서는 청천벽력 같은 소식이었답니다.
공식 문서에서 발견한 ‘KakaoMaps Api Demo
’라는 이름의 샘플 프로젝트를 실제로 돌려봐도 뭐가 문제인 건지 권한이 없다는 오류가 나오면서 지도가 제대로 표시되지 않는 탓에 ‘이 메서드는 대체 어디에 쓰이는 걸까’, ‘기존 메서드는 이번에 어떻게 변경이 되었나’, 하며 샘플 프로젝트 및 라이브러리의 코드를 조금씩 뜯어보기 시작했습니다.
항상 코틀린만 보아왔던 저에게 자바로 작성된 코드는 이해하기가 조금 어려웠답니다.
카카오맵 SDK 설명
https://apis.map.kakao.com/android_v2/reference/overview-summary.html
기존 기능들을 어떻게 다시 구현할 수 있을지에 대해서는 위의 자료에서 많이 힌트를 얻었습니다.
위 링크에서 카카오맵 API에 대한 구체적인 설명을 확인할 수 있습니다.
다행히 안드로이드 스튜디오에 자동 마이그레이션 기능이 있어 조금 수월했던 거 같아요. 세상이 참 좋아졌습니다.
그렇지만 기존에 사용하던 SDK의 의존성을 지웠을 때….
코드들에서 나타났던 빨간 줄들은 다시 생각해도 참 아찔했습니다.
기존 카카오맵 코드를 제가 작성했던 게 아니었기에 코드를 이해하는 데 조금 어렵긴 했지만,
사실 SDK가 바뀌면서 변경된 부분이 너무 많아서 코드를 아예 새로 작성해야 했다는 게
다행이라면 다행인 일일까요..
요즘 원영적 사고를 배우려고 하는 중이기에, 다행이라고 생각하겠습니다.
지도를 띄우는 것까지는 순조로웠습니다.
이전에도 새로운 SDK로 지도를 나타내본 적 있었기 때문인데요..
// 카카오맵 초기화
private fun initMapView() {
mapView = binding.dialogSchedulePlaceContainer
mapView.start(object : MapLifeCycleCallback() {
override fun onMapDestroy() {
// 지도 API 가 정상적으로 종료될 때 호출됨
}
override fun onMapError(error: Exception) {
// 인증 실패 및 지도 사용 중 에러가 발생할 때 호출됨
}
}, object : KakaoMapReadyCallback() {
override fun onMapReady(map: KakaoMap) {
// 인증 후 API 가 정상적으로 실행될 때 호출됨
kakaoMap = map
setMapContent()
}
override fun getPosition(): LatLng {
return LatLng.from(place.y, place.x)
}
override fun getZoomLevel(): Int {
// 지도 시작 시 확대/축소 줌 레벨 설정
return MapActivity.ZOOM_LEVEL
}
})
}
// 지도 표시
private fun setMapContent() {
binding.dialogSchedulePlaceNameTv.text = place.place_name
binding.dialogSchedulePlaceContainer.visibility = View.VISIBLE
// 지도 위치 조정
val latLng = LatLng.from(place.y, place.x)
// 카메라를 마커의 위치로 이동
kakaoMap?.moveCamera(CameraUpdateFactory.newCenterPosition(latLng, MapActivity.ZOOM_LEVEL))
}
그러나 문제는 언제나 예상치 못하게 찾아오는 법이죠.
나모에서는 유저가 선택한 장소를 표시할 때 마커를 찍어서 나타내는데,
이 마커의 명칭이 원래 MapPoint
였었거든요. 그런데 이번에 보니까 명칭이 아예 Label
로 바뀌었더라구요.
그리고 기존에는 별도의 스타일 지정 없이 MapPOIItem.MarkerType
을 통해 미리 만들어져 있는 스타일을 사용할 수 있었는데
신규 SDK에서는 라벨 스타일을 직접 지정해줘야 했습니다.
선택된 핀과 선택되지 않은 핀은 아래와 같이 표시해 주어야 했습니다.
핀 선택 여부 | 선택 O | 선택 X |
---|---|---|
색상 | 빨간색 | 파란색 |
장소 이름 표시 여부 | O | X |
오른쪽 이미지 같은 느낌입니다
다음은 선택된 장소에 마커를 표시하는 코드가 카카오맵 v1, v2에서 각각 어떤 식으로 작성되었는지를 설명하겠습니다.
선택된 핀과 선택되지 않은 핀의 스타일을 미리 지정해준 코드입니다.
companion object {
fun setPinStyle(isSelected: Boolean): LabelStyle {
if (isSelected) { // 선택된 핀
return LabelStyle.from(
R.drawable.ic_pin_selected
).setTextStyles(20, R.color.black)
}
return LabelStyle.from( // 기본 핀
R.drawable.ic_pin_default
)
}
선택된 핀이라면 빨간 색상으로 표시하고, 장소 이름을 나타냅니다.
기존 코드는 MapPOIItem.MarkerType.BluePin, MapPOIItem.MarkerType.RedPin으로 핀 스타일을 바로 설정할 수 있었기에 자세한 설명은 넘어가겠습니다.
선택된 장소에 핀을 표시해주는 코드입니다.
var mapPoint = MapPoint.mapPointWithGeoCoord(place_y, place_x)
mapView.setMapCenterPointAndZoomLevel(mapPoint, 1, true)
var marker = MapPOIItem()
marker.itemName = place_name
marker.tag = 0
marker.mapPoint = mapPoint
marker.markerType = MapPOIItem.MarkerType.BluePin // 기본 핀
marker.selectedMarkerType = MapPOIItem.MarkerType.RedPin // 선택된 핀
mapView.addPOIItem(marker)
markerType, selectedMarkerType을 통해 선택 시의 마커 스타일을 지정할 수 있습니다.kakaoMap?.labelManager?.layer?.addLabel(LabelOptions.from(latLng).setStyles(MapActivity.setPinStyle(false)))
위의 1번에서 지정한 코드 스타일을 바탕으로 setPinStyle(false)라면 파란 핀을, setPinStyle(true)라면 빨간 핀을 나타냅니다.3. 핀 스타일 변경
장소 검색 결과에서 선택한 장소의 핀 색상을 변경하는 코드입니다.
mapView.selectPOIItem(markerList[position], true)
selectPOIItem
를 통해 핀 선택 여부를 쉽게 나타낼 수 있습니다.markerList[position].changeStyles(LabelStyles.from(setPinStyle(true)))
changeStyles
를 통해 핀의 스타일을 바꿔줄 수 있습니다.3기를 진행하고 iOS 출시를 준비하면서 장소 선택과 관련된 내부 기획이 조금 변경되었습니다.
따라서 지도 SDK를 마이그레이션하는 이번 기회에 이 수정된 기획을 반영하고자 했습니다.
기존에 구현된 로직과 비교해 수정할 부분들은 크게 아래의 세 부분이었습니다.
자, 그럼 기획에 맞게 내부 로직을 바로잡아 봅시다.
장소는 하나만 선택하도록 해야하고, 선택된 장소의 경우 아이템 우측에 체크 표시를 달아줍니다.
// 검색 리스트에서 선택한 장소
mapRVAdapter.setItemClickListener( object : MapRVAdapter.OnItemClickListener {
override fun onClick(position: Int) {
val place : Place = placeList[position]
selectedPlace = place // 선택 장소 설정
val latLng = LatLng.from(place.y, place.x)
//...
// 장소 취소 & 확인 버튼 표시
binding.mapBtnLayout.visibility = View.VISIBLE
// 이전에 선택한 장소 핀 색상은 파란색으로 돌려놓기
if (prevLabel != markerList[position]) {
prevLabel.changeStyles(LabelStyles.from(setPinStyle(false)))
}
prevLabel = markerList[position] // 마커 업데이트
}
})
다른 아이템 클릭 시 이전에 선택된 Label 색상은 돌려놓아야 하기에, prevLabel이라는 변수를 사용합니다.
// 취소 버튼
binding.cancelBtn.setOnClickListener {
// 선택된 핀 다시 파란색으로 표시
prevLabel.changeStyles(LabelStyles.from(setPinStyle(false)))
// 줌 레벨 살짝 낮추기
moveCamera(LatLng.from(uLatitude, uLongitude), ZOOM_LEVEL - 3)
// 아이템 체크 표시 삭제
mapRVAdapter.setSelectedPosition(-1)
// 취소 & 확인 버튼 없애기
binding.mapBtnLayout.visibility = View.GONE
}
취소 버튼 클릭 시 선택된 장소를 해제해 줍니다.
기존에 선택되었던 핀은 다시 파란색으로 돌려놓고, 장소 선택을 취소했으니 줌 레벨을 살짝 낮춰 지도를 축소해 줍니다. 아이템 체크 표시까지 삭제한 뒤에 버튼도 보이지 않게 해주면 변경된 기획 반영 완료입니다.
나모에서 장소를 저장할 때 사용하는 값은 장소명과 x, y 좌표였기 때문에,
위의 정보로 지번 주소와 도로명 주소와 받아와야 했습니다.
찾아보니 좌표로 주소를 알아낼 수가 있더라구요.
위의 자료를 참고해 좌표를 통해 주소 정보를 표시해 주었습니다. 코드는 아래와 같습니다.
/** 좌표로 주소 변환 */
data class ResultCoord2Address(
val documents: List<Document>
)
data class Document(
val address: Address,
val road_address: RoadAddress?
)
data class Address(
val address_name: String, // 전체 지번 주소
)
data class RoadAddress(
val address_name: String, // 전체 도로명 주소
)
private fun setPreLocationItem(placeX: Double, placeY: Double) {
// 카카오 API를 이용해 좌표로 주소 정보 가져오기
val call = kakaoService.getPlaceInfo("KakaoAK $API_KEY", placeX.toString(), placeY.toString())
call.enqueue(object : Callback<ResultCoord2Address> {
override fun onResponse(
call: Call<ResultCoord2Address>,
response: Response<ResultCoord2Address>
) {
if (response.isSuccessful) {
val placeInfo = response.body()?.documents?.firstOrNull()
Log.d("PlaceInfo", placeInfo.toString())
if (placeInfo != null) {
selectedPlace.address_name = placeInfo.address.address_name
selectedPlace.road_address_name = placeInfo.road_address?.address_name.toString()
// 선택된 장소를 리사이클러뷰 아이템에 표시
placeList.clear()
placeList.add(selectedPlace)
setPlaceData()
mapRVAdapter.setSelectedPosition(0) // 첫 번째 아이템에 체크 표시
}
}
}
override fun onFailure(call: Call<ResultCoord2Address>, t: Throwable) {
Log.d("MapActivity", "좌표로 주소 정보 불러오기 실패\n${t.message}")
}
})
}
이렇게 우여곡절 끝에 장소 선택 기능을 잘 구현할 수 있었습니다!
거창한 기능을 구현한 건 아니지만, 기존 SDK가 지원 종료됨에 따라 신규 SDK로 마이그레이션을 진행한 경험이 저에게는 무척 새로웠기에 이렇게 글을 작성해 보았습니다😃
이외에도 새로운 요구사항에 맞게 내부 로직을 변경해가는 과정이 제법 재밌었습니다. 기능을 이것저것 사용해 보면서 신규 카카오맵 SDK와 조금 더 친해진 기분이었달까요..
그렇지만 핀 디자인이 기존과 조금 달라진 부분이 있어 이 점도 수정할 방법을 더 찾아보고자 합니다. (핀 하단에 표시되는 장소 이름이 잘 눈에 띄지 않더라구요)
카카오맵과 벌인 사투의 흔적..
https://github.com/Namo-log/Android/pull/244
변경 전/후 코드에 대한 자세한 정보는 위 PR에서 확인할 수 있습니다.
혹시라도 코틀린으로 카카오맵 v2 지도를 구현하시는 분들께 저의 삽질 이야기가 조금 도움이 되었으면 좋겠네요.
댓글로 남겨주시는 피드백은 언제든지 환영입니다:)
긴 글 읽어주셔서 감사합니다!
공식 문서를 많이 활용했습니다.. 근데 이제 자바로 적힌