paint-brush
Jetpack Compose로 모바일 지도 혁신: 개발자를 위한 Google I/O의 유용한 정보~에 의해@darrylbayliss
새로운 역사

Jetpack Compose로 모바일 지도 혁신: 개발자를 위한 Google I/O의 유용한 정보

~에 의해 Darryl Bayliss22m2024/07/09
Read on Terminal Reader

너무 오래; 읽다

지도용 Jetpack Compose는 최신 Android 개발을 혁신하여 모바일 매핑을 더 쉽고 효율적으로 만들었습니다. Google 지도를 Jetpack Compose 프로젝트에 통합하는 방법에 관한 Google I/O Extended 2023의 주요 기술과 팁을 알아보세요.
featured image - Jetpack Compose로 모바일 지도 혁신: 개발자를 위한 Google I/O의 유용한 정보
Darryl Bayliss HackerNoon profile picture
0-item



이 게시물은 2023년 7월 Google 개발자 그룹 런던을 위한 Google I/O Extended에서 진행된 강연을 기반으로 합니다. 강연 슬라이드는 여기에서 볼 수 있습니다.

지도용 Jetpack Compose

Jetpack Compose 1.0이 2021년 7월 에 출시되었다고는 상상하기 어렵습니다. 2년이 빨리 지나서 Google Play 상위 1000개 앱 중 24%가 Compose를 채택하고 있는 것을 보면 그 이유를 쉽게 이해할 수 있습니다.



모든 흥미로운 것 중에서 제가 느끼기에는 현대 Android 개발의 한 부분이 거의 관심을 받지 못하고 있다고 생각합니다. 바로 Google 지도입니다. SDK를 사용한지 꽤 시간이 지났는데, Google 지도가 시대에 맞춰 자체 Compose 라이브러리를 출시하는 것을 보고 기분 좋게 놀랐습니다.


이는 매핑 공간에서 일하는 회사 및 엔지니어에게 반가운 소식이 될 것입니다. 모바일 매핑은 355억 달러 규모의 산업으로, 연평균 복합 성장률(CAGR) 19.83%로 2028년까지 877억 달러로 증가할 것으로 예상됩니다. 원천


성장 시장 그래프


이것이 왜 중요합니까? 시장이 커질수록 기업은 모바일 매핑 애플리케이션을 통해 수익을 창출할 수 있는 기회가 더 많아집니다. 여기에는 일반적인 사용 사례, 음식, 식료품 배달, 차량 호출 서비스 등이 포함됩니다. 그러나 자세히 살펴보면 즉시 명확하지 않은 애플리케이션이 있습니다.


다음은 간단한 검색을 통해 찾을 수 있는 예입니다.


성장 시장 분야 모바일 지도는 도시의 심장 박동을 관리하고 도시의 과제를 더 잘 이해하고 대응할 수 있는 방식으로 데이터를 시각화하는 데 도움이 되므로 스마트 시티에 적합합니다. 도시 계획자, 비상 대응 조직 또는 일반 거주자에게 유용합니다.


리소스 관리도 매핑 솔루션의 이점을 누릴 수 있습니다. 농업에서 어업, 광업, 임업에 이르기까지 지도는 이러한 작업 분야에 종사하는 사람들에게 지속 가능한 방식으로 재료를 수확하기 위한 올바른 결정을 내릴 수 있는 관점을 제공합니다.


교통은 매핑 기술에 크게 의존합니다. Google 지도나 Uber와 같은 소비자 앱뿐만 아니라 기업의 차량 위치를 파악하는 등 비즈니스 수준의 기능도 제공합니다. 교통 기관은 또한 지도를 사용하여 교통을 관리하고 흐름을 원활하게 하기 위해 교통의 방향을 어디로 정할지 결정하는 데 도움을 줍니다.


마지막으로, 기후 변화와 날씨 예측이 점점 더 어려워지고 있는 가운데, 지도를 통해 기상 기관, 비상 대응 부서, 야생 동물 보호론자들은 세상이 어떻게 변화하고 있는지, 그리고 이를 줄이기 위해 긍정적인 조치를 취할 수 있는 방법을 이해할 수 있습니다.

출처:Mordor Intelligence , GMInsights , Allied Market Research , EMR Research , Google Earth Outreach , 연구 및 시장


세상이 점점 더 많은 데이터를 제공하고 있으므로 해당 데이터를 지도에 표시하는 방법을 배울 수 있는 좋은 시기입니다. 그렇게 하고 코드로 돌아가겠습니다.


Compose에 Google 지도 사용

