---
title: "Google Maps no Jetpack Compose com Kotlin em 2026: mapa, marcadores e localização"
url: "https://kotlin.dev.br/blog/google-maps-compose-kotlin-localizacao-2026/"
markdown_url: "https://kotlin.dev.br/blog/google-maps-compose-kotlin-localizacao-2026.MD"
description: "Integre Google Maps no Jetpack Compose com Kotlin: configure a API key, exiba mapas, marcadores, câmera e localização com arquitetura e segurança."
date: "2026-09-06"
author: "Karina Melo"
---

# Google Maps no Jetpack Compose com Kotlin em 2026: mapa, marcadores e localização

Integre Google Maps no Jetpack Compose com Kotlin: configure a API key, exiba mapas, marcadores, câmera e localização com arquitetura e segurança.


**Resposta rápida:** para mostrar o **Google Maps no Jetpack Compose com Kotlin**, habilite o Maps SDK for Android no projeto do Google Cloud, restrinja a API key ao aplicativo Android, adicione as dependências oficiais e renderize um `GoogleMap` dentro de um Composable. Controle enquadramento com `CameraPositionState`, represente locais com `Marker` ou `MarkerInfoWindow` e mantenha os pontos exibidos no estado do ViewModel. Solicite localização somente se a funcionalidade realmente precisar da posição do aparelho; um mapa com endereços, lojas ou rotas planejadas pode funcionar sem essa permissão. Quando usar localização, obtenha coordenadas em uma camada separada, trate ausência de sinal e nunca confunda “última posição conhecida” com uma leitura atual.

Mapas aparecem em apps de entrega, mobilidade, turismo, eventos, imóveis, logística e busca de lojas. A primeira implementação costuma parecer simples: colocar o mapa na tela e adicionar um pin. Em produção, porém, surgem perguntas mais importantes: onde guardar a chave, quem controla a câmera, como evitar recomposições caras, o que fazer sem Google Play Services, como agrupar muitos pontos e quanto dado de localização deve sair do aparelho.

