본문 바로가기

TIL

[TIL] Kotlin GoogleMaps 만들기 - 1. Activity에서 띄우기, 현재 위치 받아오기

-1- GoogleMaps를 통해 MapView 만들기 - Activity에서 띄우기

  • 1. GoogleMap API키 받아오기
  • 2. build.gradle에 dependencies 추가
  • 3. Manifest에 ① 에서 받아온 API Key넣어주기, 권한 받아오기( 사용자에게 직접 권한요청을 해도 된다 )
  • 4. xml에서 MapView 생성하기.
  • 5. MainActivity에서 GoogleMap 만들기

 

 

GoogleMap API Key생성하기

API 관리자 보기

 

  • 화면 좌측 라이브러리 탭으로 이동

  • Maps SDK for Android 선택

 

  • 사용 클릭

 

  • 사용자 인증 정보 만들기 클릭 후 API 키 항목 선택 후 진행

결제 정보 입력하라는 내용 따라가서 진행하면 된다. 무료체험이 끝나도 별도로 결제의사가 없으면 자동결제 X

 

 

  • 키 및 사용자 인증 정보에서 API키 확인

 

Google Play services 라이브러리 설치하기

  • Android Studio 상단 메뉴바 Tools → SDK Manager → SDK Tools 로 들어가 Google Play services 설치

 

 

build.gradle 추가하기

  • 버전 확인 후 최신버전으로 추가해주기
dependencies {

    implementation("com.google.android.gms:play-services-location:21.0.1")
    implementation("com.google.android.gms:play-services-maps:18.1.0")
    
    ...
    
}

 

Manifest에 meta-data 로 API 키 추가 및 권한 추가.

<manifest>

	...
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
	
    // meta-data는 <application> 안에 넣어주면 된다 </application>
    <application
        ...
        
        
        >
        <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="API 키" />
       	...
        
     </application>
</manifest>
  • meta-data의 API키를 넣어줄 때 name의 geo는 geolocation 의 뜻하며 지리적 위치 또는 위치 기반 서비스와 관련된 것임을 나타낸다. Google Maps API를 사용하려면 지도 서비스 및 위치 관련 데이터에 접근하기 위한 API키가 필요하며, 이 API 키를 설정하는 meta-data 요소의 이름이 "com.google.android.geo.API_KEY" 로 공식적으로 지정되어있다.

acitivity_main.xml

  • MapView 생성해주기
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">


    <com.google.android.gms.maps.MapView
        android:id="@+id/mapView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

 

 

MainActivity.kt

  • 1. xml에 쉽게 접근하기 위해서 ViewBinding 적용 및 GoogleMap, MapView 지연초기화로 선언 해주고 MapView의 생명주기는 Activity의 생명주기에 맞게 작성해준다
    • 자원관리 및 성능 최적화와 데이터 관리, 보안 및 데이터 무결성을 위해서도 MapView의 생명주기를 Activity의 생명주기와 맞춰주어야 한다.
  • 2. Activity에서 OnMapReadyCallBack 인터페이스를 상속 받아준다.
class MainActivity : AppCompatActivity(),OnMapReadyCallback {
    private lateinit var binding: ActivityMainBinding

    private lateinit var mapView: MapView
    private lateinit var mMap : GoogleMap
    private var currentMarker: Marker? =null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        this.mapView = binding.mapView
        mapView.onCreate(savedInstanceState)
        mapView.getMapAsync(this)
    }
    override fun onStart() {
        super.onStart()
        mapView.onStart()
    }
    override fun onStop() {
        super.onStop()
        mapView.onStop()
    }
    override fun onResume() {
        super.onResume()
        mapView.onResume()
    }
    override fun onPause() {
        super.onPause()
        mapView.onPause()
    }
    override fun onLowMemory() {
        super.onLowMemory()
        mapView.onLowMemory()
    }
    override fun onDestroy() {
        mapView.onDestroy()
        super.onDestroy()
    }

 

 

3. ActivityResultLauncher<Array<String>>을 통해 권한 확인 및 요청하기.

  • 3 - 1 import android.Manifest - Import 해주기
import android.Manifest

 

 

    • 3 - 2 권한 확인 및 권한 요청하기
