Esta publicación se basa en una charla impartida en Google I/O Extended para Google Developers Group en Londres en julio de 2023. Las diapositivas de la charla están disponibles aquí.
Es difícil imaginar que Jetpack Compose 1.0 se lanzara en julio de 2021 . Dos años después, y con el 24% de las 1000 aplicaciones principales en Google Play adoptando Compose, es fácil entender por qué.
Entre todo el entusiasmo, un rincón del desarrollo moderno de Android que creo que recibe poca atención es Google Maps. Ha pasado un tiempo desde que usé el SDK, por lo que me sorprendió gratamente ver que Google Maps se estaba poniendo al día y lanzó su propia biblioteca Compose .
Esta será una buena noticia para las empresas y los ingenieros que trabajan en el sector cartográfico. La cartografía móvil es una industria de 35.500 millones de dólares, y se prevé que aumentará a 87.700 millones de dólares en 2028 con una tasa de crecimiento anual compuesta (CAGR) del 19,83%. Fuente
¿Porque es esto importante? Un mercado más grande significa más oportunidades para que las empresas obtengan ingresos de las aplicaciones de cartografía móvil. Estos van desde los casos de uso habituales, alimentos, entrega de comestibles y servicios de transporte compartido. Sin embargo, si profundizas, hay aplicaciones que no son inmediatamente obvias.
A continuación se muestran los ejemplos que pude encontrar después de una breve búsqueda:
Los mapas móviles son excelentes para las ciudades inteligentes, ya que ayudan a gestionar los latidos de una ciudad y a visualizar datos para comprender y reaccionar mejor ante sus desafíos. Útil para planificadores urbanos, organizaciones de respuesta a emergencias o residentes cotidianos.
La gestión de recursos también se beneficia de las soluciones cartográficas. Los mapas, que van desde la agricultura hasta la pesca, desde la minería hasta la silvicultura, brindan a quienes trabajan en esta línea una perspectiva para tomar las decisiones correctas para cosechar materiales de manera sostenible.
El transporte depende en gran medida de la tecnología cartográfica. No solo aplicaciones para consumidores como Google Maps o Uber, sino funciones a nivel empresarial como comprender dónde se encuentra la flota de vehículos de una empresa. Las agencias de transporte también utilizan mapas para gestionar el tráfico y ayudar a tomar decisiones sobre hacia dónde dirigir el tráfico para facilitar el flujo.
Finalmente, dado que el cambio climático y el tiempo son cada vez más impredecibles, los mapas permiten a las agencias meteorológicas, las unidades de respuesta a emergencias y a los conservacionistas de la vida silvestre comprender cómo está cambiando nuestro mundo y qué podemos hacer para tomar medidas positivas para reducirlo.
Fuentes:Mordor Intelligence , GMInsights , Allied Market Research , EMR Research , Google Earth Outreach , Research & Markets
Dado que el mundo proporciona cada vez más datos, es un buen momento para aprender a poner esos datos en un mapa. Hagamos eso y volvamos al código.
Google Maps para Compose se basa en las siguientes dependencias:
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" }
Google Maps para Compose se basa en el SDK de Google Maps, por lo que debe importar la biblioteca de Compose y el SDK de Maps. No necesitarás usar la mayoría de los objetos en el SDK de Google Maps, ya que la biblioteca de redacción incluye la mayoría de ellos en Composables.
Las bibliotecas de utilidades y widgets son una dependencia opcional. La biblioteca de utilidades ofrece la posibilidad de agrupar marcadores en los mapas, mientras que los widgets proporcionan componentes de interfaz de usuario adicionales. Los verás en uso más adelante.
Para esta publicación, he incluido la biblioteca de solicitud de permisos de Accompanist para demostrar cómo solicitar permisos de ubicación, un permiso de uso frecuente con mapas. Accompanist es una biblioteca experimental para que Google pruebe y recopile comentarios sobre funciones que aún no forman parte de Jetpack Compose.
Finalmente, debe ir a Google Developer Console , registrarse para obtener una clave API del SDK de Google Maps y agregarla a su proyecto. Hay una guía en los documentos para desarrolladores de Google Maps sobre cómo hacer esto.
Consejo de seguridad: en Google Developer Console, bloquee su clave API para que solo funcione con su aplicación. Esto evita cualquier uso no autorizado.
Mostrar un mapa es tan sencillo como se muestra a continuación:
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" ) } }
Cree un objeto LatLng
con la posición de un área y utilícelo junto con rememberCameraPositionState
para establecer la posición inicial de la cámara. Este método recuerda la posición del mapa a medida que se mueve usando las manos o mediante programación. Sin este método, Compose volvería a calcular el mapa a su posición inicial en cada cambio de estado.
A continuación, cree una redacción GoogleMap
y pase un modificador de su elección y el estado de la cámara. GoogleMap
también proporciona una API de ranura para pasar elementos componibles adicionales, estos elementos componibles son los que desea dibujar en el mapa.
Agregue un Marker
componible y luego agregue un MarkerState
que contenga la posición del marcador en su interior. Finalmente, agregue un título y una descripción del marcador.
Ejecutar esto ofrece una bonita vista aérea del oeste de Londres con un marcador en Hyde Park.
Puede personalizar la ventana del marcador utilizando MarkerInfoWindowContent
Composable. Esto también tiene una API basada en ranuras, lo que significa que puede pasar sus elementos componibles para representar su interfaz de usuario personalizada en la ventana.
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" ) } } } }
Al ejecutar esto, se muestra la ventana personalizada sobre el marcador cuando lo tocas.
Mostrar varios marcadores es tan sencillo como añadir tantos como necesites. Agreguemos marcadores para algunos parques diferentes en el oeste de Londres.
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" ) } }
Ejecute el código y verá sus marcadores aparecer en el mapa.
Un mapa puede llenarse en poco tiempo. Si intenta mostrar 300 marcadores, al usuario le resultará visualmente difícil entender lo que está sucediendo. Google Maps y tu dispositivo tampoco te lo agradecerán, ya que tendrá que representar cada marcador , lo que afectará el rendimiento y la duración de la batería.
La solución a esto es el Clustering , una técnica que agrupa marcadores cercanos entre sí en un solo marcador. Esta agrupación ocurre a nivel de zoom. A medida que alejas el mapa, los marcadores se agruparán en un grupo; a medida que acercas el mapa, el grupo se dividirá en marcadores individuales.
Google Maps for Compose proporciona esto de forma inmediata a través de un elemento componible Clustering
. No es necesario escribir una clasificación o filtrado complejos para que se produzca la agrupación.
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 }
Observe que se agregó la clase de datos ParkItem
. Necesitamos esto porque los elementos pasados a un elemento componible Clustering
deben ajustarse a la interfaz ClusterItem
. La interfaz proporciona al clúster la posición, el título y el fragmento de cada marcador.
Acérquese y aléjese y verá la agrupación en acción.
Los mapas y la posición del usuario suelen ir de la mano, por lo que tiene sentido que algunas aplicaciones de mapas soliciten permiso para conocer la ubicación del usuario.
Trate el permiso de un usuario con respeto si hace esto, el permiso de ubicación es uno de los permisos más sensibles que se pueden adquirir de un usuario.
Asegúrese de informar al usuario por qué necesita este permiso y muestre activamente los beneficios de otorgarlo. Puntos de bonificación si su aplicación funciona parcialmente sin necesidad de ningún permiso.
Google proporciona algunas guías excelentes sobre cómo manejar la ubicación de los usuarios , así como una guía separada para acceder a los datos de ubicación en segundo plano .
Entonces hizo su debida diligencia y decidió que necesita el permiso del usuario para acceder a la ubicación. Con la biblioteca de permisos en Accompanist, lo haces así:
// 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") } } }
A través del acompañante, estamos verificando si la aplicación tiene acceso al permiso ACCESS_FINE_LOCATION
o un alto nivel de precisión de GPS en inglés. Es importante incluir los permisos solicitados en el manifiesto de Android, ya que de lo contrario no podrá solicitarlos.
El sistema Android y la tienda Google Play también utilizan el manifiesto para comprender cómo funciona su aplicación e informar a los usuarios.
Si no se concede el permiso, se muestra un pequeño cuadro de diálogo componible que explica la necesidad del permiso y un botón para iniciar la solicitud de permiso a través del sistema.
Si bien la mayoría de las aplicaciones de mapas requieren que el usuario mueva el mapa mediante el tacto, Google Maps for Compose proporciona API para mover el mapa mediante programación. Esto puede resultar útil si desea navegar a un área específica en respuesta a un evento.
En este ejemplo, navegaremos suavemente por la aplicación a través de nuestra colección de marcadores.
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 } } } }
La parte clave aquí es el código dentro de LaunchedEffect
. Para cada marcador, la aplicación configura una llamada cameraPositionState.animate()
para navegar hasta el marcador. La cámara recibe esta información a través de una actualización de la cámara, creada usando CameraUpdateFactory.newLatLngZoom()
.
Este método toma un LatLng
, un flotante que indica el nivel de zoom del mapa y un largo para establecer la duración de la animación.
Finalmente, para espaciar las animaciones usamos delay()
para agregar una pausa de 4 segundos entre cada animación.
No es solo un mapa aéreo con el que Google Maps for Compose te ayuda. También puedes dar acceso a las aplicaciones a Street View , que muestra una vista de 360 grados de una ubicación. Para ello, utiliza el elemento componible 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 }) } } }
En este ejemplo, configuramos la variable selectedMarker
cada vez que se toca un marcador. Si se selecciona un marcador mostramos Street View, pasando en la posición del marcador.
Quizás quieras dibujar tus propias formas y anotaciones en el mapa. Google Maps for Compose proporciona una serie de elementos componibles para hacer esto; en esta publicación usaremos el elemento componible Circle
.
Un círculo es una buena forma si su aplicación usa Geocercas para reaccionar a los cambios desde la ubicación de un usuario. Un círculo puede representar el área dentro de la cual está activa una geocerca.
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 ) }
Aquí, configuramos un círculo para cada uno de nuestros marcadores. Crear un círculo implica pasarle una posición y el tamaño del radio del círculo. También usamos dos parámetros opcionales para establecer el color del borde y rellenar el color del círculo.
Un buen mapa a menudo viene con leyendas y diagramas que muestran a qué equivale una medida de espacio en el mapa en distancia. Esto le da una idea de los espacios involucrados en el mapa, ya que no todos los mapas pueden utilizar la misma forma de medición.
Para los mapas digitales que pueden acercarse y alejarse, esto añade una capa particular de complejidad ya que las distancias representadas pueden cambiar dinámicamente. Afortunadamente, Google Maps para Compose lo tiene cubierto.
Al utilizar la biblioteca de widgets, obtienes acceso a los elementos componibles DisappearingScaleBar
y ScaleBar
. Estos son componentes de la interfaz de usuario que se encuentran en la parte superior del mapa y brindan a los usuarios una medida de distancia que cambia según el nivel de zoom.
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 ) }
Después de agregar el elemento componible, obtienes una ScaleBar que cambia según el nivel de zoom en la parte superior del mapa.
Google Maps para Compose es una excelente manera de trabajar con Google Maps y hay mucho más que aprender. Aquí hay algunos lugares que recomiendo si necesita ayuda: