paint-brush
Transformando mapas móveis com Jetpack Compose: insights do Google I/O para desenvolvedorespor@darrylbayliss
Novo histórico

Transformando mapas móveis com Jetpack Compose: insights do Google I/O para desenvolvedores

por Darryl Bayliss22m2024/07/09
Read on Terminal Reader

Muito longo; Para ler

O Jetpack Compose for Maps transformou o desenvolvimento moderno do Android, tornando o mapeamento móvel mais fácil e eficiente. Descubra as principais técnicas e dicas do Google I/O Extended 2023 sobre como integrar o Google Maps aos seus projetos do Jetpack Compose.
featured image - Transformando mapas móveis com Jetpack Compose: insights do Google I/O para desenvolvedores
Darryl Bayliss HackerNoon profile picture
0-item



Esta postagem é baseada em uma palestra proferida no Google I/O Extended para o Google Developers Group Londres em julho de 2023. Os slides da palestra estão disponíveis aqui

Jetpack Compose para mapas

É difícil imaginar que o Jetpack Compose 1.0 foi lançado em julho de 2021 . Dois anos depois, com 24% dos 1.000 principais aplicativos do Google Play adotando o Compose, é fácil entender o porquê.



Entre toda a empolgação, um aspecto do desenvolvimento moderno do Android que sinto que recebe pouca atenção é o Google Maps. Já faz um tempo que não uso o SDK, então fiquei agradavelmente surpreso ao ver que o Google Maps estava se atualizando e lançou sua própria biblioteca Compose .


Esta será uma boa notícia para empresas e engenheiros que trabalham na área de mapeamento. O mapeamento móvel é uma indústria de US$ 35,5 bilhões, com previsões de que aumentará para US$ 87,7 bilhões até 2028, a uma taxa composta de crescimento anual (CAGR) de 19,83%. Fonte


Gráfico de mercados em crescimento


Por que isso é importante? Um mercado maior significa mais oportunidades para as empresas obterem receitas a partir de aplicações de mapeamento móvel. Eles variam desde os casos de uso usuais, alimentos, entrega de alimentos e serviços de carona. Porém, se você se aprofundar, verá que existem aplicações que não são imediatamente óbvias.


Abaixo estão os exemplos que consegui encontrar após uma breve pesquisa:


Áreas de mercado em crescimento Os mapas móveis são excelentes para Cidades Inteligentes, ajudando a gerir o ritmo cardíaco de uma cidade e a visualizar dados de forma a melhor compreender e reagir aos seus desafios. Útil para planejadores urbanos, organizações de resposta a emergências ou residentes comuns.


A gestão de recursos também se beneficia das soluções de mapeamento. Variando da agricultura à pesca, da mineração à silvicultura, os mapas fornecem aos que trabalham nesta linha de trabalho uma perspectiva para tomar as decisões corretas para colher materiais de forma sustentável.


O transporte depende fortemente da tecnologia de mapeamento. Não apenas aplicativos de consumo, como Google Maps ou Uber, mas funções de nível empresarial, como entender onde está localizada a frota de veículos de uma empresa. As agências de transporte também usam mapas para gerenciar o tráfego e ajudar a tomar decisões sobre para onde direcionar o tráfego para facilitar o fluxo.


Finalmente, com as alterações climáticas e o tempo cada vez mais imprevisível, os mapas permitem que as agências meteorológicas, as unidades de resposta a emergências e os conservacionistas da vida selvagem compreendam como o nosso mundo está a mudar e o que podemos fazer para tomar medidas positivas para reduzir esta situação.

Fontes:Mordor Intelligence , GMInsights , Allied Market Research , EMR Research , Google Earth Outreach , Pesquisa e Mercados


Com o mundo fornecendo cada vez mais dados, é um bom momento para aprender como colocar esses dados em um mapa. Vamos fazer isso e voltar ao código.


Usando o Google Maps para Compose

O Google Maps para Compose depende das seguintes dependências:

 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" }


O Google Maps para Compose foi desenvolvido com base no SDK do Google Maps, portanto, você precisa importar a biblioteca do Compose e o SDK do Maps. Você não precisará usar a maioria dos objetos no SDK do Google Maps, pois a biblioteca de composição agrupa a maioria deles em elementos que podem ser compostos.


As bibliotecas de utilitários e widgets são uma dependência opcional. A biblioteca de utilitários fornece a capacidade de agrupar marcadores nos mapas, enquanto os widgets fornecem componentes de UI adicionais. Você os verá em uso mais tarde.


