본문 바로가기

카테고리 없음

Android 네이버 지도 연동 (Jetpack Compose)

네이버 지도 연동, 다음 지도 연동 등 많이 해봤지만 다 xml 위에 해봤었지 

이번엔 Jetpack Compose 로 개발하는 앱에 네이버 지도 연동해보려 한다. 

1. 네이버 클라우드 플랫폼의 지도에 어플리케이션 등록한다

(1) Menu - Service - Maps 들어간다
(2) Application 등록한다 
 나는 Dynamic Map, GeoCoding, Reverse Geocoding 선택함.

Android 앱 패키지 이름도 등록한 후에 확인 누르면 끝 

 

▼ 공식 문서 참고하였음
시작하기 · 네이버 지도 안드로이드 SDK

2. Gradle 의존성 추가

아래 코드를 libs.versions.toml 에 추가해준다
[versions]
naverMap = "3.19.1"

[libraries]
naver-map-sdk = { group = "com.naver.maps", name = "map-sdk", version.ref = "naverMap" }​

 


app수준의 build.gradle.kts 에 아래 코드 추가
dependencies {
    // ... 기존 것들 ...
    // Naver Map
    implementation("com.naver.maps:map-sdk:3.23.3")
}​

저장소 추가 필요 — 루트 settings.gradle.kts에 네이버 maven 저장소 등록:
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
       maven("https://repository.map.naver.com/archive/maven")
    }
}​


 

참고로 공식문서에 따르면 클라이언트 ID 지정할 때 2가지 방식이 있다.
1. AndroidManifest.xml에 지정

2. API를 호출해 지정

나는 API를 호출해 지정하는 방식을 사용해보겠다. ( AndroidManifest를 수정하지 않다고 된다고함) 

 

 

3. API 키를 안전하게 보관 (하드코딩 금지)

(1) local.properties에 추가 (이 파일은 이미 gitignore 되어있어서 안전)
NCP_KEY_ID=발급받은_클라이언트_ID​

(2) app수준의 build.gradle.kts 에서 local.properties의 NCP_KEY_ID값을 읽어서 AndroidManifest.xml 에 주입
defaultConfig {
        val localProperties = Properties().apply {
            load(rootProject.file("local.properties").inputStream())
        }
        
       anifestPlaceholders["NCP_KEY_ID"] = localProperties.getProperty("NCP_KEY_ID") ?: ""
        buildConfigField("String", "NCP_KEY_ID", "\"${localProperties.getProperty("NCP_KEY_ID")}\"")

 

import도 해줘야함.

 

4. HereLogApp.kt — 코드로 직접 클라이언트 지정 (핵심)

@HiltAndroidApp
class HereLogApp : Application() {
    override fun onCreate() {
        super.onCreate()
        NaverMapSdk.getInstance(this).client =
            NaverMapSdk.NcpKeyClient(BuildConfig.NCP_KEY_ID)
    }
}

 

5. 지도 컴포넌트 만들기

NaverMapView.kt  파일 생성하여 아래와 같이 코드작성
package com.psg.herelog.ui.main

import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.compose.LocalLifecycleOwner
import com.naver.maps.map.MapView
import com.naver.maps.map.NaverMap

@Composable
fun NaverMapView(
    modifier: Modifier = Modifier,
    onMapReady: (NaverMap) -> Unit = {}
){
    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current
    val mapView = remember { MapView(context) } // 아래에서 context 수정 예쩡

    // MapView는 Activity/Fragment 생명주기에 맞춰 onCreate/onResume/onPause/onDestroy를 직접 호출해줘야 함
    DisposableEffect(lifecycleOwner) {
        val lifecycleObserver = LifecycleEventObserver{ _, event ->
            when (event){
                Lifecycle.Event.ON_CREATE -> mapView.onCreate(null)
                Lifecycle.Event.ON_START -> mapView.onStart()
                Lifecycle.Event.ON_RESUME -> mapView.onResume()
                Lifecycle.Event.ON_PAUSE -> mapView.onPause()
                Lifecycle.Event.ON_STOP -> mapView.onStop()
                Lifecycle.Event.ON_DESTROY -> mapView.onDestroy()
                else -> {}
            }
        }
        lifecycleOwner.lifecycle.addObserver(lifecycleObserver)

        onDispose {
            lifecycleOwner.lifecycle.removeObserver(lifecycleObserver)
        }
    }

    AndroidView(
        modifier = modifier,
        factory = {
            mapView.apply {
                onCreate(null)
                getMapAsync { naverMap ->
                    onMapReady(naverMap)
                }
            }
        }
    )
}​


MainScreen.kt 파일에서 naver지도 넣기
// 2. 순수 UI 담당 (Preview 기능)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun MainScreenContent(
    uiState: MainUiState,
    onSearchQueryChange: (String) -> Unit,
    onFilterClick: () -> Unit,
    onFilterDismiss: () -> Unit
){
    Box(modifier = Modifier.fillMaxSize()){
        NaverMapView(
            modifier = Modifier.fillMaxSize(),
            onMapReady = {naverMap ->

            }
        )

        Column(
            modifier = Modifier
                .padding(16.dp)
        ) {


        }

        // 상단 검색창 + 필터 버튼
        Row(
            modifier = Modifier
                .align(Alignment.TopCenter)
                .fillMaxWidth()
                .padding(16.dp),
            verticalAlignment = Alignment.CenterVertically
        ) {
            OutlinedTextField(
                value = uiState.searchQuery,
                onValueChange = onSearchQueryChange,
                placeholder = {Text("상호명, 지번으로 검색")},
                leadingIcon = {Icon(Icons.Default.Search, contentDescription = "검색")},
                singleLine = true,
                modifier = Modifier
                    .weight(1f)
                    .background(MaterialTheme.colorScheme.surface, RoundedCornerShape(12.dp))
            )

            Spacer(modifier = Modifier.height(8.dp))

            IconButton(
                onClick = onFilterClick,
                modifier = Modifier.background(MaterialTheme.colorScheme.surface, RoundedCornerShape(12.dp))
            ) {
                Icon(Icons.Default.Build, contentDescription = "필터")
            }
        }
    }

    // 필터 바텀시트
    if(uiState.showFilterSheet){
        ModalBottomSheet(onDismissRequest = onFilterDismiss) {
            Column(modifier = Modifier.padding(24.dp)) {
                Text("필터", style = MaterialTheme.typography.titleLarge)
                Spacer(modifier = Modifier.height(16.dp))
                Text("카테고리, 평점 등 필터 옵션 (추후 구현)")
                Spacer(modifier = Modifier.height(24.dp))
            }
        }
    }
}​

 

 

 

  • 인증을 Manifest meta-data가 아니라 HereLogApp.onCreate()에서 코드로 직접 설정하는 방식 사용
  • BuildConfig.NCP_KEY_ID로 안전하게 값 전달 (하드코딩 없음) - 보안 굿
  • 변수명을 NCP_KEY_ID 하나로 통일해서 local.properties ↔ build.gradle.kts ↔ 코드 전부 일치시킴

 

 

▼ 최종적으로 잘 뜸을 볼 수 있다 

 

사실 처음엔 익숙한 AndroidManifest.xml 에 meta써서 작성하는 방식을 썼다가 401에러가 자꾸 떠서.
직접 api호출하느나 식으로 변경해보았다. (처음 써봄) 
이게 더 나은 것 같기도 하다. 잘 떠서 다행이오 

다들 좋은 수요일되십쇼