본문 바로가기

TIL

[TIL] Kotlin Naver Map API

Topic = NaverMap의 Google Geocoder를 통해 지도 위치의 주소가져오기

 

 


 

 

 

 

 

 

Naver Map API 사용하기 [ 1 ] Cloud 로 이동

 

 

 

NAVER CLOUD PLATFORM

cloud computing services for corporations, IaaS, PaaS, SaaS, with Global region and Security Technology Certification

www.ncloud.com

  • 한글이어서 그런 것도 있지만 한국 기업이기 때문에 설명이 친절한 편이어서 Client ID까지 금방 편하게 생성 가능하다.

 

NaverMap Service 종류

  • 각 기능에 대해서도 콘솔에 자세하게 설명이 되어져 있지만 간략하게 설명
    • Mobile Dynamic Map 
      • 기본적으로 볼 수 있는 단순한 지도의 기능 및 Android Map SDK 를 제공하여 다양한 기능을 편리하게 사용할 수 있다.
    • Static Map API
      • REST 형식을 따르는 네이버 지도 인터페이스로, HTTP GET 메소드를 이용하여 네이버 지도 이미지 받을 수 있다
    • Direction 5, 15
      • 경로 찾아주는 네이버 지도 기능 지원 5, 15 는 각각 경유지를 입력할 수 있는 갯수를 나타낸다
    • Geocoding
      • REST 형식을 따르는 네이버 지도 인터페이스로 지번 도로명을 좌표값으로 반환 받는 API
      • 이번 프로젝트에서는 시간 관계로 아쉽지만 사용하지 못할 것 같다.
    • Reverse GeoCoding
      • 조립은 분해의 역순 - 좌표를 통해 주소 정보를 가져올 수 있다.

 

 

 

 

 

사용등록이 정상적으로 완료되면 Manifest에 Client ID를 등록 및 설정 등으로 사전 작업을 해보자

 

 

build.gradle, Manifest 설정

 

Manifest

<manifest ... 

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

       
    <application
        ...
        >
        
        <meta-data
            android:name="com.naver.maps.map.CLIENT_ID"
            android:value="NAVER_MAP_CLIENT_ID"/>
  • 사용자 위치 정보 권한 추가
  • Client ID metaData에 추가

 

Project - build.gradle

allprojects {
    repositories {
        google()
        mavenCentral()
        maven("https://naver.jfrog.io/artifactory/maven/")
    }
}

 

 

Module - build.gradle

dependencies {
    ...
    
    // 네이버 Maps SDK
    implementation("com.naver.maps:map-sdk:3.17.0")
    
    // FusedLocationProviderClient 사용하기 위해 필요 - 21.0.1 이상
    implementation("com.google.android.gms:play-services-location:21.0.1")
    
}

build가 정상적으로 되지 않는 현상이 발생할 수 있다 아래에 오류코드를 참고

 

 

 

❌ 에러 - Project 단위에 build.gradle 내부에 maven(https:naver ~ ) 을 넣어주고 SDK 를 넣고 build를 해도 정상적으로 다운로드가 안되는 현상

에러 내용 

> Build was configured to prefer settings repositories over project repositories but repository 'maven' was added by build file 'app\build.gradle'

  • settings.gradle에 이미 maven이 존재하는 등 setting.gradle을 prefer 하도록 구성이 되어져 있지만 project 단위의 build.gradle에 의해 maven을 추가하기 때문에 생기는 오류다

 

해결방법

  • settings.gradle의 dependencyResolutionManagement 내부에 추가해주기.

 

 

 

MapView - 기본적인 지도 열기

 

Xml

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/map_fragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:name="com.naver.maps.map.MapFragment"
        />

 

  • ID 값은 개발자 임의대로 설정해주어도 되지만 MapFragment를 별도로 생성해주지 않는 이상 name 값은 위 코드 그대로 사용하여야 한다.
    • android:name 속성을 사용하여 com.naver.maps.map.MapFragment를 지정 함으로 해당 View를 MapFragment 클래스의 인스턴스로 채우게 된다. 

 

Fragment

class MapViewFragment : Fragment() {

    private var _binding : FragmentMapViewBinding? = null
    private val binding get() = _binding!!

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentMapViewBinding.inflate(inflater,container,false)

