-
Notifications
You must be signed in to change notification settings - Fork 0
2. Naver Map 활용
Naver Map은 맵을 터치하는 것으로 여러 기능과 유저 입력의 상호 작용을 제공합니다.
이러한 기본 기능은 Naver Map SDK 시작하기와 Naver Map Overview에 자세하게 나와있습니다. 이것을 참고하여 프로젝트에 적용하였습니다.
Naver Map SDK는 지도가 로딩 될 때마다 한번 호출하게 됩니다.
Naver Map SDK는 호출 제한이 있어서 많이 호출할 수록 불리하고, 그 만큼 사용자의 데이터 또한 많이 사용되기 때문에 적게 호출할 수록 좋습니다.
따라서 저는 Naver Map을 관리하는 Fragment를 사용하여 맵의 호출 횟수를 최소화 하려 합니다.
지도를 사용할 때에는 MapView와 mapFragment 두 가지 방법이 있습니다.
MapView는 사용하는 Fragment, 또는 Activity의 수명 주기와 함께 MapView의 주기를 맞춰야 하기 때문에
문서에서는 미리 맞춰진 MapFragment를 사용할 것을 권장합니다.
MainFragment.kt MapFragment를 Fragment 내에서 사용하는 방법.(Fragment의 자식 Fragment로 생성)
val mapFragment =
childFragmentManager.findFragmentById(R.id.fragment_main_map_container) as MapFragment?
?: MapFragment.newInstance().also { mapFragment ->
childFragmentManager.commit {
add(R.id.fragment_main_map_container, mapFragment)
}
}
mapFragment.getMapAsync(this)위 처럼 Fragment 내의 자식 Fragment로 생성하였습니다. Fragment의 장점은 재사용이 가능하다는 점인데,
이 Fragment를 재사용하여 SDK의 호출 횟수를 최소로 하려고 합니다.
따라서 저는 이 App을 단일 Activity로 구성하고, Fragment를 갈아 끼움으로서 Fragment BackStack을 관리하는 쪽이 효율적이라고 생각했습니다.
Map이 준비가 되면 Callback을 받을 수 있습니다.
Callback은 Interface로 되어있어 이 내용물을 override 하여 구현해야 합니다. 저는 MainFragment.kt에 Callback을 상속 받아 이를 구현하였습니다.
@AndroidEntryPoint
class MainFragment: Fragment() ,OnMapReadyCallback {
private var _binding: FragmentMainBinding? = null
private val binding: FragmentMainBinding
get() = _binding!!
//viewModel
private val viewModel: MainViewModel by viewModels(ownerProducer = { requireActivity() })
//naverMap
private var map: NaverMap? = null
//...중략
@UiThread
override fun onMapReady(naverMap: NaverMap) {
Log.d("MainFragment", "on map ready")
this.map = naverMap
//...중략
}위와 같이 map이 준비가 되면 그 객체를 클래스 필드로 저장하여 사용하도록 하였습니다.
이 앱에서는 두 가지의 위치 데이터가 존재합니다. GPS로 받아온 사용자의 현재 위치와 사용자가 임의로 세팅해 놓은 위치 이 두 가지 입니다.
GPS 위치와 사용자가 검색을 원하는 위치는 같을 수도 있고, 다를 수도 있습니다.
GPS는 단말기의 실제 위치이지만, 실제 위치가 사용자가 음식점 검색을 원하는 위치일 지, 아닐 지는 모릅니다.
따라서 LiveData 변수를 이용하여 두 위치를 변수로 저장하였습니다.
@HiltViewModel
class MainViewModel @Inject constructor(private val retrofitRepository: RetrofitRepository): ViewModel() {
//지금 가장 앞에 나와있는, 유저와 상호작용 하고 있는 fragment
private val _frontFragment = MutableLiveData<Int>()
val frontFragment: LiveData<Int>
get() = _frontFragment
//gps 위치
private val _userPosition = MutableLiveData<LatLng>()
val userPosition: LiveData<LatLng>
get() = _userPosition
//사용자가 검색을 원하는 위치
private val _selectedPosition = MutableLiveData<LatLng>()
val selectedPosition: LiveData<LatLng>
get() = _selectedPosition
//...중략
}MVVM 패턴을 준수하기 위해서 Map을 관리하는 MainFragment.kt는 사용자가 원하는 위치 데이터를 관찰하고 있어야 하고, 데이터가 변경이 될 때 마다 이를 UI에 반영해야 합니다. UI에 반영한다는 것은 사용자가 원하는 위치를 MapFragment가 지도로서 보여 주어야 합니다.
따라서 Map은 위의 _selectedPosition을 observe하여 그 때 마다 Map의 카메라를 이동 시켜 정확한 지도를 사용자에게 보여 줍니다.
viewModel.selectedPosition.observe(viewLifecycleOwner) {
moveCamera(it)
marker.position = it
marker.map = map
viewModel.setSelectedAddress(it)
}//위의 moveCamera() 메소드
private fun moveCamera(latLng: LatLng) {
val cameraUpdate = CameraUpdate.scrollTo(latLng).animate(CameraAnimation.Easing, 500)
map?.moveCamera(cameraUpdate)
}UI를 지도 위에 올릴 경우 Touch Event를 처리해야 하지만, 다행히 Naver Map은 Touch Event를 Intercept 하지 않아 지도 위에 UI를 올려 놓아도 UI가 잘 반응합니다.
그래서 위 1. 기본 세팅에서 말한 바와 같이, 단일 Activity로 구성하고, 그 위에 지도 Fragment를 올린 다음, 그 위에 UI를 올리고, UI Fragment를 교체하며 페이지를 나눴습니다.

Fragment의 생명 주기에서 onPause와 onStop의 조건은 Fragment 전체가 다른 Fragment에게 전부 가려질 때 발생합니다.
위의 예시에서 UI Fragment는 Map Fragment를 일부만 가리게 됩니다.
따라서 UI Fragment의 크기를 단말기 화면 전체로 하여도, UI를 제외한 부분을 투명하게 만든다면, Map Fragment도 정상적으로 작동하게 됩니다.
//다음 UI 프래그먼트를 부모 프래그먼트 BackStack에 쌓기
binding.fragmentLocationSearch.fragmentLocationButtonSearch -> {
Log.d("LocationButtonFragment", "search view clicked")
parentFragmentManager.commit {
replace(
R.id.fragment_main_ui_container,
SearchFragment(),
FragmentTag.FRAGMENT_SEARCH.tag
)
addToBackStack(FragmentTag.FRAGMENT_SEARCH.tag)
}
}결과. 지도 Fragment는 그대로인 채 UI만 변경