Este guia apresenta uma base moderna para Maps Compose, com configuração segura, mapa inicial, marcadores, eventos, posição do usuário, arquitetura, desempenho, testes e cuidados de privacidade. As APIs e os requisitos comerciais podem evoluir; confirme versões, faturamento e configuração na [documentação oficial do Maps SDK for Android](https://developers.google.com/maps/documentation/android-sdk/overview) e na [referência da biblioteca Maps Compose](https://github.com/googlemaps/android-maps-compose) antes de publicar.

## Como a integração é dividida

Uma tela de mapa normalmente combina componentes com responsabilidades diferentes:

| Componente | Responsabilidade |
|---|---|
| Maps SDK for Android | renderização do mapa e serviços da plataforma |
| Maps Compose | Composables e estado para integrar o mapa à UI Compose |
| ViewModel | estado de tela, seleção, filtros e carregamento dos pontos |
| Repositório | busca de lojas, entregas, imóveis ou outros itens do domínio |
| Provedor de localização | coordenadas do aparelho, quando necessárias |
| Google Cloud Console | ativação da API, credenciais, restrições e cotas |

`GoogleMap` deve renderizar o estado; ele não precisa consultar sua API REST nem decidir regras de negócio. Um marcador também não deve carregar um objeto gigante da camada de rede. Prefira transformar a resposta em um modelo pequeno e estável para a tela.

```kotlin
data class MapPoint(
    val id: String,
    val title: String,
    val subtitle: String?,
    val latitude: Double,
    val longitude: Double,
)

data class MapUiState(
    val points: List<MapPoint> = emptyList(),
    val selectedPointId: String? = null,
    val isLoading: Boolean = false,
    val message: String? = null,
)
```

Essa separação facilita testes, evita que detalhes do SDK vazem pelo domínio e segue os mesmos princípios do [guia de arquitetura MVVM com Kotlin](/guias/guia-arquitetura-mvvm-kotlin/).

## Configure o projeto no Google Cloud

Antes do código Android, crie ou selecione um projeto no Google Cloud e habilite o Maps SDK for Android. Dependendo dos serviços utilizados — mapa, Places, Routes ou geocodificação — outras APIs podem ser necessárias. Ative somente o que o produto usa e acompanhe as regras atuais de cobrança e cota.

Depois, crie uma API key e aplique duas restrições:

1. **restrição de aplicativo:** Android apps;
2. **restrição de API:** apenas as APIs necessárias para esse cliente.

Na restrição Android, associe o nome do pacote e a impressão digital SHA-1 do certificado. Cadastros de debug e release geralmente têm impressões diferentes. Se o app usa Play App Signing, confira também o certificado de assinatura gerenciado pelo Google Play; usar apenas a chave local é uma causa comum de mapa funcionar no debug e falhar após a publicação.

Uma API key de Maps usada pelo cliente Android não é um segredo capaz de permanecer invisível dentro do APK. A proteção prática vem das **restrições corretas**, da redução das APIs permitidas, do monitoramento de consumo e da rotação quando houver abuso. Não dependa apenas de ofuscação.

> **Importante:** não coloque credenciais de backend, tokens de usuário ou chaves sem restrição no manifesto. O cliente móvel é um ambiente não confiável.

## Injete a API key sem versioná-la

Uma abordagem comum é manter o valor local fora do Git e usar o Secrets Gradle Plugin for Android para gerar o placeholder consumido pelo manifesto. Confira a configuração vigente na [documentação de uso de API keys no Android](https://developers.google.com/maps/documentation/android-sdk/get-api-key).

No arquivo local que não será commitado:

```properties
# local.properties ou arquivo definido pelo projeto
MAPS_API_KEY=sua-chave-restrita
```

No manifesto, referencie o placeholder configurado pelo plugin:

```xml
<application
    android:name=".MapApplication"
    android:theme="@style/Theme.Exemplo">

    <meta-data
        android:name="com.google.android.geo.API_KEY"
        android:value="${MAPS_API_KEY}" />

</application>
```

Forneça um valor separado pelo ambiente de CI para builds de release. Não faça commit da chave real, não imprima o valor em logs e não reutilize a mesma credencial irrestrita em Android, iOS, web e servidor.

Para validar a restrição, instale uma build assinada como release em um dispositivo de teste. Uma configuração que funciona apenas via Android Studio ainda não está pronta para produção.

## Adicione as dependências

Use as versões estáveis recomendadas nas fontes oficiais no momento da implementação. Centralizar as versões em `libs.versions.toml` deixa atualizações e auditorias mais simples:

```toml
[versions]
mapsCompose = "<versao-estavel-atual>"
playServicesLocation = "<versao-estavel-atual>"

[libraries]
google-maps-compose = {
  module = "com.google.maps.android:maps-compose",
  version.ref = "mapsCompose"
}
google-play-services-location = {
  module = "com.google.android.gms:play-services-location",
  version.ref = "playServicesLocation"
}
```

```kotlin
// build.gradle.kts do módulo Android
dependencies {
    implementation(libs.google.maps.compose)

    // Adicione apenas se a tela realmente consultar a posição do aparelho.
    implementation(libs.google.play.services.location)
}
```

O artefato de localização não é obrigatório para exibir o mapa. Essa distinção ajuda a manter permissões e dependências proporcionais ao recurso.

Se seu projeto ainda espalha números de versão em vários módulos, o [guia de Gradle Version Catalog](/blog/gradle-version-catalog-kotlin-2026/) mostra uma organização mais sustentável.

## Mostre o primeiro mapa em Compose

O exemplo mínimo define uma posição inicial e renderiza o mapa:

```kotlin
@Composable
fun StoreMapScreen() {
    val initialPosition = LatLng(-23.5505, -46.6333)
    val cameraPositionState = rememberCameraPositionState {
        position = CameraPosition.fromLatLngZoom(initialPosition, 12f)
    }

    GoogleMap(
        modifier = Modifier.fillMaxSize(),
        cameraPositionState = cameraPositionState,
    )
}
```

`rememberCameraPositionState` preserva o controlador enquanto o Composable permanece na composição. Não recrie esse estado para cada mudança irrelevante da tela, pois isso pode reposicionar o mapa e interromper o gesto do usuário.

Defina a posição inicial a partir do contexto do produto, não obrigatoriamente da localização atual. Exemplos:

- cidade escolhida na busca;
- área atendida pelo serviço;
- enquadramento dos resultados retornados;
- último viewport salvo pelo próprio app;
- localização somente após consentimento e ação explícita.

Abrir diretamente uma solicitação de localização para centralizar um mapa que poderia começar no Brasil inteiro cria atrito sem benefício claro.

## Configure propriedades e controles do mapa

Separe propriedades visuais de configurações de interação:

```kotlin
@Composable
fun ConfiguredMap(
    cameraPositionState: CameraPositionState,
    locationEnabled: Boolean,
) {
    val properties by remember(locationEnabled) {
        mutableStateOf(
            MapProperties(
                isMyLocationEnabled = locationEnabled,
                mapType = MapType.NORMAL,
            )
        )
    }

    val uiSettings by remember {
        mutableStateOf(
            MapUiSettings(
                zoomControlsEnabled = false,
                compassEnabled = true,
                myLocationButtonEnabled = locationEnabled,
            )
        )
    }

    GoogleMap(
        modifier = Modifier.fillMaxSize(),
        cameraPositionState = cameraPositionState,
        properties = properties,
        uiSettings = uiSettings,
    )
}
```

Só defina `isMyLocationEnabled = true` depois de confirmar que a permissão foi concedida. Ativar a camada sem autorização pode causar falha. Além disso, o ponto azul é uma representação visual controlada pelo SDK; ele não substitui uma leitura de coordenadas para regras do seu domínio.

Ao colocar controles Compose sobre o mapa, reserve espaço para barras do sistema e outros elementos:

```kotlin
Box(Modifier.fillMaxSize()) {
    ConfiguredMap(
        cameraPositionState = cameraPositionState,
        locationEnabled = locationEnabled,
    )

    FloatingActionButton(
        onClick = onCenterRequested,
        modifier = Modifier
            .align(Alignment.BottomEnd)
            .navigationBarsPadding()
            .padding(16.dp),
    ) {
        Icon(
            imageVector = Icons.Default.MyLocation,
            contentDescription = "Centralizar na minha localização",
        )
    }
}
```

Um `contentDescription` específico é melhor que “botão de localização”. Para mais decisões de semântica e contraste, consulte o guia de [acessibilidade no Android com Compose](/blog/acessibilidade-android-compose-kotlin-2026/).

## Adicione marcadores a partir do estado

Para poucos pontos, renderizar um marcador por item é direto:

```kotlin
@Composable
fun PointsMap(
    points: List<MapPoint>,
    selectedPointId: String?,
    onPointSelected: (String) -> Unit,
) {
    val cameraPositionState = rememberCameraPositionState {
        position = CameraPosition.fromLatLngZoom(
            LatLng(-14.2350, -51.9253),
            3.5f,
        )
    }

    GoogleMap(
        modifier = Modifier.fillMaxSize(),
        cameraPositionState = cameraPositionState,
        onMapClick = { onPointSelected("") },
    ) {
        points.forEach { point ->
            val markerState = rememberUpdatedMarkerState(
                position = LatLng(point.latitude, point.longitude),
            )

            Marker(
                state = markerState,
                title = point.title,
                snippet = point.subtitle,
                alpha = if (point.id == selectedPointId) 1f else 0.85f,
                onClick = {
                    onPointSelected(point.id)
                    false
                },
            )
        }
    }
}
```

A API exata disponível pode variar conforme a versão do Maps Compose; ajuste `rememberUpdatedMarkerState` ou o construtor de estado conforme a documentação da versão adotada.

Use um ID de domínio para seleção. Não compare marcadores por título ou coordenadas: duas lojas podem ter o mesmo nome, e coordenadas podem sofrer arredondamento.

O retorno de `onClick` informa se seu código consumiu o evento. Retornar `false` permite o comportamento padrão, como abrir a info window; retornar `true` indica que a aplicação cuidou totalmente da ação. Escolha conscientemente para não criar um toque que seleciona o item, mas nunca mostra feedback.

## Sincronize mapa e lista sem criar loops

Telas de busca costumam combinar mapa e lista. Ao tocar em um card, a câmera deve ir ao ponto; ao tocar no marcador, o card correspondente deve ficar selecionado. Centralize o ID selecionado no ViewModel:

```kotlin
class MapViewModel(
    private val repository: PlacesRepository,
) : ViewModel() {

    private val _state = MutableStateFlow(MapUiState())
    val state: StateFlow<MapUiState> = _state.asStateFlow()

    fun selectPoint(id: String?) {
        _state.update { it.copy(selectedPointId = id) }
    }
}
```

A animação da câmera é um efeito de UI e pode permanecer na camada Compose:

```kotlin
val selectedPoint = uiState.points.firstOrNull {
    it.id == uiState.selectedPointId
}

LaunchedEffect(selectedPoint?.id) {
    selectedPoint ?: return@LaunchedEffect

    cameraPositionState.animate(
        CameraUpdateFactory.newLatLngZoom(
            LatLng(selectedPoint.latitude, selectedPoint.longitude),
            15f,
        ),
    )
}
```

Use o ID como chave do `LaunchedEffect`. Se você usar o estado inteiro ou uma lista recriada como chave, a animação pode reiniciar em recomposições e “brigar” com os gestos do usuário.

Também diferencie movimento programático de movimento manual quando a busca depende da área visível. Sem isso, o fluxo pode virar um ciclo:

1. resultado selecionado anima a câmera;
2. câmera parada dispara nova busca;
3. nova lista altera seleção;
4. câmera anima novamente.

Uma solução é pesquisar apenas após ação explícita — “Buscar nesta área” — ou aplicar debounce e comparar os limites geográficos antes de consultar o backend.

## Enquadre vários pontos com limites geográficos

Quando a API retorna diversos locais, um zoom fixo não serve para todas as distribuições. Calcule um `LatLngBounds`:

```kotlin
suspend fun fitPoints(
    cameraPositionState: CameraPositionState,
    points: List<MapPoint>,
    paddingPx: Int,
) {
    if (points.isEmpty()) return

    if (points.size == 1) {
        val point = points.first()
        cameraPositionState.animate(
            CameraUpdateFactory.newLatLngZoom(
                LatLng(point.latitude, point.longitude),
                15f,
            )
        )
        return
    }

    val bounds = LatLngBounds.builder().apply {
        points.forEach { point ->
            include(LatLng(point.latitude, point.longitude))
        }
    }.build()

    cameraPositionState.animate(
        CameraUpdateFactory.newLatLngBounds(bounds, paddingPx)
    )
}
```

Execute a atualização quando o mapa já tiver dimensões válidas. Se o cálculo ocorrer cedo demais, a operação de bounds pode falhar porque o SDK ainda não conhece o tamanho disponível. Na UI, dispare o efeito depois do layout ou utilize a estratégia recomendada pela versão atual da biblioteca.

Considere também o espaço ocupado por bottom sheets e barras. Enquadrar pontos no retângulo total e depois cobrir metade deles com uma lista deslizante produz um resultado tecnicamente correto, mas ruim para o usuário.

## Solicite localização apenas no momento necessário

Se a tela oferece “perto de mim” ou acompanhamento do usuário, peça a permissão quando a pessoa acionar o recurso. O guia de [permissões no Android com Kotlin](/blog/permissoes-android-kotlin-2026/) aprofunda rationale, negativa e diferenças entre versões.

No manifesto, localização aproximada pode ser suficiente para descoberta regional:

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

Declare `FINE` somente quando a precisão fizer diferença real. O usuário pode conceder apenas localização aproximada, e o recurso precisa degradar com honestidade.

Em Compose, modele a decisão como estado:

```kotlin
@Composable
fun LocationPermissionAction(
    hasLocationPermission: Boolean,
    onPermissionChanged: (Boolean) -> Unit,
    onLocate: () -> Unit,
) {
    val launcher = rememberLauncherForActivityResult(
        ActivityResultContracts.RequestMultiplePermissions()
    ) { result ->
        val granted = result[Manifest.permission.ACCESS_FINE_LOCATION] == true ||
            result[Manifest.permission.ACCESS_COARSE_LOCATION] == true
        onPermissionChanged(granted)
        if (granted) onLocate()
    }

    Button(
        onClick = {
            if (hasLocationPermission) {
                onLocate()
            } else {
                launcher.launch(
                    arrayOf(
                        Manifest.permission.ACCESS_COARSE_LOCATION,
                        Manifest.permission.ACCESS_FINE_LOCATION,
                    )
                )
            }
        }
    ) {
        Text("Usar minha localização")
    }
}
```

Não repita automaticamente o diálogo após uma negativa. Explique o benefício, ofereça busca manual e, quando apropriado, mostre uma ação para abrir as configurações.

## Obtenha uma posição do aparelho com segurança

A Fused Location Provider API pode fornecer uma posição recente ou solicitar uma leitura atual. Elas não significam a mesma coisa:

- **última localização:** rápida e econômica, mas pode estar ausente ou antiga;
- **localização atual:** tenta produzir uma leitura adequada dentro de critérios e tempo limitados;
- **atualizações contínuas:** indicadas somente para acompanhamento que permanece ativo.

Encapsule o SDK atrás de uma interface:

```kotlin
data class DeviceLocation(
    val latitude: Double,
    val longitude: Double,
    val accuracyMeters: Float?,
    val capturedAtMillis: Long,
)

interface DeviceLocationSource {
    suspend fun currentLocation(): Result<DeviceLocation>
}
```

Uma implementação conceitual pode usar `getCurrentLocation` e converter a `Task` para coroutine com o adaptador apropriado:

```kotlin
class FusedDeviceLocationSource(
    private val client: FusedLocationProviderClient,
    private val permissionChecker: LocationPermissionChecker,
) : DeviceLocationSource {

    override suspend fun currentLocation(): Result<DeviceLocation> = runCatching {
        check(permissionChecker.hasAnyLocationPermission()) {
            "Permissão de localização não concedida"
        }

        val cancellationTokenSource = CancellationTokenSource()

        val location = client.getCurrentLocation(
            Priority.PRIORITY_BALANCED_POWER_ACCURACY,
            cancellationTokenSource.token,
        ).await() ?: error("Localização indisponível")

        DeviceLocation(
            latitude = location.latitude,
            longitude = location.longitude,
            accuracyMeters = location.accuracy,
            capturedAtMillis = location.time,
        )
    }
}
```

A assinatura e as extensões de coroutine dependem das bibliotecas adotadas. Verifique a [documentação oficial de localização no Android](https://developer.android.com/develop/sensors-and-location/location) antes de copiar o exemplo.

A tela precisa tratar pelo menos:

- permissão ausente;
- localização desativada no aparelho;
- posição nula;
- timeout;
- coordenada com baixa precisão;
- dispositivo sem os serviços esperados;
- cancelamento quando a tela deixa de precisar do resultado.

Para uma busca de lojas, precisão balanceada costuma ser melhor que GPS máximo. Para navegação curva a curva, o problema é muito mais amplo que centralizar um mapa e requer desenho específico de consumo, foreground service e experiência em segundo plano.

## Muitos marcadores: agrupamento e janela visível

Centenas de marcadores individuais aumentam poluição visual e podem custar desempenho. Antes de otimizar código, reduza o conjunto exibido:

1. consulte apenas a área visível;
2. limite resultados por zoom;
3. agregue itens no backend quando fizer sentido;
4. use clustering para pontos próximos;
5. carregue detalhes somente após seleção.

As bibliotecas de utilitários e integrações opcionais do ecossistema Google Maps oferecem recursos de clustering. Confirme o artefato compatível com sua versão do Maps Compose. O importante é não transformar cada recomposição em reconstrução completa de milhares de objetos.

Mantenha listas imutáveis e IDs estáveis. Faça cálculos pesados — ordenação geográfica, conversão de modelos, criação de grupos — fora do corpo do Composable, preferencialmente no ViewModel ou em uma camada própria.

Evite também ícones de marcador gerados a partir de layouts complexos para cada frame. Cacheie recursos derivados quando a aparência não muda e teste em aparelhos intermediários, não apenas no dispositivo mais rápido da equipe.

## Rotas, Places e geocodificação são serviços separados

Mostrar um mapa não cria automaticamente rotas nem transforma texto em coordenadas. Trate cada necessidade como uma capacidade separada:

- **Places:** busca e detalhes de locais;
- **Routes:** cálculo de trajetos e informações relacionadas;
- **geocodificação:** conversão entre endereços e coordenadas;
- **Maps SDK:** mapa interativo no Android.

Cada serviço pode ter configuração, cota, preço, termos e restrições próprios. Não habilite tudo “para garantir”. Se uma operação exige credencial de servidor ou lógica sensível, faça-a no backend e aplique proteção adequada; não mova uma chave privilegiada para o APK.

Ao receber latitude e longitude da sua API, valide intervalos antes de criar `LatLng`:

```kotlin
fun MapPoint.hasValidCoordinates(): Boolean {
    return latitude in -90.0..90.0 &&
        longitude in -180.0..180.0 &&
        latitude.isFinite() &&
        longitude.isFinite()
}
```

Dados inválidos não devem derrubar a tela inteira. Remova o ponto problemático, registre telemetria sem dados pessoais desnecessários e permita que o restante do mapa continue disponível.

## Privacidade e LGPD

Localização pode revelar casa, trabalho, rotina, religião, saúde e relações pessoais. Mesmo quando o app não pretende inferir essas informações, um histórico detalhado pode permitir inferências. Aplique minimização desde o desenho:

- peça localização apenas para uma ação clara;
- prefira precisão aproximada quando suficiente;
- não envie coordenadas ao backend se o cálculo pode ocorrer no aparelho;
- não registre latitude e longitude completas em analytics;
- defina retenção e exclusão para dados armazenados;
- explique compartilhamento com terceiros;
- limite acesso interno e proteja dados em trânsito e repouso;
- não mantenha rastreamento contínuo depois que a tarefa terminou.

Eventos de analytics podem registrar “busca nesta área usada” ou “marcador selecionado”, sem anexar coordenadas exatas. Se uma métrica precisa de região, avalie agregação suficientemente ampla e documente a finalidade.

O [guia de LGPD para apps Android](/blog/lgpd-android-kotlin-2026/) oferece uma visão mais ampla de consentimento, minimização e ciclo de vida dos dados. A implementação técnica não substitui avaliação jurídica aplicável ao produto.

## Estado, rotação e restauração do processo

O mapa possui estado interno, mas seu produto também tem filtros, item selecionado e viewport relevante. Decida explicitamente o que deve sobreviver:

- ID do item selecionado: ViewModel e, se necessário, `SavedStateHandle`;
- filtros da busca: ViewModel;
- lista de resultados: recarregada pelo repositório ou cacheada conforme a arquitetura;
- posição da câmera: estado do mapa ou representação serializável quando a continuidade for importante;
- diálogo e mensagens temporárias: estado de UI consumível.

Não salve objetos do SDK ou listas enormes em `SavedStateHandle`. Para uma câmera, latitude, longitude, zoom e orientação já formam um estado pequeno:

```kotlin
data class SavedMapViewport(
    val latitude: Double,
    val longitude: Double,
    val zoom: Float,
    val bearing: Float,
    val tilt: Float,
)
```

Restaurar o viewport não significa sempre mover a câmera. Se a tela foi aberta por um [App Link ou deep link](/blog/app-links-deep-links-android-kotlin-2026/), o destino explícito deve ter prioridade sobre a posição antiga.

## Teste sem depender apenas do mapa real

Testes unitários não precisam renderizar tiles. Extraia regras puras e valide-as separadamente:

```kotlin
class SelectVisiblePointsTest {
    @Test
    fun `remove pontos com coordenadas invalidas`() {
        val points = listOf(
            MapPoint("ok", "Loja", null, -23.5, -46.6),
            MapPoint("bad", "Inválida", null, 120.0, -46.6),
        )

        val valid = points.filter(MapPoint::hasValidCoordinates)

        assertEquals(listOf("ok"), valid.map { it.id })
    }
}
```

Também teste:

- seleção de marcador por ID;
- regra de filtros;
- erro e retry do repositório;
- permissão negada;
- localização nula ou antiga;
- comportamento com lista vazia;
- escolha de ponto único versus bounds;
- cancelamento da leitura de localização.

Nos testes de UI, substitua a tela do mapa por uma abstração ou semântica observável quando o objetivo for testar fluxo de produto. Reserve testes instrumentados com o SDK real para um conjunto menor: inicialização, credencial de release, gestos principais, marcador, info window e camadas sobrepostas.

Antes de publicar, execute uma matriz manual:

| Cenário | Resultado esperado |
|---|---|
| API key inválida | estado diagnosticável; app não fecha |
| sem internet | UI explica indisponibilidade ou mantém dados já carregados |
| permissão negada | mapa funciona sem posição do usuário |
| localização desligada | busca manual continua disponível |
| apenas localização aproximada | recurso aceita menor precisão ou explica limitação |
| zero resultados | mapa e estado vazio não entram em loop |
| centenas de pontos | clustering ou redução mantém interação fluida |
| rotação/processo recriado | filtros e seleção importante são restaurados |
| build da loja | mapa autentica com o certificado correto |

## Erros comuns

### Versionar a API key sem restrição

Esconder o valor em um arquivo Kotlin não resolve. Retire-o do histórico quando necessário, restrinja a credencial por aplicativo e API e monitore uso anormal.

### Pedir localização assim que a tela abre

O mapa pode funcionar sem a posição do usuário. Peça acesso depois que a pessoa escolher “perto de mim” ou outra função que explique o benefício.

### Usar a última posição como se fosse atual

Ela pode ter sido capturada horas atrás. Verifique idade e precisão ou solicite uma leitura atual com timeout compatível com a tarefa.

### Animar a câmera em toda recomposição

Coloque animações em efeitos com chaves estáveis. Caso contrário, o mapa volta ao ponto selecionado enquanto o usuário tenta arrastá-lo.

### Fazer uma chamada de rede para cada movimento

A câmera emite muitas mudanças. Use gesto concluído, botão “Buscar nesta área”, debounce e comparação do viewport para controlar consultas.

### Renderizar milhares de pins

Filtre pela área visível e pelo zoom, agrupe pontos e carregue detalhes sob demanda.

### Guardar localização exata em analytics

Métricas de produto raramente precisam de coordenadas completas. Colete eventos sem dados pessoais desnecessários e aplique a política de retenção.

### Validar somente no debug

A chave pode estar associada apenas ao certificado de debug. Teste o artefato assinado e a configuração do Play App Signing antes do lançamento.

## Checklist de produção

Antes de liberar uma tela com Google Maps e Kotlin:

- [ ] Maps SDK e somente APIs necessárias estão habilitados;
- [ ] API key está restrita ao pacote, certificados e APIs corretos;
- [ ] credenciais privilegiadas permanecem no backend;
- [ ] build de release foi testada em dispositivo real;
- [ ] mapa funciona sem localização quando possível;
- [ ] permissão é solicitada em contexto e aceita modo aproximado;
- [ ] posição nula, antiga e imprecisa tem tratamento;
- [ ] câmera não reinicia durante recomposições;
- [ ] consultas por viewport têm debounce ou ação explícita;
- [ ] marcadores usam IDs estáveis e coordenadas validadas;
- [ ] grande volume usa redução ou clustering;
- [ ] filtros e seleção sobrevivem à recriação necessária;
- [ ] logs e analytics não expõem coordenadas exatas;
- [ ] loading, erro, vazio e modo offline têm UI definida;
- [ ] acessibilidade dos controles sobrepostos foi revisada;
- [ ] cotas, faturamento e alertas de consumo estão configurados.

## Perguntas frequentes

### Preciso pedir permissão para mostrar o Google Maps em Compose?

Não. Exibir um mapa, marcadores e áreas definidas pela aplicação não exige acesso à localização do aparelho. A permissão é necessária quando o recurso consulta ou mostra a posição do usuário.

### Posso colocar a API key do Google Maps no APK?

Uma chave de cliente Android será distribuída com o aplicativo e não pode ser tratada como segredo absoluto. Restrinja-a ao pacote, aos certificados de assinatura e às APIs necessárias. Chaves privilegiadas de servidor não devem ir para o APK.

### `isMyLocationEnabled` fornece as coordenadas do usuário?

Não. Essa propriedade controla a camada visual de localização do mapa. Para usar coordenadas em uma regra do app, consulte uma API de localização com permissão, tratamento de erro e critérios de precisão.

### Como evitar que o mapa volte sozinho ao marcador?

Não execute `animate` diretamente no corpo do Composable. Use `LaunchedEffect` com uma chave estável, como o ID selecionado, e não atualize a seleção em resposta a todo movimento da câmera.

### Marker clustering é obrigatório?

Não para poucos pontos. Ele se torna útil quando marcadores próximos se sobrepõem, prejudicam a leitura ou tornam a interação pesada. Antes disso, limite os dados à janela visível e ao nível de zoom.

### Maps Compose substitui o ViewModel?

Não. A biblioteca oferece UI e estado específico do mapa. Resultados, filtros, seleção, erros e regras de domínio continuam pertencendo a uma arquitetura de aplicação testável.

## Próximos passos

Comece pela menor entrega útil: mapa sem permissão, um conjunto pequeno de pontos vindos do ViewModel e seleção por ID. Depois valide a chave na build de release, acrescente enquadramento e somente então implemente “perto de mim” se houver uma necessidade real.

Uma boa tela de mapa não é a que reúne todas as APIs geográficas. É a que mantém a tarefa clara quando a rede falha, a localização está desativada ou o usuário prefere não conceder acesso. Com credenciais restritas, estado previsível, consultas controladas e privacidade por padrão, o Google Maps no Jetpack Compose deixa de ser uma demonstração visual e vira uma parte confiável do app Android.