        return binding.root
    }
}
  • Fragment에서 별도로 코드 작성을 하지 않아도 자동으로 NaverMap 클래스를 통해 기본적인 지도를 화면에 띄우는 것이 가능하다.

 

 

  • 기본적인 MapView은 기본적인 지도의 역할만 할 뿐 사용자와 상호작용 등의 기능은 지원하지 않는다. 부가적인 기능들을 추가하기 위해서는 MapFragment 를 사용해 NaverMap 객체를 생성해보자,
  • MapFragment, MapView는 커스텀하여 직접 생성할 수 있으나, NaverMap 객체는 오직 콜백 메서드를 통해서만 생성할 수 있다

 

 

 

NaverMap 객체 생성하기

 

 

XML

  • MapFragment 위에 유저와 상호작용할 수 있는 ViewItem을 사용하기 위해 FrameLayout으로 감싸준다.
  •  ✅ name 값은 반드시 com.naver.maps.map.MapFragment 를 사용해주도록 하자
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/bg_state_img_reservation_white"
    tools:context=".view.MapViewFragment">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        
        <androidx.fragment.app.FragmentContainerView
            android:id="@+id/map_fragment"
            android:name="com.naver.maps.map.MapFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />


        <ImageView
            android:id="@+id/map_btn_back"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_marginStart="16dp"
            android:padding="12dp"
            android:src="@drawable/clamp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <ImageView
            android:id="@+id/map_btn_complete"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_marginEnd="16dp"
            android:padding="12dp"
            android:src="@drawable/clamp_right"
            android:visibility="visible"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/map_tv_address"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_marginStart="16dp"
            android:layout_marginTop="650dp"
            android:layout_marginEnd="16dp"
            android:layout_marginBottom="110dp"
            android:background="@color/gray900"
            android:gravity="center"
            android:text="교환 장소로 원하는 위치를 터치 해주세요"
            android:textSize="18sp"
            android:textStyle="bold"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/map_btn_back" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

 

 

 

MapViewFragment.kt

class MapViewFragment : Fragment(), OnMapReadyCallback {

    companion object {
        private const val LOCATION_PERMISSION_REQUEST_CODE = 1000
    }

    private var _binding: FragmentMapViewBinding? = null
    private val binding get() = _binding!!

    private lateinit var locationSource: FusedLocationSource

    private lateinit var naverMap: NaverMap

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        locationSource = FusedLocationSource(this, LOCATION_PERMISSION_REQUEST_CODE)
    }
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        // NaverMap 인스턴스 생성
        val fm = childFragmentManager
        val mapFragment = fm.findFragmentById(R.id.map_fragment) as MapFragment?
            ?: MapFragment.newInstance().also {
                fm.beginTransaction().add(R.id.map_fragment, it).commit()
            }
        mapFragment.getMapAsync(this)
    }
    
    
    
    
    
    override fun onMapReady(naverMap: NaverMap) {
        this.naverMap = naverMap
    }
}
  • 먼저 Fragment() 상속 구문 옆에 OnMapReadyCallback 인터페이스를 정의해주자, 정의 해주면 Implementary member를 통해 onMapReady를 생성해주고 권한요청 및, 콜백 메서드를 호출 해보자.
  • onViewCreated 에서 콜백 메서드를 정의 해준다 이때 주의해야할 점은 Fragment에서 MapFragment 콜백 메서드 사용 시 childFragmentManager를 사용해야 한다.
    • childFragmentManager를 사용하지 않으면 초기 생성 시에는 MapFragment 가 정상적으로 초기화가 되지만 해당 View Fragment를 나갔다가 다시 MapFragment를 사용하려고 하면 설정이 정상적으로 되지 않는다.

 

 


 

 

onMapReady - Marker 옵션

 

 

Marker 옵션 [ 1 ] - Marker 색상 지정

 

    private val marker = Marker()
    
    override fun onMapReady(naverMap: NaverMap) {
    
        ...
        
        // 마커 색상 지정
        marker.icon = MarkerIcons.RED
        
    }

 

  • 사진 크기가 각각 다른 점 양해 부탁드립니다

 

 

 