Para esta postagem, incluí a biblioteca de solicitações de permissões do Accompanist para demonstrar como solicitar permissões de localização, uma permissão frequentemente usada em mapas. Accompanist é uma biblioteca experimental para o Google testar e coletar feedback sobre recursos que ainda não fazem parte do Jetpack Compose.


Por fim, você precisa acessar o Google Developer Console , inscrever-se para obter uma chave de API do SDK do Google Maps e adicioná-la ao seu projeto. Há um guia nos documentos para desenvolvedores do Google Maps sobre como fazer isso.


Dica de segurança: no Google Developer Console, bloqueie sua chave de API para que ela funcione apenas com seu aplicativo. Isso evita qualquer uso não autorizado.


Mostrando um mapa

Mostrar um mapa é tão simples quanto abaixo:


 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" ) } }


Crie um objeto LatLng com a posição de uma área e use-o em conjunto com rememberCameraPositionState para definir a posição inicial da câmera. Este método lembra a posição do mapa conforme você se move usando as mãos ou de forma programática. Sem esse método, o Compose recalcularia o mapa de volta à sua posição inicial a cada mudança de estado.


Em seguida, crie uma composição GoogleMap e passe um modificador de sua escolha e o estado da câmera. GoogleMap também fornece uma API Slot para passar elementos que podem ser compostos adicionais. Esses elementos que podem ser compostos são o que você deseja desenhar no mapa.


Adicione um Marker que pode ser composto e, em seguida, adicione um MarkerState contendo a posição do marcador dentro dele. Por fim, adicione um título e uma descrição do marcador.


Executar isto dá uma bela vista aérea do oeste de Londres com um marcador no Hyde Park.


Marcador no Hyde Park

Personalizando a janela do marcador

Você pode personalizar a janela do marcador usando um MarkerInfoWindowContent Composable. Isso também tem uma API baseada em slot, o que significa que você pode passar seus elementos que podem ser compostos para renderizar sua IU personalizada na janela.


 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" ) } } } }


Executar isso mostra a janela personalizada acima do marcador quando você toca nele.


Marcador de janela personalizado no Hyde Park

Mostrando vários marcadores

Mostrar vários marcadores é tão simples quanto adicionar quantos você precisar. Vamos adicionar marcadores para alguns parques diferentes no 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" ) } }


Execute o código e você verá seus marcadores aparecerem no mapa.


Vários marcadores de parque


Marcadores de agrupamento

Um mapa pode ficar ocupado em um curto espaço de tempo. Se você estiver tentando mostrar 300 marcadores, será visualmente difícil para o usuário entender o que está acontecendo. O Google Maps e seu dispositivo também não agradecerão, pois terão que renderizar todos os marcadores , impactando o desempenho e a vida útil da bateria.


A solução para isso é o Clustering , uma técnica que agrupa marcadores próximos uns dos outros em um único marcador. Esse agrupamento acontece em nível de zoom. À medida que você diminui o zoom do mapa, os marcadores serão agrupados em um cluster, à medida que você aumenta o zoom, o cluster será dividido em marcadores individuais.


O Google Maps para Compose fornece isso imediatamente por meio de um Clustering que pode ser composto. Não há necessidade de escrever classificações ou filtros complexos para que o clustering ocorra.


 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 a classe de dados ParkItem adicionada. Precisamos disso porque os itens passados para um elemento que pode ser composto Clustering devem estar em conformidade com a interface ClusterItem . A interface fornece ao Cluster a posição, o título e o snippet de cada marcador.

Aumente e diminua o zoom e você verá o agrupamento em ação.


Obtendo permissão de localização

Os mapas e a posição do usuário geralmente andam de mãos dadas, por isso faz sentido que alguns aplicativos de mapeamento peçam permissão para a localização do usuário.

Trate a permissão de um usuário com respeito. Se você fizer isso, a permissão de localização é uma das permissões mais confidenciais para adquirir de um usuário.


Certifique-se de informar ao usuário por que você precisa dessa permissão e mostrar ativamente os benefícios de concedê-la. Pontos de bônus se seu aplicativo funcionar parcialmente sem a necessidade de permissão.


O Google fornece ótimos guias sobre como lidar com a localização dos usuários , bem como um guia separado para acessar dados de localização em segundo plano .


Então você fez a devida diligência e decidiu que precisa da permissão do usuário para acessar o local. Com a biblioteca de permissões no Accompanist, você faz assim:


 // 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") } } }


