Kakap map jetpack compose

[FAQ] 지도/로컬 API 문의 전 꼭 읽어 주세요.
https://devtalk.kakao.com/t/faq-api/125610

아래 코드와 같이 화면을 구현해 보았으나, 화면에 지도가 표시 되지 않습니다. 무엇을 더 확인 해야 할까요???

@Composable
fun KakaoMapView(viewModel: MainViewModel = koinViewModel()) {

val context = LocalContext.current
val mapView = rememberMapView(context = context)

var _lat = viewModel.lat.observeAsState(0.0)
var _lon = viewModel.lon.observeAsState(0.0)
var lat = _lat.value
var lon = _lon.value

Column ( modifier = Modifier.fillMaxSize()){
    Row( modifier = Modifier.fillMaxSize()) {
        IconButton( onClick = {
            viewModel.setLocation(context)
        }) {
            Icon(Icons.Default.MyLocation, contentDescription = "Search My Location")
        }
    }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .padding(3.dp)
    ) {
        AndroidView(
            factory = {
                mapView
            },
        )
    }
}

}

@Composable
fun rememberMapView(
context: Context,
): MapView {
val mapView = remember {
MapView(context).also { mapView →
mapView.start(
object : MapLifeCycleCallback() {
override fun onMapDestroy() = Unit
override fun onMapError(e: Exception?) = Unit
override fun onMapResumed() = Unit
},

            object : KakaoMapReadyCallback() {
                override fun onMapReady(map: KakaoMap) = Unit
            }
        )
    }
}

return mapView

}