Marker 옵션 [ 2 ] - Marker 이미지 지정

 

    private val marker = Marker()
        
    override fun onMapReady(naverMap: NaverMap) {
    
        ...

        // 마커 이미지 지정
        marker.icon = OverlayImage.fromResource(R.drawable.map_ic_marker_fill_red32)
    }
  • OverlayImage 옵션과 MarkerIcons.Color 가 겹칠 경우, MarkerIcons.Color 옵션이 우선된다
  • 즉 아이콘을 적용해도 지정한 색상의 기본아이콘이 적용된다.

 

 

 

Marker 옵션 [ 3 ] - Marker 텍스트 설정
    private val marker = Marker()
        
    override fun onMapReady(naverMap: NaverMap) {
    
        ...
        
        // Caption Text 설정
        marker.captionText = "여기!"

    }

 

 

 

 

Marker 옵션 [ 4 ] - Marker 크기 지정
    private val marker = Marker()
            
    override fun onMapReady(naverMap: NaverMap) {
    
        ...
        
        
         // 마커 크기 지정
        marker.width = 50
        marker.height = 70

    }

 

 

 

 


 

게시글 DB에 위치정보 저장하기 [ 1 ]
  • 먼저 아래와 같이 Map을 통해 가져올 데이터의 형식을 지정해주었다.
data class LocationData (
    val latLng : LatLng,
    val address : String,
    val cityInfo : List<String>,
)

 

  • latLng
    • 위도 경도 LatLng 값으로 게시글에 지도 탭 클릭 시 지도에 거래 장소 위치를 바로 확인할 수 있도록 하기 위해 저장
  • address
    • Geocoder 통해서 Map의 해당 위치에 대한 위치정보를 받아와서 사용하기 위해 저장.
  • cityInfo
    • Firestore의 whereToEqiual 쿼리를 통한 지역별 검색용으로 저장,
    • 저장 시 replace("광역시","") 와 같이 광역시, 특별시, 대한민국, 특별자치 를 지우는 것을 추천

 

google - Geocoder를 통해 가져올 값

 

city 인포에 담을 값들

 

쿼리의 whereToEqual 으로 DB 값을 가져올 때 '인천' 쿼리값을 통해 요청 시 '인천광역시' 에 해당하는 값이 넘어오지 않는다 일치하는 값만 가져오기 때문

 

 

 

 

위치 권한 요청하기

 

지도를 통해 장소를 확인하는 버튼 클릭 시 권한요청하는 로직

  • Todo - 지도로 이동하는 Fragment가 게시글 작성,수정 과 작성된 글의 위치정보 확인 3곳에서 Map에 접근할 수 있어서 차라리 앱 시작 시 확인 하는 방법을 사용하는 것이 좋을 것 같다.
  • Todo 2 - 현재는 권한 요청을 허용했을 때 다시 한번 지도를 클릭해주어야 MapFragment로 넘어가는데, 허용 시 바로 Fragment가 전환될 수 있도록 추가하는 기능을 적용하지 못했다.
    private val PERMISSIONS = arrayOf(
        android.Manifest.permission.ACCESS_FINE_LOCATION,
        android.Manifest.permission.ACCESS_COARSE_LOCATION,
    )

    private fun permissionCheck(context:Context): Boolean {

        for (permission in PERMISSIONS) {
            if (ContextCompat.checkSelfPermission(context,permission)
                != PackageManager.PERMISSION_GRANTED
            ) {
                return false
            }
        }
        return true
    }
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        ...
        
        binding.btnViewLocationMap.setOnClickListener {

            // 위치 권한 확인 후 없다면 요청, 있다면 MapFragment로 이동
            if (!Util.permissionCheck(this.requireContext())) {
                ActivityCompat.requestPermissions(requireActivity(), Util.PERMISSIONS, 5000)
            }
            else {
                parentFragmentManager.beginTransaction().add(R.id.frag_edit, MapViewFragment(EDITABLE))
                    .addToBackStack(null).commit()
            }
        }

 

 

 


 

 

NaverMap 객체 설정

 

 

  • MapViewFragment의 주 생성자 프로퍼티로 state를 받는데, 이는 권장되는 방법이 아니다 Fragment ResultListener를 통해서 state 값을 전달해주는 것이 권장되는 방식이다.
  • ✔️ 리팩토링 예정 ->  프로젝트 전반적인 리팩토링 글은 별도로 작성할 예정
class MapViewFragment(private val state:String) : Fragment(), OnMapReadyCallback {


    ...
    