override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // 권한 요청
        locationPermission = registerForActivityResult(
            ActivityResultContracts.RequestMultiplePermissions()
        ) {results ->
            if (!results.all { it.value }){
                Toast.makeText(this,"권한 승인이 필요합니다.",Toast.LENGTH_SHORT).show()
            }
        }
        locationPermission.launch(
            arrayOf(
                Manifest.permission.ACCESS_COARSE_LOCATION,
                Manifest.permission.ACCESS_FINE_LOCATION
            )
        )
        
        
        ...
        
    }
}

 

 

4. setLocation 함수 만들기, onMapReady 메소드 만들기

    override fun onMapReady(googleMap: GoogleMap) {
    	googleMap.uiSettings.apply {
            isZoomControlsEnabled
            isZoomGesturesEnabled
            isRotateGesturesEnabled = false
        }
        mMap = googleMap

        fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)
        setLocation(37.602589, 127.143674)!!.showInfoWindow()

    }

    private fun setLocation(latitude: Double, longitude: Double): Marker? {
        val positionLatLng = LatLng(latitude!!, longitude!!)
        val markerOption = MarkerOptions().apply {
            position(positionLatLng)
            title("Here!")
            snippet("기본")
        }

        val cameraOption = CameraPosition.builder()
            .target(positionLatLng)
            .zoom(15f)
            .build()

        mMap.mapType = GoogleMap.MAP_TYPE_NORMAL
        mMap.animateCamera(CameraUpdateFactory.zoomTo(15f))
        mMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraOption))
        return mMap.addMarker(markerOption)
    }
  • 4 - 1 setLocation 함수 만들기
    • markerOption, Camera Position 설정하기. 
      • MarkerOption
        • latitude와 longitude 값을 받아 해당 위치에 마커를 표시하고, 마커에 title, snipper을 통해 사용자에게 정보 전달을 한다. title 해당 위치 마커의 제목으로 사용되고, snippet은 부연설명을 제공한다.
      • CameraPosition
        • Camera( Google Map에서 보여지는 화면 )의 위치 및 zoom 레벨
        • zoom 레벨 - 0 부터 시작하여 더 큰 값을 가질 수록 지도가 더 확대 된다.
    • mapType 지정
      • MAP_TYPE_NORMAL
        • 일반 도로 지도 - 도로 등의 중요한 특징들이 표시되는 지도
      • MAP_TYPE_HYBRID
        • 도로 지도와 위성사진 데이터가 동시에 표시되는 지도
      • MAP_TYPE_SATELLITE
        • 위성사진 데이터로 도로 및 특징이 표시되 지도 (대한민국은 위성지도 사용제한이라고 한다.)
      • MAP_TYPE_TERRAIN
        • 지형도 데이터로 색상, 등고선 및 원근 음영이 표시되는 지도
        •  
        • addMarker(markerOption) 
          • markerOption 에서 설정한 Option으로 화면에 표시될 Marker 추가
  • 4 - 2 onMapReady 매소드 생성 
    • GoogleMap.uiSettings - 지도의 화면 확대, 화면 회전등의 유저와 GoogleMap UI와 상호작용 및 화면 UI 설정을 조정하는 인터페이스 이다. 
        • isRotateGesturesEnabled : Boolean
          • 지도 회전 제스처를 사용할 수 있는지 여부를 설정한다.
        • isZoomControlsEnabled : Boolean
          • 지도 화면에 확대 / 축소 컨트롤을 표시할지 여부를 설정한다.
        • isZoomGesturesEnabled : Boolean
          • 확대 / 축소 제스처(핀치 줌)를 사용할 수 있는지 여부를 설정한다.
        • isCompassEnabled : Boolean
          • 지도 화면에 나침반 컨트롤을 표시할 지 여부를 설정한다.
        • isMyLocationButtonEnabled : Boolean
          • 현재 위치 버튼을 표시할지 여부를 설정한다. 해당 버튼을 누르면 지도가 현재 위치로 이동한다
          • 현재 위치 권환 허용을 필요로 한다.
        • isMapToolbarEnabled : Boolean
          • 지도 도구 모음을 할성화할지 여부를 설정한다. 도구 모음에는 마커 및 지도 정보 공유, 지도 및 거리 뷰 전환 등의 옵션이 포함된다.uiSettings 기능
        • isZoomControlsEnabled : Boolean
          • 지도 화면에 확대 / 축소 컨트롤을 표시할지 여부를 설정한다
    • getFusedLocationProviderClient(this) 
      • FusedLocationProviderClient 객체를 얻기 위한 팩토리 메서드로 주어진 this(activity) Context를 기반으로 FusedLocationProviderClien를 생성한다.
      • onMapReady 메서드에서 FusedLocationProviderClient를 초기화 하는 이유는 지도를 표시하고 나면 사용자의 현재 위치를 추적하거나 지도의 초기 위치를 설정하는 것이 일반적이기 때문이다.
    •  showInfoWindow()
      • Google Maps 화면이 시작되었을 때 마커와 함께 지정해놓은 title, snipper 을 보여준다. showInfoWindow()를 사용하지 않을 경우 마커의 위치는 확인되지만 title, snippet은 마커를 클릭해야 확인이 가능하다.

 

 

