Naver_Maps_API_적용을_위한_MapFragment와_MapView_선택 - boostcampwm-2024/and04-Nature-Album GitHub Wiki

💡 Naver Maps API 적용을 위한 MapFragment와 MapView 선택

  • 우리 팀은 Compose와 Naver Maps를 통합하기에 앞서 MapFragmentMapView 중 어떤 것을 사용할지 고민해보았다. 기본적으로 Naver Maps API에서는 지도를 MapFragment를 통해 구현할 것을 권장하고 있으며, 이를 활용하면 Fragment 내에서 지도를 쉽게 사용할 수 있다. 하지만 Compose 프로젝트에서는 Fragment를 Compose로 래핑해야 하므로 MapFragment의 생명주기 관리가 필요하다. 이에 따라 MapFragmentMapView를 비교하고, Compose의 특성과 팀 프로젝트 상황에 맞게 선택을 진행하였다.

💡 MapFragment와 MapView의 선택 기준

  1. MapFragment

    Naver Maps API가 권장하는 방식으로, 지도와 지도 위에 여러 오버레이 기능을 제공하기에 용이하다. Android의 Fragment 생명주기에 따라 자동으로 초기화 및 종료가 관리되므로, 전통적인 Android View 시스템에서는 사용이 간편하다. 하지만 Compose 프로젝트에서 MapFragment를 사용할 경우, AndroidView를 통해 MapFragment를 래핑해 Compose에 삽입해야 하며, 화면을 이동했다가 돌아올 때마다 Fragment 생명주기 관리 문제와 recomposition에 따른 Fragment 중복 생성 문제가 발생할 수 있다. 이러한 문제로 인해 MapFragment의 사용은 Fragment 생명주기를 별도로 관리해야 하므로 복잡성이 증가한다.

  2. MapView

    MapView는 Fragment 없이 뷰 단독으로 지도를 표시할 수 있는 방식이다. MapFragment와 달리 생명주기가 Compose의 재구성과 별개로 관리되기 때문에 DisposableEffect와 같은 Compose의 생명주기 관련 요소와 직접 연결해 제어할 수 있다. 이를 통해 각 화면 전환에 따른 생명주기 호출을 개발자가 직접 지정할 수 있으며, 필요에 따라 MapView를 일관된 ID로 고정하여 중복 생성을 방지할 수 있다. MapView는 생명주기를 직접 관리하는 코드가 추가되지만, Compose와의 호환성과 재사용 측면에서 유리하다.


💡 문제 해결 과정

  • Compose와 Fragment를 함께 사용할 때 생명주기 관리가 필요하다는 점을 고려하여, 팀에서는 MapFragment가 아닌 MapView를 선택하기로 했다. 기존에 다른 팀에서도 Fragment 생명주기를 추가로 관리하지 않을 경우 문제가 발생한다는 경험을 공유했으며, 이와 같은 문제를 사전에 방지하기 위해 생명주기 관리를 직접 제어할 수 있는 MapView를 사용하기로 했다.
  • MapView를 사용할 때는 AndroidView를 통해 Compose에 삽입하며, 생명주기는 DisposableEffectLifecycleObserver를 통해 직접 관리하도록 구성했다. 이러한 방식은 화면 전환 시 MapView가 올바르게 초기화되고 종료되도록 하며, 중복 생성을 방지할 수 있는 구조를 제공한다. 팀에서는 Mapbox의 Compose 라이브러리를 참고하여 MapView 생명주기를 관리하는 코드를 작성했다.

💡 생명주기 관리 코드

Compose 내에서 MapView를 생성하고, 생명주기 관리를 설정하는 코드의 주요 부분은 다음과 같다. MapView를 고정된 ID로 설정하고 LifecycleObserver를 추가하여 Compose의 DisposableEffect 내에서 MapView의 생명주기를 관리하였다.

@Composable
fun MapScreen(modifier: Modifier = Modifier) {
    val context = LocalContext.current
    val lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current

    val mapView = remember {
        MapView(context).apply {
            id = R.id.fixed_map_view // MapView의 고정된 ID 설정
        }
    }

    DisposableEffect(lifecycleOwner) {
        val lifecycle = lifecycleOwner.lifecycle
        val observer = 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 -> Unit
            }
        }
        lifecycle.addObserver(observer)

        onDispose {
            lifecycle.removeObserver(observer)
            mapView.onDestroy()
        }
    }

    AndroidView(factory = { mapView }, modifier = modifier) {
        it.getMapAsync { naverMap ->
            naverMap.minZoom = 10.0
            naverMap.maxZoom = 18.0
        }
    }
}


💡 결론 및 마무리

  • Naver Maps API를 Compose 프로젝트에 통합하기 위해, 초기에 MapViewMapFragment 두 방식을 모두 실험해 보았다. MapFragmentFragmentContainerView로 래핑하여 Compose에서 호스팅하고 내부에서 ViewModel을 통해 동작을 확인했으나, 민주님 팀에서 이미 경험한 것처럼 Compose와 Fragment의 생명주기 불일치로 인해 화면 전환 시 Fragment 중복 생성 등 예상치 못한 문제가 발생할 가능성이 있었다.
  • 이에 비해 MapView는 Compose의 DisposableEffectLifecycleObserver를 통해 MapScreen의 생명주기에 맞춰 직접 관리할 수 있어 Compose의 재구성과 생명주기 관리가 훨씬 용이했다. 이를 통해 MapView를 Composable처럼 다루며 화면 전환 시 올바르게 초기화 및 종료되도록 관리할 수 있었다. 따라서 우리 팀은 생명주기 관리와 Compose와의 호환성이 우수한 MapView를 사용하는 것이 더 적합하다고 결론 내렸다.
  • 이번 결정 과정에서 다른 팀의 피드백과 생명주기 관리의 중요성을 고려한 경험은 프로젝트의 안정성을 높이는 데에 유익했다. 앞으로도 Compose와 기존 View 시스템 간의 통합 방식에 대해 지속적으로 학습하고, 프로젝트의 최적화를 위한 기술적 도전을 이어갈 계획이다.