    // LocationData의 cityInfo에 들어갈 List
    private val locationCityInfo: MutableList<String> = mutableListOf()
    
    // 사용할 LocationData
    private var _locationInfo: LocationData? = null
    private val locationInfo : LocationData get() = _locationInfo!!
    
    // 초기 View
    private var initCameraPosition: LatLng = LatLng(37.655798, 126.7748480)

    ...
    
    // NaverMap 객체 설정
    override fun onMapReady(naverMap: NaverMap) {

        // marker 설정 들
        ...
        
        
        naverMap.apply {
        
            // Map에 나타나는 Layer 관리 ( 아래 예시 코드 -> 등산로, 등고선 제외 )
            setLayerGroupEnabled(NaverMap.LAYER_GROUP_MOUNTAIN, false)
            
            // 실내지도 속성 활성화 여부 -> 지도유형이 Basic, Terrain 일 경우에만 사용가능.
            isIndoorEnabled = true

            // 지도 밝기 조정 ( -1 ~ 1 사이로 지정 )
            lightness = 0.1f

            // 카메라 최소 최대 줌 레벨 제한
            maxZoom = 20.0
            minZoom = 8.0

            // marker 초기 위치 설정
            marker.position = LatLng(initCameraPosition.latitude, initCameraPosition.longitude)
            marker.map = naverMap
            
            // UI 설정 객체 접근
            val uiSettings = naverMap.uiSettings

            // naverMap UI 설정
            uiSettings.apply {
            
                // 좌측 하단 Naver 로고 클릭 활성화 여부
                isLogoClickEnabled = false

                // 나침반 활성화 여부
                isCompassEnabled = true

                // 현재 위치 추적 버튼 활성화 여부 -> 사용자 현재 위치 설정을 해주어야 한다
                isLocationButtonEnabled = true
            }

            // 지도 위치 클릭 이벤트
            _currentPostInfo?.let {
                if (state == READ_ONLY) {
                    // todo 게시글 작성자가 아닌 사용자의 지도 클릭 이벤트
                } else {
                    setOnMapClickListener { pointF, latLng ->
                        mapClickEvent(latLng)
                        Log.d("xxxx", " currentPost 있을때")
                    }
                }
            }

            // 카메라 초기 위치 지정, 카메라 이동 애니메이션
            moveCamera(
                CameraUpdate.scrollTo(initCameraPosition)
                    .animate(CameraAnimation.Fly, 1000)
            )

            // 현재 위치
            locationSource =
                FusedLocationSource(this@MapViewFragment, LOCATION_PERMISSION_REQUEST_CODE)
            locationTrackingMode = LocationTrackingMode.NoFollow

        }
    }

    // 화면 클릭 이벤트
    private fun mapClickEvent(latLng: LatLng) {
        marker.position = LatLng(latLng.latitude, latLng.longitude)
        marker.map = naverMap

        getAddress(latLng.latitude, latLng.longitude)
        Log.d("xxxx", " 지도 클릭 ${getAddress(latLng.latitude, latLng.longitude)}")
    }