--------------------실행시 로그 ----
---------------------------- PROCESS ENDED (24890) for package com.example.pill2024 ----------------------------
---------------------------- PROCESS STARTED (29255) for package com.example.pill2024 ----------------------------
00:38:19.162 D —> RequestHeader(https://dapi.kakao.com/v2/maps/vector/auth) {Accept=[application/json], Authorization=[KakaoAK 9ec46f9124e7e98961a25530bc05c447], KA=[mapSdk/2.12.8 os/android-34 lang/ko-KR origin/cLtWB/yTFq5QbK1WamQnRbOVofI= device/SM-S918N android_pkg/com.example.pill2024]}
00:38:19.433 V ← {null=[HTTP/1.1 200 OK], Connection=[keep-alive], Content-Length=[0], Date=[Tue, 29 Oct 2024 15:38:19 GMT], strict-transport-security=[max-age=31536000; includeSubDomains], X-Android-Received-Millis=[1730216299433], X-Android-Response-Source=[NETWORK 200], X-Android-Selected-Protocol=[http/1.1], X-Android-Sent-Millis=[1730216299365], X-Request-Id=[28033a0c521984316fda2c8c9afa0974]}

화면이 나타나지 않습니다. 다른 걸 수정해야 할까요???

  1. 위에 올려주신 로그가 지도를 실행했을 때 나오는 로그의 전부인가요?

  2. 지도를 실행했을 때, onMapReady() 함수로 호출이 불리는가요?

  3. 지도가 어떤 상태로 안나오는지 확인을 위해, 지도가 안나오는 부분에 대한 캡쳐화면 첨부 부탁 드립니다.

  1. 로그는 K3f 로 필터링 했습니다.

10:48:39.622 D —> RequestHeader(https://dapi.kakao.com/v2/maps/vector/auth) {Accept=[application/json], Authorization=[KakaoAK 9ec46f9124e7e98961a25530bc05c447], KA=[mapSdk/2.12.8 os/android-34 lang/ko-KR origin/cLtWB/yTFq5QbK1WamQnRbOVofI= device/SM-S918N android_pkg/com.example.pill2024]}
10:48:40.012 V ← {null=[HTTP/1.1 200 OK], Connection=[keep-alive], Content-Length=[0], Date=[Wed, 30 Oct 2024 01:48:39 GMT], strict-transport-security=[max-age=31536000; includeSubDomains], X-Android-Received-Millis=[1730252920011], X-Android-Response-Source=[NETWORK 200], X-Android-Selected-Protocol=[http/1.1], X-Android-Sent-Millis=[1730252919913], X-Request-Id=[7223dddbdb8e584aa35e72b17c5d352f]}
10:49:02.544 D onDetachedFromWindow(isFinishing=false)
10:49:02.659 D —> RequestHeader(https://dapi.kakao.com/v2/maps/vector/auth) {Accept=[application/json], Authorization=[KakaoAK 9ec46f9124e7e98961a25530bc05c447], KA=[mapSdk/2.12.8 os/android-34 lang/ko-KR origin/cLtWB/yTFq5QbK1WamQnRbOVofI= device/SM-S918N android_pkg/com.example.pill2024]}
10:49:02.718 V ← {null=[HTTP/1.1 200 OK], Connection=[keep-alive], Content-Length=[0], Date=[Wed, 30 Oct 2024 01:49:02 GMT], strict-transport-security=[max-age=31536000; includeSubDomains], X-Android-Received-Millis=[1730252942717], X-Android-Response-Source=[NETWORK 200], X-Android-Selected-Protocol=[http/1.1], X-Android-Sent-Millis=[1730252942662], X-Request-Id=[52f3f8744bccf6a283bdd0a6ac70a9d8]}

  1. 무슨 말씀 일까요???
    다른 질의 내용에 있는 코드을 옮겨서 해 보는 중입니다.

  2. 이미지는 첨부해 보겠습니다.

  3. 앱 등록할 때 혹시 package 이름을 변경해서 다시 등록하였을 때 문제가 될까요?

올려주신 로그를 보니, 인증을 시도한 로그만 있고 중간에 onDetachedFromWindow() 가 불리면서 MapView 자체가 뷰 구조에서 없어진걸로 보여집니다. MapView 가 제대로 표시되고 있는게 맞는지 확인 부탁드립니다.

음 … 예제 소스 코드을 다시 복사해서 map 은 그려 집니다.

다음 궁금한 것은 위에 코드와 같이 해서 지도을 그린 다음… camera 위치를 조정 하는 방법은 어떻게 될까요???
아래 코드 처럼 onMapReady 함수을 밖으로 호출 하게 해서 해 보아도 잘 안 되네요.

미리 감사드립니다.

import android.content.Context
import android.util.Log
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MyLocation
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocal
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.example.pill2024.viewModels.MainViewModel
import com.kakao.vectormap.KakaoMap
import com.kakao.vectormap.KakaoMapReadyCallback
import com.kakao.vectormap.LatLng

import com.kakao.vectormap.MapLifeCycleCallback
import com.kakao.vectormap.MapView
import com.kakao.vectormap.camera.CameraPosition
import com.kakao.vectormap.camera.CameraUpdate
import com.kakao.vectormap.camera.CameraUpdateFactory
import com.ramcosta.composedestinations.annotation.Destination
import org.koin.androidx.compose.koinViewModel
import kotlin.also

@Destination
@Composable
fun KakaoMap(
viewModel: MainViewModel = koinViewModel()
) {
val context = LocalContext.current
var _lat = viewModel.lat.observeAsState(0.0)
var _lon = viewModel.lon.observeAsState(0.0)
var lat = _lat.value
var lon = _lon.value
val mapView = rememberMapView(context = context, onMapReady = {
viewModel.setLocation(context)
Log.e(“”,“onMapReady … lat/lon: ${lat} ${lon}”)
val latLon = LatLng.from(lat, lon)
val cameraUpdate = CameraUpdateFactory.newCenterPosition(latLon, 10)
it.moveCamera(cameraUpdate)
})

Column ( modifier = Modifier.fillMaxSize()) {
    Row(modifier = Modifier.fillMaxWidth()) {
        IconButton(onClick = {
            viewModel.setLocation(context)
        }) {
            Icon(Icons.Default.MyLocation, contentDescription = "Search My Location")
        }
    }
    Box(
        modifier = Modifier
            .fillMaxSize()
            .padding(3.dp)
    ) {
        AndroidView(
            factory = {
                mapView
            },
        )
    }
}

}

@Composable
fun rememberMapView(
context: Context,
onMapReady: (KakaoMap) → Unit
): MapView {
val mapView = remember {
MapView(context).also { mapView →
mapView.start(
object : MapLifeCycleCallback() {
override fun onMapDestroy() = Unit
override fun onMapError(e: Exception?) = Unit
override fun onMapResumed() = Unit
},

            object : KakaoMapReadyCallback() {
                override fun onMapReady(map: KakaoMap) {
                    onMapReady(map)
                }
            }
        )
    }
}

return mapView

}

우선 위에 올려주신 코드만 놓고 보면, onMapReady() 함수는 정상적으로 실행이 되시나요? (로그가 잘 찍힘 등)

그렇다면, 아래의 코드가 원하는 위치로 카메라 이동이 안된다는 질문이실 거 같은데, 좌표가 잘 들어갔는지 Log.e(“”,“onMapReady … lat/lon: ${lat} ${lon}”) 으로 로그를 찍으셨던 좌표값 공유 부탁 드립니다.

val cameraUpdate = CameraUpdateFactory.newCenterPosition(latLon, 10)
it.moveCamera(cameraUpdate)

저것이 onMapready 상태 일 때는 좌표가 0.0, 0.0 이였는 데, gps 수신 되고 나면 37.5236062 126.8916459 이 되는 데…
아마도 이때는 onMapReady 가 끝난 상태 일 겁니다.

그럼 그 이후에는 카메라 이동을 할 수 없을 까요???

onMapReady() 호출이 불리곤 난 후에는, onMapReady 의 파라미터로 들어온 KakaoMap 객체를 가지고 카메라 관련 함수를 호출하시면 되십니다. 즉, gps 수신 되고 나서 제대로 된 좌표가 들어오면 그때 카메라 함수를 호출하시면 되십니다.

말씀하신 대로 onMapReady() 일 때는 좌표가 0.0, 0.0 이라서, 지도 이동이 제대로 안된 것 같습니다.

lodlabel 을 클릭 했을 때 다른 호출을 해 보고 싶은 데 … 예제을 혹시 알 수 있을 까요???
아래 코드에 어떻게 추가 하면 좋을까요???

fun KaKaoMap(searchKeyList: List, centerPosition: LatLng, isHome: Boolean) {
val context = LocalContext.current
val mapView = remember {MapView(context)}

Column ( modifier = Modifier.fillMaxSize()) {
    Box(
        modifier = Modifier
            .fillMaxSize()
            .padding(3.dp)
    ) {
        AndroidView(
            modifier = Modifier.fillMaxSize(), // AndroidView의 높이 임의 설정
            factory = { context ->
                mapView.apply {
                    mapView.start(
                        object : MapLifeCycleCallback() {
                            // 지도 생명 주기 콜백: 지도가 파괴될 때 호출
                            override fun onMapDestroy() {
                                // 필자가 직접 만든 Toast생성 함수
                                //makeToast(context = context, message = "지도를 불러오는데 실패했습니다.")
                            }

                            // 지도 생명 주기 콜백: 지도 로딩 중 에러가 발생했을 때 호출
                            override fun onMapError(exception: Exception?) {
                                // 필자가 직접 만든 Toast생성 함수
                                //makeToast(context = context, message = "지도를 불러오는 중 알 수 없는 에러가 발생했습니다.\n onMapError: $exception")
                            }
                        },
                        object : KakaoMapReadyCallback() {
                            // KakaoMap이 준비되었을 때 호출
                            @SuppressLint("UseCompatLoadingForDrawables")
                            override fun onMapReady(kakaoMap: KakaoMap) {
                                // 카메라를 (locationY, locationX) 위치로 이동시키는 업데이트 생성
                                val cameraUpdate = CameraUpdateFactory.newCenterPosition(centerPosition)

                                // 지도에 표시할 라벨의 스타일 설정
                                var style = kakaoMap.labelManager?.addLabelStyles(LabelStyles.from(LabelStyle.from(R.drawable.ic_home_v1)))
                                if (!isHome) {
                                    style = kakaoMap.labelManager?.addLabelStyles(LabelStyles.from(LabelStyle.from(R.drawable.ic_medic_v1)))
                                }
                                Log.e("", "center ${centerPosition.latitude} ${centerPosition.longitude}")
                                // 라벨 옵션을 설정하고 위치와 스타일을 적용
                                val options = LabelOptions.from(centerPosition).setStyles(style)

                                // KakaoMap의 labelManager에서 레이어를 가져옴
                                val layer = kakaoMap.labelManager?.layer

                                // 카메라를 지정된 위치로 이동
                                kakaoMap.moveCamera(cameraUpdate)

                                // 지도에 라벨을 추가
                                layer?.addLabel(options)
                                if (isHome) {
                                    searchKeyList.forEach { item ->
                                        Log.e(
                                            "",
                                            "forEach ... ${item.placeName} ${item.roadAddressName} ${item.y} ${item.x}"
                                        )
                                        val lat = item.y.toDouble()
                                        val lon = item.x.toDouble()
                                        val style = LabelStyles.from(
                                            LabelStyle.from(R.drawable.ic_medic_v1)
                                                .setTextStyles(30, 0x000000)
                                        )
                                        val labelText =
                                            LabelTextBuilder().setTexts(item.placeName)
                                        val options = LabelOptions.from(LatLng.from(lat, lon))
                                            .setClickable(true)
                                            .setStyles(style).setTexts(labelText)
                                        val lodLayer = kakaoMap.labelManager?.lodLayer
                                        lodLayer?.addLodLabel(options)
                                    }
                                }
                            }

                            override fun getPosition(): LatLng {
                                // 현재 위치를 반환
                                return centerPosition
                            }
                        },
                    )
                }
            },
        )
    }
}

}

LodLabel 의 클릭 이벤트 등록은 아래와 같이 하시면 됩니다.
다른 이벤트 리스너 등록도 아래와 같이 비슷하게 하시면 됩니다.

kakaoMap.setOnLodLabelClickListener(new KakaoMap.OnLodLabelClickListener() {
    @Override
    public boolean onLodLabelClicked(KakaoMap kakaoMap, LodLabelLayer layer, LodLabel label) {
        // 클릭 된 LodLabel 의 이벤트가 들어옴. 
        return false;
    }
});