Kotlin Multiplatform(KMP) 환경에서 플랫폼별 네이티브 SDK를 공통 UI 프레임워크인 Compose Multiplatform으로 래핑하는 과정은 단순히 인터페이스를 맞추는 것 이상의 기술적 고민을 필요로 합니다. 특히 한국 시장에서 필수적인 네이버 지도 SDK를 활용하여 Android와 iOS에서 동일한 선언적 개발 경험을 제공하기 위해 라이브러리를 구축하며 겪은 도전 과제와 트러블슈팅 사례를 정리합니다.
기존의 네이버 지도 SDK는 각 플랫폼의 네이티브 뷰 시스템에 강하게 결합되어 있습니다. Android는 View, iOS는 UIKit 기반의 명령형 API를 사용하므로, KMP 프로젝트의 commonMain에서 이를 직접 제어하기에는 구조적 한계가 명확했습니다. 이를 Compose의 상태 관리 시스템과 연결하여 선언적으로 관리할 수 있는 라이브러리의 필요성을 느껴 개발을 시작하게 되었습니다.
특히 이번 프로젝트는 개인적인 활용을 넘어, 누구나 사용할 수 있는 오픈소스 SDK 라이브러리 형태로 배포하는 첫 시도였습니다. 배포 규격에 맞는 코드 구조 설계부터 Maven Central 배포까지의 과정을 경험하며, 단순한 기능 구현을 넘어 라이브러리로서의 안정성과 확장성에 대해 깊이 고민할 수 있었습니다.
네이버 지도 SDK는 마커나 경로선 같은 오버레이를 추가할 때 객체를 생성하고 직접 지도 인스턴스에 대입하는 명령형 방식을 따릅니다.
NaverMapScope를 설계하고, 컴포저블의 생성과 소멸 시점에 맞춰 DisposableEffect를 통해 네이티브 객체의 등록과 해제가 자동으로 이루어지도록 추상화했습니다.가장 복잡했던 작업 중 하나는 이미지 리소스의 통합 처리였습니다. 특히 iOS에서는 Compose의 Painter 리소스를 네이버 지도 SDK가 이해할 수 있는 UIImage 형태로 변환해야 했습니다.
CanvasDrawScope를 이용해 메모리 상의 ImageBitmap에 이미지를 그린 뒤, 이를 다시 UIImage로 변환하는 브릿지 로직을 구현했습니다. 이 과정에서 플랫폼 간 밀도(Density) 차이와 좌표계 변환을 정교하게 처리했습니다.네이버 지도 SDK는 별도의 전용 Maven 저장소 설정을 요구합니다. 라이브러리 사용자가 프로젝트마다 수동으로 저장소 URL을 추가하는 번거로움을 줄이기 위해 naver-map-compose-gradle-plugin을 별도로 제작했습니다.
개발 과정에서 마주한 주요 문제들과 그 해결 과정을 정리합니다.
현상: 네이티브 SDK를 Kotlin Native로 변환(
cinterop)하여 사용하는 과정에서 공식 문서의 설명과 실제 동작이 다른 경우가 다수 발생했습니다.
- 문제: 특정 API의 리턴 타입이 문서와 다르거나, Objective-C 인터페이스가 Kotlin Native로 변환될 때 예상치 못한 타입으로 매핑되었습니다.
- 대응: 공식 문서에만 의존하지 않고 실제 생성된 라이브러리 바이너리를 직접 분석하고 테스트하며 적절한 타입을 찾아내는 과정을 거쳤습니다.
현상: 초기 개발 단계에서 iOS의 마커 객체가 화면에서 사라진 후에도 메모리에서 해제되지 않는 현상이 발견되었습니다.
- 문제: Kotlin의 Native Interop 과정에서 발생한 강한 참조 고리(Strong Reference Cycle)가 원인이었습니다.
- 대응: 오버레이 객체의
map속성에null을 명시적으로 대입하는 로직을 Compose의onDispose시점에 엄격하게 적용하여 해결했습니다.
현상: 간헐적으로 지도가 표시되지 않거나 초기화 에러가 발생했습니다.
- 문제: 네이버 지도는 뷰가 생성되기 전 클라이언트 ID 초기화가 선행되어야 하는데, 초기화 요청과 렌더링 간의 경합 조건(Race Condition)이 발생했습니다.
- 대응: 초기화 상태를 싱글턴 객체에서 관리하고, 플랫폼별 초기화 로직이 완료된 것을 보장한 뒤 뷰를 노출하도록 실행 순서를 조정했습니다.
현상: iOS에서 위치 오버레이의
iconWidth또는iconHeight를 AUTO로 설정할 경우 런타임 오류가 발생했습니다.
- 문제: iOS 네이버 지도 SDK는 AUTO 크기 상수를 Android와 다른 방식으로 처리합니다. Android에서 통용되는 상수 값을 그대로 넘기면 플랫폼 내부에서 유효하지 않은 값으로 해석되었습니다.
- 대응: iOS 구현부에서 AUTO 값을 별도로 감지하여 플랫폼에 맞는 처리 방식으로 분기하도록 수정했습니다.
현상: Android에서 위치 추적 기능 활성화 시 간헐적으로 위치 업데이트가 시작되지 않거나, 권한 요청 흐름이 정상 동작하지 않는 경우가 있었습니다.
- 문제:
FusedLocationSource의 초기화가 Compose의 컴포지션 이후 지연되어 실행되었고, 권한 요청 결과 콜백과 위치 소스 활성화 사이의 순서가 보장되지 않았습니다.- 대응:
FusedLocationSource를 컴포저블 진입 시점에 즉시 초기화하도록 수정하고, 권한 요청 결과를 처리한 직후 위치 추적을 활성화하는 순서를 명시적으로 보장하도록 개선했습니다.
현상: 위치 오버레이의 방향 화살표(subIcon)가 의도한 위치에 표시되지 않고 어긋나 보이는 문제가 발생했습니다.
- 문제:
subIconAnchor의 기본값이 잘못 설정되어 있어 방향 화살표가 위치 오버레이 아이콘의 하단 중앙이 아닌 엉뚱한 위치를 기준으로 렌더링되었습니다.- 대응:
subIconAnchor의 기본값을CenterBottom으로 명시하여 위치 오버레이 아이콘 하단 중앙에 화살표가 정확히 붙도록 수정했습니다.
첫 오픈소스 SDK 배포라는 도전 과정에서 플랫폼별 네이티브 SDK의 내부 동작 원리를 깊이 이해할 수 있었습니다. 특히 KMP 환경에서 복잡한 리소스 변환과 메모리 관리, 그리고 문서와 실제 동작 간의 괴리를 극복하는 과정은 매우 값진 경험이 되었습니다.
향후 로드맵:
GitHub: kmp-naver-map
Maintainer: Junho Cho (hiwhwnsgh@gmail.com)