Compose용 Google 지도는 다음 종속성을 사용합니다.

 dependencies { implementation "com.google.maps.android:maps-compose:2.11.4" implementation "com.google.android.gms:play-services-maps:18.1.0" // Optional Util Library implementation "com.google.maps.android:maps-compose-utils:2.11.4" implementation 'com.google.maps.android:maps-compose-widgets:2.11.4' // Optional Accompanist permissions to request permissions in compose implementation "com.google.accompanist:accompanist-permissions:0.31.5-beta" }


Compose용 Google 지도는 Google Maps SDK를 기반으로 구축되었으므로 Compose 라이브러리와 Maps SDK를 가져와야 합니다. Compose 라이브러리가 이러한 객체 대부분을 컴포저블에 래핑하므로 Google Maps SDK에서 대부분의 객체를 사용할 필요가 없습니다.


유틸리티 및 위젯 라이브러리는 선택적 종속성입니다. utils 라이브러리는 지도에 마커를 클러스터링하는 기능을 제공하고 위젯은 추가 UI 구성 요소를 제공합니다. 나중에 이러한 기능이 사용되는 것을 볼 수 있습니다.


이 게시물에는 지도에서 자주 사용되는 권한인 위치 권한을 요청하는 방법을 보여주기 위해 Accompanist의 권한 요청 라이브러리를 포함시켰습니다. Accompanist는 Google이 아직 Jetpack Compose에 포함되지 않은 기능을 시험해보고 피드백을 수집하기 위한 실험적 라이브러리입니다.


마지막으로 Google 개발자 콘솔 로 이동하여 Google Maps SDK API 키를 등록하고 프로젝트에 추가해야 합니다. 이를 수행하는 방법에 대한 가이드가 Google 지도 개발자 문서 에 있습니다.


보안 팁: Google 개발자 콘솔에서는 API 키가 애플리케이션에서만 작동하도록 잠급니다. 이를 통해 무단 사용을 방지할 수 있습니다.


지도 표시

지도를 표시하는 방법은 다음과 같이 간단합니다.


 setContent { val hydePark = LatLng(51.508610, -0.163611) val cameraPositionState = rememberCameraPositionState { position = CameraPosition.fromLatLngZoom(hydePark, 10f) } GoogleMap( modifier = Modifier.fillMaxSize(), cameraPositionState = cameraPositionState) { Marker( state = MarkerState(position = hydePark), title = "Hyde Park", snippet = "Marker in Hyde Park" ) } }


영역의 위치가 포함된 LatLng 객체를 생성하고 이를 rememberCameraPositionState 와 함께 사용하여 카메라의 초기 위치를 설정합니다. 이 방법은 손을 사용하거나 프로그래밍 방식으로 이동할 때 지도의 위치를 기억합니다 . 이 방법이 없으면 Compose는 모든 상태 변경 시 지도를 초기 위치로 다시 계산합니다.


다음으로 GoogleMap 작성을 만들고 선택한 수정자와 카메라 상태를 전달합니다. GoogleMap 추가 컴포저블을 전달하기 위한 Slot API도 제공합니다. 이러한 컴포저블은 지도에 그리려는 것입니다.


Marker 컴포저블을 추가한 다음 내부에 마커 위치가 포함된 MarkerState 추가합니다. 마지막으로 마커의 제목과 설명을 추가합니다.


이것을 실행하면 하이드파크에 마커가 있는 웨스트 런던의 멋진 공중 전망이 제공됩니다.


마커 인 하이드 파크

마커 창 사용자 정의

MarkerInfoWindowContent 컴포저블을 사용하여 마커 창을 맞춤설정할 수 있습니다. 여기에는 슬롯 기반 API도 있습니다. 즉, 컴포저블을 전달하여 창에서 맞춤 UI를 렌더링할 수 있습니다.


 setContent { val hydePark = LatLng(51.508610, -0.163611) val cameraPositionState = rememberCameraPositionState { position = CameraPosition.fromLatLngZoom(hydePark, 10f) } GoogleMap( modifier = Modifier.fillMaxSize(), cameraPositionState = cameraPositionState) { MarkerInfoWindowContent( state = MarkerState(position = hydePark), title = "Hyde Park", snippet = "Marker in Hyde Park" ) { marker -> Column(horizontalAlignment = Alignment.CenterHorizontally) { Text( modifier = Modifier.padding(top = 6.dp), text = marker.title ?: "", fontWeight = FontWeight.Bold ) Text("Hyde Park is a Grade I-listed parked in Westminster") Image( modifier = Modifier .padding(top = 6.dp) .border( BorderStroke(3.dp, color = Color.Gray), shape = RectangleShape ), painter = painterResource(id = R.drawable.hyde_park), contentDescription = "A picture of hyde park" ) } } } }


이것을 실행하면 마커 위에 사용자 정의 창이 표시됩니다.


하이드파크의 맞춤형 윈도우 마커

여러 마커 표시

여러 마커를 표시하는 것은 필요한 만큼 추가하는 것만큼 간단합니다. 서부 런던에 있는 몇몇 공원에 대한 마커를 추가해 보겠습니다.


 setContent { val hydePark = LatLng(51.508610, -0.163611) val regentsPark = LatLng(51.531143, -0.159893) val primroseHill = LatLng(51.539556, -0.16076088) val cameraPositionState = rememberCameraPositionState { position = CameraPosition.fromLatLngZoom(hydePark, 10f) } GoogleMap( modifier = Modifier.fillMaxSize(), cameraPositionState = cameraPositionState) { // Marker 1 Marker( state = MarkerState(position = hydePark), title = "Hyde Park", snippet = "Marker in Hyde Park" ) // Marker 2 Marker( state = MarkerState(position = regentsPark), title = "Regents Park", snippet = "Marker in Regents Park" ) // Marker 3 Marker( state = MarkerState(position = primroseHill), title = "Primrose Hill", snippet = "Marker in Primrose Hill" ) } }


코드를 실행하면 지도에 마커가 나타나는 것을 볼 수 있습니다.


다중 공원 마커


클러스터링 마커

짧은 시간 내에 지도가 바빠질 수 있습니다. 300개의 마커를 표시하려고 하면 사용자가 무슨 일이 일어나고 있는지 시각적으로 이해하기 어려울 것입니다. Google 지도와 기기는 모든 단일 마커를 렌더링해야 하므로 성능과 배터리 수명에 영향을 미치기 때문에 사용자에게 감사하지 않습니다.


이에 대한 해결책은 서로 가까운 마커를 단일 마커로 그룹화하는 기술인 클러스터링 입니다. 이 클러스터링은 확대/축소 수준을 기준으로 발생합니다. 지도를 축소하면 마커가 클러스터로 그룹화되고, 확대하면 클러스터가 개별 마커로 분할됩니다.


Google Maps for Compose는 Clustering 컴포저블을 통해 이를 즉시 제공합니다. 클러스터링이 발생하기 위해 복잡한 정렬이나 필터링을 작성할 필요가 없습니다.


 setContent { val hydePark = LatLng(51.508610, -0.163611) val regentsPark = LatLng(51.531143, -0.159893) val primroseHill = LatLng(51.539556, -0.16076088) val crystalPalacePark = LatLng(51.42153, -0.05749) val greenwichPark = LatLng(51.476688, 0.000130) val lloydPark = LatLng(51.364188, -0.080703) val cameraPositionState = rememberCameraPositionState { position = CameraPosition.fromLatLngZoom(hydePark, 10f) } GoogleMap( modifier = Modifier.fillMaxSize(), cameraPositionState = cameraPositionState) { val parkMarkers = remember { mutableStateListOf( ParkItem(hydepark, "Hyde Park", "Marker in hyde Park"), ParkItem(regentspark, "Regents Park", "Marker in Regents Park"), ParkItem(primroseHill, "Primrose Hill", "Marker in Primrose Hill"), ParkItem(crystalPalacePark, "Crystal Palace", "Marker in Crystal Palace"), ParkItem(greenwichPark, "Greenwich Park", "Marker in Greenwich Park"), ParkItem(lloydPark, "Lloyd park", "Marker in Lloyd Park"), ) } Clustering(items = parkMarkers, onClusterClick = { // Handle when the cluster is tapped }, onClusterItemClick = { marker -> // Handle when a marker in the cluster is tapped }) } } data class ParkItem( val itemPosition: LatLng, val itemTitle: String, val itemSnippet: String) : ClusterItem { override fun getPosition(): LatLng = itemPosition override fun getTitle(): String = itemTitle override fun getSnippet(): String = itemSnippet }


ParkItem 데이터 클래스가 추가되었습니다. Clustering 컴포저블에 전달된 항목은 ClusterItem 인터페이스를 준수해야 하기 때문에 이것이 필요합니다. 인터페이스는 클러스터에 각 마커의 위치, 제목 및 스니펫을 제공합니다.

확대 및 축소하면 클러스터링이 작동하는 것을 볼 수 있습니다.


위치 권한 얻기

지도와 사용자 위치는 종종 밀접하게 관련되어 있으므로 일부 매핑 앱에서는 사용자 위치에 대한 권한을 요청하는 것이 합리적입니다.

이렇게 하는 경우 사용자의 권한을 존중하십시오 . 위치 권한은 사용자로부터 획득할 수 있는 가장 민감한 권한 중 하나입니다.


이 권한이 필요한 이유를 사용자에게 알리고 권한 부여에 따른 이점을 적극적으로 보여주세요. 권한이 전혀 필요 없이 앱이 부분적으로 작동하는 경우 보너스 포인트가 됩니다.


Google은 사용자 위치를 처리하는 방법에 대한 몇 가지 유용한 가이드와 백그라운드에서 위치 데이터에 액세스하기 위한 별도의 가이드를 제공합니다.


따라서 귀하는 실사를 완료하고 해당 위치에 액세스하려면 사용자의 허가가 필요하다고 결정했습니다. Accompanist의 권한 라이브러리를 사용하면 다음과 같이 할 수 있습니다.


 // Don't forget to add the permissions to AndroidManifest.xml val allLocationPermissionState = rememberMultiplePermissionsState( listOf(android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION) ) // Check if we have location permissions if (!allLocationPermissionsState.allPermissionsGranted) { // Show a component to request permission from the user Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, modifier = Modifier .padding(horizontal = 36.dp) .clip(RoundedCornerShape(16.dp)) .background(Color.white) ) { Text( modifier = Modifier.padding(top = 6.dp), textAlign = TextAlign.Center, text = "This app functions 150% times better with percise location enabled" ) Button(modifier = Modifier.padding(top = 12.dp), onClick = { allLocationPermissionsState.launchMultiplePermissionsRequest() }) { Text(text = "Grant Permission") } } }


반주자를 통해 앱이 ACCESS_FINE_LOCATION 권한에 액세스할 수 있는지 또는 영어로 높은 수준의 GPS 정확도를 확인하고 있습니다. 그렇지 않으면 권한을 요청할 수 없으므로 Android 매니페스트에 요청된 권한을 포함하는 것이 중요합니다.


Android 시스템과 Google Play 스토어도 매니페스트를 사용하여 앱 작동 방식을 이해하고 사용자에게 알립니다.

권한이 부여되지 않으면 권한의 필요성을 설명하는 작은 대화상자 컴포저블과 시스템을 통해 권한 요청을 실행하는 버튼이 표시됩니다.



지도 애니메이션

대부분의 지도 앱에서는 사용자가 터치를 통해 지도를 이동해야 하지만 Google Maps for Compose는 프로그래밍 방식으로 지도를 이동할 수 있는 API를 제공합니다. 이는 이벤트에 대한 응답으로 특정 영역으로 이동하려는 경우 유용할 수 있습니다.


이 예에서는 마커 컬렉션을 통해 앱을 부드럽게 탐색합니다.


 Box(contentAlignment = Alignment.Center) { GoogleMap( modifier = Modifier.fillMaxSize(), cameraPositionState = cameraPositionState ) { Clustering(items = parkMarkers, onClusterClick = { // Handle when the click is tapped false }, onClusterItemClick = { marker -> // Handle when the marker is tapped }) LaunchedEffect(key1 = "Animation") { for (marker in parkMarkers) { cameraPositionState.animate( CameraUpdateFactory.newLatLngZoom( marker.itemPosition, // LatLng 16.0f), // Zoom level 2000 // Animation duration in millis ), delay(4000L) // Delay in millis } } } }


여기서 중요한 부분은 LaunchedEffect 내부의 코드입니다. 각 마커에 대해 앱은 cameraPositionState.animate() 호출을 설정하여 마커로 이동합니다. 카메라는 CameraUpdateFactory.newLatLngZoom() 을 사용하여 생성된 카메라 업데이트를 통해 이 정보를 수신합니다.


이 메소드는 지도의 확대/축소 수준을 나타내는 부동 소수점인 LatLng 와 애니메이션 지속 시간을 설정하는 데 사용되는 long 을 사용합니다.

마지막으로 애니메이션 간격을 지정하기 위해 delay() 사용하여 각 애니메이션 사이에 4초의 일시 중지를 추가합니다.


스트리트 뷰 표시 중

Google Maps for Compose가 도움을 주는 항공 지도는 단순한 항공 지도가 아닙니다. 또한 앱에 스트리트 뷰 액세스 권한을 부여하여 위치를 360도 보기로 표시할 수도 있습니다. StreetView 컴포저블을 사용하여 이 작업을 수행합니다.


 var selectedMarker: ParkItem? by remember { mutableStateOf(null) } if (selectedMarker != null) { StreetView(Modifier.fillMaxSize(), streetViewPanoramaOptionsFactory = { StreetViewPanoramaOptions().position(selectedMarker!!.position) }) } else { Box(contentAlignment = Alignment.Center) { GoogleMap( modifier = Modifier.fillMaxSize(), cameraPositionState = cameraPositionState ) { Clustering(items = parkMarkers, onClusterClick = { // Handle when the cluster is clicked false }, onClusterItemClick = { marker -> // Handle when a marker in the cluster is clicked selectedMarker = marker false }) } } }


이 예에서는 마커를 탭할 때마다 selectedMarker 변수를 설정합니다. 마커를 선택하면 스트리트 뷰가 표시되어 마커 위치가 전달됩니다.



도형 그리기

지도에 자신만의 모양과 주석을 그릴 수도 있습니다. Compose용 Google 지도는 이를 위해 다양한 컴포저블을 제공합니다. 이 게시물에서는 Circle 컴포저블을 사용하겠습니다.


원은 앱이 지오펜스를 사용하여 사용자 위치의 변경 사항에 반응하는 경우 사용하기에 좋은 모양입니다. 원은 지오펜스가 활성화된 영역을 나타낼 수 있습니다.


 Box(contentAlignment = Alignment.Center) { GoogleMap( modifier = Modifier.fillMaxSize(), cameraPositionState = cameraPositionState ) { Clustering(items = parkMarkers, onClusterClick = { // Handle when the cluster is clicked false }, onClusterItemClick = { marker -> // Handle when a marker in the cluster is clicked selectedMarker = marker false }) } } parkMarkers.forEach { Circle( center = it.position, radius = 120.0, fillColor = Color.Green, strokeColor = Color.Green ) }


여기서는 각 마커에 대해 원을 설정합니다. 원을 만들려면 원의 위치와 반지름 크기를 전달해야 합니다. 또한 두 개의 선택적 매개변수를 사용하여 테두리 색상을 설정하고 원 색상을 채웁니다.



ScaleBar 표시

좋은 지도에는 지도상의 공간 측정이 거리와 얼마나 동일한지를 보여주는 범례와 도표가 함께 제공되는 경우가 많습니다. 모든 지도가 동일한 측정 형식을 사용하는 것은 아니기 때문에 이를 통해 지도와 관련된 공간에 대한 아이디어를 얻을 수 있습니다.


확대 및 축소할 수 있는 디지털 지도의 경우 표현된 거리가 동적으로 변경될 수 있으므로 특정 복잡성 계층이 추가됩니다. 다행히 Google Maps for Compose를 사용하면 됩니다.


위젯 라이브러리를 사용하면 DisappearingScaleBarScaleBar 컴포저블에 액세스할 수 있습니다. 이는 지도 상단에 위치하는 UI 구성요소로, 확대/축소 수준에 따라 변경되는 거리 측정값을 사용자에게 제공합니다.


 Box(contentAlignment = Alignment.Center) { GoogleMap( modifier = Modifier.fillMaxSize(), cameraPositionState = cameraPositionState ) { // You can also use ScaleBar DisappearingScaleBar( modifier = Modifier .padding(top = 5.dp, end = 15.dp) .align(Alignment.TopStart), cameraPositionState = cameraPositionState ) Clustering(items = parkMarkers, onClusterClick = { // Handle when the cluster is clicked false }, onClusterItemClick = { marker -> // Handle when a marker in the cluster is clicked selectedMarker = marker false }) } } parkMarkers.forEach { Circle( center = it.position, radius = 120.0, fillColor = Color.Green, strokeColor = Color.Green ) }


컴포저블을 추가하면 지도 상단의 확대/축소 수준에 따라 변경되는 ScaleBar가 표시됩니다.




도움말 및 지원

Compose용 Google 지도는 Google 지도를 사용하여 작업할 수 있는 좋은 방법이며 더 많은 것을 배울 수 있습니다. 도움이 필요하다면 제가 추천하는 몇 군데 장소는 다음과 같습니다.

  • Compose Repo용 Google 지도 : 라이브러리의 소스 코드가 포함된 저장소입니다. 라이브러리 사용 방법과 버그 보고서 및 기여를 제출할 수 있는 위치에 대한 코드 샘플이 포함되어 있습니다.
  • Android용 Google 지도 웹사이트 : Android용 Google 지도의 개념을 배울 수 있는 곳입니다. 이는 높은 수준이며 Compose 라이브러리를 사용하지 않지만 백그라운드에서 사용되므로 알아두는 것이 중요합니다.
  • Google Maps Platform Discord Google 지도의 공식 Discord 서버입니다. 여기에서는 다양한 플랫폼용 Google 지도에 대해 논의하고, 도움을 요청하고 제공하며, 자신의 작업을 선보이는 사람들을 찾을 수 있습니다.