Via acompanhante, estamos verificando se o aplicativo tem acesso à permissão ACCESS_FINE_LOCATION , ou um alto nível de precisão do GPS em inglês. É importante incluir as permissões solicitadas no manifesto do Android, pois de outra forma você não poderá solicitá-las.


O sistema Android e a Google Play Store também utilizam o manifesto para entender como seu app funciona e informar os usuários.

Se a permissão não for concedida, uma pequena caixa de diálogo que pode ser composta será exibida explicando a necessidade da permissão e um botão para iniciar a solicitação de permissão por meio do sistema.



Animando o mapa

Embora a maioria dos aplicativos de mapas exija que o usuário mova o mapa por meio do toque, o Google Maps for Compose fornece APIs para mover o mapa de forma programática. Isto pode ser útil se você quiser navegar para uma área específica em resposta a um evento.


Neste exemplo, navegaremos suavemente no aplicativo por nossa coleção 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 } } } }


A parte principal aqui é o código dentro do LaunchedEffect . Para cada marcador, o aplicativo configura uma chamada cameraPositionState.animate() para navegar até o marcador. A câmera recebe essas informações por meio de uma atualização da câmera, criada usando CameraUpdateFactory.newLatLngZoom() .


Este método utiliza um LatLng , um float que indica o nível de zoom do mapa e um long para definir a duração da animação.

Finalmente, para espaçar as animações usamos delay() para adicionar uma pausa de 4 segundos entre cada animação.


Mostrando o Street View

Não é apenas um mapa aéreo com o qual o Google Maps for Compose ajuda você. Você também pode conceder aos aplicativos acesso ao Street View , mostrando uma visão 360º de um local. Você faz isso usando o elemento que pode ser composto 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 }) } } }


Neste exemplo, estamos definindo a variável selectedMarker sempre que um marcador é tocado. Se um marcador for selecionado mostramos o Street View, passando na posição do marcador.



Desenhando formas

Você pode desenhar suas próprias formas e anotações no mapa. O Google Maps para Compose fornece vários elementos que podem ser compostos para fazer isso. Nesta postagem, usaremos o elemento que pode ser composto Circle .


Um círculo é uma boa forma de usar se seu aplicativo usar cercas geográficas para reagir às alterações na localização de um usuário. Um círculo pode representar a área em que uma cerca geográfica está ativa.


 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 ) }


Aqui, montamos um círculo para cada um dos nossos marcadores. Criar um círculo envolve passar a ele uma posição e o tamanho do raio do círculo. Também usamos dois parâmetros opcionais para definir a cor da borda e preencher a cor do círculo.



Mostrando uma ScaleBar

Um bom mapa geralmente vem com legendas e diagramas mostrando a que medida de espaço no mapa é equivalente em distância. Isso dá uma ideia dos espaços envolvidos no mapa, pois nem todos os mapas podem utilizar a mesma forma de medição.


Para mapas digitais que podem aumentar e diminuir o zoom, isso adiciona uma camada específica de complexidade, pois as distâncias representadas podem mudar dinamicamente. Felizmente, o Google Maps para Compose ajuda você.


Usando a biblioteca Widgets, você obtém acesso aos elementos que podem ser compostos DisappearingScaleBar e ScaleBar . Esses são componentes de IU localizados na parte superior do mapa, fornecendo aos usuários uma medida de distância que muda dependendo do nível 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 ) }


Depois de adicionar o elemento que pode ser composto, você obtém um ScaleBar que muda dependendo do nível de zoom na parte superior do mapa.




Ajuda e suporte

O Google Maps para Compose é uma ótima maneira de trabalhar com o Google Maps e há muito mais para aprender. Aqui estão alguns lugares que recomendo se você precisar de ajuda:

  • Google Maps for Compose Repo : o repositório que contém o código-fonte da biblioteca. Contém exemplos de código sobre como usar a biblioteca e também onde você pode enviar seus relatórios de bugs e contribuições
  • Site do Google Maps para Android : o lugar para aprender os conceitos por trás do Google Maps para Android. Eles são de alto nível e não usam a biblioteca Compose, mas são importantes de serem conhecidos, pois são usados em segundo plano.
  • Discord da plataforma Google Maps O servidor Discord oficial do Google Maps. Aqui você encontra pessoas discutindo sobre o Google Maps para diversas plataformas, pedindo e oferecendo ajuda e apresentando seus próprios trabalhos.