완성코드 MainAcitivty.kt

class MainActivity : AppCompatActivity(), OnMapReadyCallback {
    private lateinit var binding: ActivityMainBinding

    private lateinit var mapView: MapView
    private lateinit var mMap: GoogleMap

    lateinit var locationPermission: ActivityResultLauncher<Array<String>>
    lateinit var fusedLocationProviderClient: FusedLocationProviderClient

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // 권한 요청
        locationPermission = registerForActivityResult(
            ActivityResultContracts.RequestMultiplePermissions()
        ) {results ->
            if (!results.all { it.value }){
                Toast.makeText(this,"권한 승인이 필요합니다.",Toast.LENGTH_SHORT).show()
            }
        }
        locationPermission.launch(
            arrayOf(
                Manifest.permission.ACCESS_COARSE_LOCATION,
                Manifest.permission.ACCESS_FINE_LOCATION
            )
        )

        // Activity에서 GoogleMaps를 사용하는 경우
        mapView = binding.mapView
        mapView.onCreate(savedInstanceState)
        mapView.getMapAsync(this)

    }


    override fun onMapReady(googleMap: GoogleMap) {
        mMap = googleMap
        mMap.uiSettings.apply {
            isZoomControlsEnabled
            isZoomGesturesEnabled
            isRotateGesturesEnabled = false
            isMapToolbarEnabled = true
        }



        fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)
        setLocation(37.602589, 127.143674)!!.showInfoWindow()

    }

    private fun setLocation(latitude: Double, longitude: Double): Marker? {
        val positionLatLng = LatLng(latitude!!, longitude!!)
        val markerOption = MarkerOptions().apply {
            position(positionLatLng)
            title("Here!")
            snippet("기본")
        }

        val cameraOption = CameraPosition.builder()
            .target(positionLatLng)
            .zoom(15f)
            .build()

        mMap.mapType = GoogleMap.MAP_TYPE_NORMAL
        mMap.animateCamera(CameraUpdateFactory.zoomTo(15f))
        mMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraOption))
        return mMap.addMarker(markerOption)
    }

    override fun onStart() {
        super.onStart()
        mapView.onStart()
    }

    override fun onStop() {
        super.onStop()
        mapView.onStop()
    }

    override fun onResume() {
        super.onResume()
        mapView.onResume()
    }

    override fun onPause() {
        super.onPause()
        mapView.onPause()
    }

    override fun onLowMemory() {
        super.onLowMemory()
        mapView.onLowMemory()
    }

    override fun onDestroy() {
        mapView.onDestroy()
        super.onDestroy()
    }
}

 

작동화면

 

 

 

2. 현재 위치 가져오기

MainActivity.kt

  • 1. 에서 작성한 MainActivity.kt에서 setLocation을 updateLocation(), setLastLocation()으로 변경
  • updateLocation()의 LocationRequest.create() 가 Deprecated 되었기 때문에 Location.Builder로 변경되었다.
class MainActivity : AppCompatActivity(), OnMapReadyCallback {
    private lateinit var binding: ActivityMainBinding

    private lateinit var mapView: MapView
    private lateinit var mMap: GoogleMap

    lateinit var locationPermission: ActivityResultLauncher<Array<String>>
    lateinit var fusedLocationProviderClient: FusedLocationProviderClient
    lateinit var locationCallback: LocationCallback

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // 권한 요청
        locationPermission = registerForActivityResult(
            ActivityResultContracts.RequestMultiplePermissions()
        ) { results ->
            if (!results.all { it.value }) {
                Toast.makeText(this, "권한 승인이 필요합니다.", Toast.LENGTH_SHORT).show()
            }
        }
        locationPermission.launch(
            arrayOf(
                Manifest.permission.ACCESS_COARSE_LOCATION,
                Manifest.permission.ACCESS_FINE_LOCATION
            )
        )