    // geocoder 통해서 지도 주소정보 가져오기
    private fun getAddress(latitude: Double, longitude: Double): String {
        val geocoder = Geocoder(requireContext(), Locale.KOREA)
        val address: ArrayList<Address>
        var addressResult = ""
        locationCityInfo.clear()
        try {
            // API 레벨 33 이상일 경우
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                geocoder.getFromLocation(
                    latitude, longitude, 2
                )
                { address ->
                    if (address.size != 0) {
                        addressResult =
                            address[0].getAddressLine(0).replace("대한민국", "").replace("KR", "")
                        Log.d("xxxx", "getAddress 33 ↑ : ${address}")

                        // geocoder를 통해서 얻은 주소정보를 textView에 띄워준다
                        binding.mapTvAddress.text = addressResult

                        // address 에서 검색 키워드로 사용할 xx시, xx동 등의 정보를 cityInfo에 추가해준다.
                        for (index in address.indices) {
                            locationCityInfo.apply {
                                add(address[index].adminArea)
                                address[index].locality?.let {
                                    add(it)
                                }

                                address[index].featureName?.let {
                                    add(it)
                                }
                            }
                        }

                        _locationInfo = LocationData(
                            LatLng(latitude, longitude),
                            addressResult,
                            locationCityInfo
                        )
                        Log.d("xxxx", "location Info 33↑ Change: $locationInfo")
                    }
                }
            } else { // API 레벨 33 미만일 경우
                address = geocoder.getFromLocation(latitude, longitude, 1) as ArrayList<Address>
                if (address.size > 0) {
                    val currentLocationAddress = address[0].getAddressLine(0)
                        .toString()
                    addressResult = currentLocationAddress.replace("대한민국", "").replace("광역시","")

                    binding.mapTvAddress.text = addressResult

                    for (index in address.indices) {
                        locationCityInfo.apply {
                            add(address[index].adminArea)
                            add(address[index].locality)
                            add(address[index].featureName)
                        }
                    }

                    Log.d("xxxx", " 33 ↓ locationCityInfo 33 ↓= $locationCityInfo ")
                    _locationInfo = LocationData(
                        LatLng(latitude, longitude),
                        addressResult,
                        locationCityInfo
                    )
                    Log.d("xxxx", "getAddress33 ↓: ${address} ")
                }
            }

        } catch (e: Exception) {
            e.printStackTrace()
        }
        return addressResult
    }
  • 코드 어느부분을 빼고 정리해야할지 애매해서 주석으로 최대한 이해하기 쉽게 작성하려고 노력했으나 다음에 내가 알아볼 수 있을지 의문이다. NaverMap 객체 단독으로 사용하는 설정들은 별도로 정리 X 
  • Marker 클릭 이벤트
    • 현재는 하나의 MapViewFragment를 사용하여 MapViewFragment 내부에서 게시글 작성자와 일반 이용자를생성자의 state 값을 통해 이를 구분한다. 아래에 추가로 작성하겠지만 게시글 디테일 페이지에서 Map을 클릭할 시 MapView Fragment의 프로퍼티로 READ_ONLY를 통해 MapView로 접근하기에 지도를 클릭해도 마커의 위치가 변하지 않도록 하였다.
  • _locationInfo
    • 게시글 수정 및 작성 시 게시글에 위치 정보를 저장하여 LatLng 값과, geocoder를 통해 가져온 주소에 대한 정보를 담아 게시글에 해당 정보를 통해 initCamera포지션 및 교환장소 정보 제공을 위해 LocationData의 형식으로 저장 한 뒤 게시글 수정, 업데이트 시 Post 의 LatLng에 값을 지정해줄 것이다.
  • 게시글 DB에 위치값을 저장하는 방식은 아래에 수정, 업로드 부분에서 추가로 다룰 예정.

 

 

 


 

 

 

지도 초기 값 지정 및 수정하기

 

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // NaverMap 인스턴스 생성
        val fm = childFragmentManager
        val mapFragment = fm.findFragmentById(R.id.map_fragment) as MapFragment?
            ?: MapFragment.newInstance().also {
                fm.beginTransaction().add(R.id.map_fragment, it).commit()
            }
        mapFragment.getMapAsync(this)

        myPostFeedViewModel.currentPost.observe(viewLifecycleOwner) {
            it?.let {
                // currentPostInfo 객체 초기화
                _currentPostInfo = it

                // 게시글에 설정된 LatLng에 따라 카메라 초기 위치 설정
                if (it.locationLatLng.isNotEmpty()) {
                    initCameraPosition = LatLng(it.locationLatLng[0], it.locationLatLng[1])
                }

                // 게시글 작성 및 수정이 아닌 경로인 일반 디테일에서 지도를 호출한 경우 완료버튼 X
                if (state != READ_ONLY) {
                        binding.mapBtnComplete.visibility = View.VISIBLE
                } else {
                    binding.mapBtnComplete.visibility = View.GONE
                }
                // 초기 마커 텍스트 위치 지정.
                binding.mapTvAddress.text = it.address
            }
        }

        // 뒤로가기 버튼 클릭 이벤트
        binding.mapBtnBack.setOnClickListener {
            parentFragmentManager.popBackStack()
        }

        // 위치 설정 완료 버튼 클릭 이벤트
        binding.mapBtnComplete.setOnClickListener {
            _locationInfo?.let {
                if (binding.mapTvAddress.text.length > 2) {
                    // 위치 정보 LiveData에 넘겨주기.
                    myPostFeedViewModel.setLocationInfo(locationInfo!!)
                    
                    // 디테일 페이지로 돌아온 뒤 지도 클릭 시 변경사항 반영
                    if (_currentPostInfo != null){
                        myPostFeedViewModel.setCurrentPost(currentPostInfo.copy(locationLatLng = listOf(locationInfo.latLng.latitude,locationInfo.latLng.longitude)))
                    }
                    Log.d("xxxx", " setLocationInfo ! ${locationInfo} ")
                    parentFragmentManager.popBackStack()
                }
            } ?: Toast.makeText(requireContext(), "위치를 다시 선택해주세요", Toast.LENGTH_SHORT).show()
        }
    }

 

  • 현재 게시글 값이 존재한다면? → 게시글을 최초 작성하고 있는 것이 아니므로 초기 카메라 위치가 게시글 정보에 저장된 위치로 이동
  • 보기전용 MapView의 경우 수정 완료 버튼을 상호작용할 수 없도록 visibility를 gone으로 설정해준다.
  • 한두번 대한민국 서울, 대한민국 인천 으로 값을 받아오게 되는 상황이 있었다. geocoder 함수 내에서 대한민국을 제거해두었기 때문에 서울, 인천과 같이 세부주소가 없는 상황 예외처리를 위해 2글자가 넘어야 적용되도록 해두었다.

 

 

 

 

 

 

 

 

 


 

 

 