        // Activity에서 GoogleMaps를 사용하는 경우
        mapView = binding.mapView
        mapView.onCreate(savedInstanceState)
        mapView.getMapAsync(this)

    }


    override fun onMapReady(googleMap: GoogleMap) {
        mMap = googleMap
        mMap.uiSettings.apply {
            isZoomControlsEnabled
            isZoomGesturesEnabled
            isRotateGesturesEnabled = false
            isMapToolbarEnabled = true
        }


        fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)
//        setLocation(37.602589, 127.143674)!!.showInfoWindow()
        updateLocation()
    }

    fun updateLocation() {
        val locationRequest = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY,5000).apply {
            setMinUpdateDistanceMeters(0f)
            setGranularity(Granularity.GRANULARITY_PERMISSION_LEVEL)
            setWaitForAccurateLocation(true)
        }.build()
        locationCallback = object : LocationCallback() {
            override fun onLocationResult(locationResult: LocationResult) {
                locationResult?.let {
                    for (location in it.locations) {
                        setLastLocation(location)
                    }
                }
            }
        }
        if (ActivityCompat.checkSelfPermission(
                this,
                Manifest.permission.ACCESS_FINE_LOCATION
            ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
                this,
                Manifest.permission.ACCESS_COARSE_LOCATION
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            return
        }
        fusedLocationProviderClient.requestLocationUpdates(
            locationRequest, locationCallback,
            Looper.myLooper()!!
        )
    }

    fun setLastLocation(lastLocation: Location) {
        val LATLNG = LatLng(lastLocation.latitude, lastLocation.longitude)

        val makerOptions = MarkerOptions().apply {
            position(LATLNG)
            title("Here")
            snippet("기본")
        }
        val cameraOption = CameraPosition.builder()
            .target(LATLNG)
            .zoom(15f)
            .build()

        mMap.addMarker(makerOptions)
        mMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraOption))


    }

//    fun setLocation(latitude: Double, longitude: Double): Marker? {
//        val positionLatLng = LatLng(latitude!!, longitude!!)
//        val markerOption = MarkerOptions().apply {
//            position(positionLatLng)
//            title("Here!")
//            snippet("기본")
//        }
//
//        val cameraOption = CameraPosition.builder()
//            .target(positionLatLng)
//            .zoom(15f)
//            .build()
//
//        mMap.mapType = GoogleMap.MAP_TYPE_NORMAL
//        mMap.animateCamera(CameraUpdateFactory.zoomTo(15f))
//        mMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraOption))
//        return mMap.addMarker(markerOption)
//    }


    override fun onStart() {
        super.onStart()
        mapView.onStart()
    }

    override fun onStop() {
        super.onStop()
        mapView.onStop()
    }

    override fun onResume() {
        super.onResume()
        mapView.onResume()
    }

    override fun onPause() {
        super.onPause()
        mapView.onPause()
    }

    override fun onLowMemory() {
        super.onLowMemory()
        mapView.onLowMemory()
    }

    override fun onDestroy() {
        mapView.onDestroy()
        super.onDestroy()
    }
}

 

 

 


 

[오늘 복습한 내용]

1. Google Map을 이해하려고 며칠동안 잡고 복습하고 있다.

 

 


[오류,에러 등등]

1. Manifest에서 Permission을 추가해줬는데 해당 부분이 오류가 남.

해결방법 →  직접 코드 작성으로 Manifest Import 해주기

import android.Manifest

 

2.  Resource$NotFoundException 오류

FATAL EXCEPTION: main Process: com.example.googlemaps, PID: 6468 java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.googlemaps/com.example.googlemaps.MainActivity}: android.content.res.Resources$NotFoundException: Resource ID #0x7f070016 type #0x3 is not valid

 

잘 돌아가던 앱이 갑자기 실행이 안되어서 앱 삭제 후 다시 설치하니까 정상작동 되더라


[느낀 점]

1. 잠을 줄여야겠다

 

2. 조금 더 열심히 해야겠다

 

3. 시간이 너무 빠르다

 

 


[Reference]

 

 

// GoogleMaps

https://eunoia3jy.tistory.com/185

https://developerson.tistory.com/108

https://furang-note.tistory.com/16