디테일 페이지, 작성 페이지에 반영해주기

 

 

 

WritePostFragment, EditPostFragment

- 지도 좌표 설정 후 디테일 페이지로 돌아왔을 때 변경된 내용 반영해주기.

 

 

1. ViewModel, LiveData를 통해 변경된 값 보내주기 위해 ViewModel 함수 및 LiveData 정의

class MyPostFeedViewModel : ViewModel() {

    ...
    
    private var _locationInfo: LocationData? = null
    private val locationInfo : LocationData get() = _locationInfo!!

    // 기존 데이터 제거하기
    @SuppressLint("NullSafeMutableLiveData")
    fun cleanLocationResult(){
        _locationResult.postValue(null)
    }

    // 위치 정보 넘기기, 받기
    fun setLocationInfo(locationData: LocationData){
        _locationResult.postValue(locationData)
    }
    
    ...
}

 

 

2. observe하고 있는 LiveData의 값이 변동된 것을 감지해 View에 적용

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        
        myPostFeedViewModel.currentPostToEditPage.observe(viewLifecycleOwner) { post ->
            currentPost = post
           
            ...

            // 수정 페이지 진입 시 기본 값 지정해주기
            locationLatLng = LocationData(LatLng(post.locationLatLng[0],post.locationLatLng[1]),post.address,post.locationKeyword)

        }
        
        // 위치 변경 시 수정 페이지 변경된 값 반영
        myPostFeedViewModel.locationResult.observe(viewLifecycleOwner){
            it?.let {
                locationLatLng = it
                binding.editEtvAddress.setText(it.address)
                Log.d("xxxx", " 위치 변경 locationLatLng = ${locationLatLng}")
            }
        }
    }

 

 

 

 

 

 


 

[ A. 오늘 복습한 내용 / B. 다음에 학습할 내용 ]

A. 지도로 이동하는 Fragment가 게시글 작성,수정 과 작성된 글의 위치정보 확인 3곳에서 Map에 접근할 수 있어서 차라리 앱 시작 시 확인 하는 방법을 사용하는 것이 좋을 것 같다.

 

B. 현재는 권한 요청을 허용했을 때 다시 한번 지도를 클릭해주어야 MapFragment로 넘어가는데, 허용 시 바로 Fragment가 전환될 수 있도록 추가하는 기능을 적용하지 못했다.

 


 

[오류,에러 등등]

1. maven 추가 후 build가 정상적으로 되지 않는 현상 - 본문에서 다룸

 

 


 

[느낀 점]

1. 코드 작성하면서도 마음 한 켠에 리팩토링에 대한 짐이 쌓였는데, 언제 어떻게 풀어야 좋을지 모르겠다

 

2. 어렵다😺 🐶

 

3. 벌써 11월인 게 신기하다 시간이 너무 빠르다

 

 


[Reference]

 

// Geocoding

https://guide.ncloud-docs.com/docs/ko/maps-geocoding-api

// Naver Maps 
https://navermaps.github.io/maps.js.ncp/docs/tutorial-UI-Event.html