Geolocalização com Capacitor no Ionic: coordenadas, mapas e boas práticas

Capítulo 15

Tempo estimado de leitura: 10 minutos

+ Exercício

O que é geolocalização no Ionic com Capacitor

Geolocalização é a capacidade do app obter a posição aproximada do dispositivo (latitude/longitude) usando fontes como GPS, Wi‑Fi e rede celular. No Ionic com Capacitor, o acesso é feito via plugin @capacitor/geolocation, que abstrai as diferenças entre Android, iOS e Web.

Em apps móveis, geolocalização envolve três pontos essenciais: permissões (o usuário precisa autorizar), estratégia de obtenção (uma leitura única vs. monitoramento contínuo) e tratamento de falhas (GPS desligado, permissão negada, timeout, indisponibilidade).

Localização única vs. monitoramento (watch)

  • Localização única (getCurrentPosition): pega uma coordenada “agora” e encerra. É a melhor opção para a maioria dos casos (ex.: preencher endereço, marcar ponto no mapa, calcular distância pontual).
  • Monitoramento (watchPosition): fica recebendo atualizações conforme o usuário se move. Útil para navegação, tracking, corrida/ciclismo, entregas em tempo real.

Impacto em bateria e precisão

Quanto maior a precisão, maior o consumo de bateria. Em geral:

  • Alta precisão (enableHighAccuracy: true) tende a acionar GPS com mais frequência: melhor para navegação, pior para bateria.
  • Baixa/média precisão pode usar rede/Wi‑Fi: suficiente para “aproximado”, menor consumo.
  • Monitoramento contínuo é o que mais consome bateria, especialmente com alta precisão e intervalos curtos.

Instalação e permissões

1) Instalar o plugin

npm i @capacitor/geolocation

Em seguida, sincronize com as plataformas (quando estiver usando Android/iOS):

npx cap sync

2) Configurar permissões (visão prática)

O Capacitor cuida de boa parte, mas você precisa garantir que o app declare permissões no projeto nativo.

Continue em nosso aplicativo e ...
  • Ouça o áudio com a tela desligada
  • Ganhe Certificado após a conclusão
  • + de 5000 cursos para você explorar!
ou continue lendo abaixo...
Download App

Baixar o aplicativo

  • Android: normalmente envolve permissões como ACCESS_COARSE_LOCATION e/ou ACCESS_FINE_LOCATION no AndroidManifest.xml. Para monitoramento em segundo plano, há exigências adicionais (não recomendado para iniciantes sem necessidade real).
  • iOS: é necessário declarar mensagens de uso no Info.plist (ex.: “Precisamos da sua localização para mostrar sua posição no mapa”). Sem isso, o app pode falhar ao solicitar permissão.

Mesmo com declarações corretas, o usuário pode negar. Por isso, o app deve sempre ter fallback e mensagens claras.

Passo a passo: obter coordenadas com fallback e exibir na tela

O objetivo aqui é: (1) solicitar permissão, (2) tentar obter a posição, (3) lidar com erros comuns, (4) exibir latitude/longitude e precisão.

1) Serviço de geolocalização (recomendado para organizar)

Crie um serviço para centralizar permissões, leitura única e monitoramento. Exemplo:

import { Injectable } from '@angular/core';
import { Geolocation, Position, PermissionStatus } from '@capacitor/geolocation';

export type LocationResult =
  | { ok: true; position: Position }
  | { ok: false; reason: 'permission-denied' | 'location-unavailable' | 'timeout' | 'gps-disabled' | 'unknown'; message: string };

@Injectable({ providedIn: 'root' })
export class LocationService {
  async ensurePermission(): Promise<PermissionStatus> {
    // Checa status atual
    const status = await Geolocation.checkPermissions();

    // Se ainda não concedido, solicita
    if (status.location !== 'granted') {
      return Geolocation.requestPermissions();
    }

    return status;
  }

  async getOnce(options?: { highAccuracy?: boolean; timeoutMs?: number }): Promise<LocationResult> {
    try {
      const perm = await this.ensurePermission();
      if (perm.location !== 'granted') {
        return {
          ok: false,
          reason: 'permission-denied',
          message: 'Permissão de localização negada. Ative nas configurações do sistema.'
        };
      }

      const position = await Geolocation.getCurrentPosition({
        enableHighAccuracy: options?.highAccuracy ?? true,
        timeout: options?.timeoutMs ?? 10000,
        maximumAge: 0
      });

      return { ok: true, position };
    } catch (err: any) {
      // Mapeamento simples de erros comuns
      const msg = String(err?.message ?? err);

      // Em alguns ambientes, códigos podem existir (ex.: 1,2,3 na Web)
      const code = err?.code;
      if (code === 1) {
        return { ok: false, reason: 'permission-denied', message: 'Permissão negada pelo usuário.' };
      }
      if (code === 2) {
        return { ok: false, reason: 'location-unavailable', message: 'Localização indisponível. Verifique GPS/rede.' };
      }
      if (code === 3) {
        return { ok: false, reason: 'timeout', message: 'Tempo esgotado ao obter localização. Tente novamente.' };
      }

      // Heurística para GPS desativado (mensagens variam por plataforma)
      if (msg.toLowerCase().includes('gps') || msg.toLowerCase().includes('location services')) {
        return { ok: false, reason: 'gps-disabled', message: 'Serviços de localização parecem estar desativados.' };
      }

      return { ok: false, reason: 'unknown', message: 'Falha ao obter localização. Tente novamente.' };
    }
  }

  async startWatch(onUpdate: (pos: Position) => void, onError?: (e: any) => void) {
    const perm = await this.ensurePermission();
    if (perm.location !== 'granted') {
      throw new Error('Permissão de localização não concedida.');
    }

    const watchId = await Geolocation.watchPosition(
      { enableHighAccuracy: true, maximumAge: 0, timeout: 10000 },
      (position, err) => {
        if (err) {
          onError?.(err);
          return;
        }
        if (position) onUpdate(position);
      }
    );

    return watchId;
  }

  async stopWatch(watchId: string) {
    await Geolocation.clearWatch({ id: watchId });
  }
}

2) Página: mostrar coordenadas e botões de ação

Agora, uma página simples com botões para “Obter localização” e “Iniciar/Parar monitoramento”. A UI pode ser minimalista e focada no comportamento.

import { Component, OnDestroy } from '@angular/core';
import { Position } from '@capacitor/geolocation';
import { LocationService } from '../services/location.service';

@Component({
  selector: 'app-location',
  templateUrl: './location.page.html'
})
export class LocationPage implements OnDestroy {
  loading = false;
  errorMsg = '';
  position?: Position;

  watching = false;
  watchId?: string;

  constructor(private locationService: LocationService) {}

  async getLocationOnce() {
    this.loading = true;
    this.errorMsg = '';

    const result = await this.locationService.getOnce({ highAccuracy: true, timeoutMs: 10000 });

    this.loading = false;

    if (!result.ok) {
      this.position = undefined;
      this.errorMsg = result.message;
      return;
    }

    this.position = result.position;
  }

  async toggleWatch() {
    this.errorMsg = '';

    if (this.watching && this.watchId) {
      await this.locationService.stopWatch(this.watchId);
      this.watching = false;
      this.watchId = undefined;
      return;
    }

    try {
      this.watching = true;
      this.watchId = await this.locationService.startWatch(
        (pos) => (this.position = pos),
        (err) => (this.errorMsg = 'Erro no monitoramento: ' + (err?.message ?? err))
      );
    } catch (e: any) {
      this.watching = false;
      this.watchId = undefined;
      this.errorMsg = e?.message ?? 'Não foi possível iniciar o monitoramento.';
    }
  }

  ngOnDestroy() {
    if (this.watchId) {
      this.locationService.stopWatch(this.watchId);
    }
  }
}

Template (exemplo) exibindo coordenadas, precisão e timestamp:

<ion-header>
  <ion-toolbar>
    <ion-title>Localização</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content class="ion-padding">
  <ion-button expand="block" (click)="getLocationOnce()" [disabled]="loading">
    Obter localização (uma vez)
  </ion-button>

  <ion-button expand="block" color="secondary" (click)="toggleWatch()">
    {{ watching ? 'Parar monitoramento' : 'Iniciar monitoramento' }}
  </ion-button>

  <ion-spinner *ngIf="loading"></ion-spinner>

  <ion-card *ngIf="position">
    <ion-card-header>
      <ion-card-title>Coordenadas</ion-card-title>
    </ion-card-header>
    <ion-card-content>
      <p><strong>Latitude:</strong> {{ position.coords.latitude }}</p>
      <p><strong>Longitude:</strong> {{ position.coords.longitude }}</p>
      <p><strong>Precisão (m):</strong> {{ position.coords.accuracy }}</p>
      <p><strong>Timestamp:</strong> {{ position.timestamp }}</p>
    </ion-card-content>
  </ion-card>

  <ion-item *ngIf="errorMsg" lines="none">
    <ion-label color="danger">{{ errorMsg }}</ion-label>
  </ion-item>
</ion-content>

3) Estratégias de fallback (quando falha)

Fallback é o plano B para manter o app útil quando a localização não vem. Algumas estratégias práticas:

  • Permissão negada: exibir mensagem com ação “Abrir configurações” (em muitos casos você orienta o usuário; abrir configurações pode depender de plugin adicional). Também ofereça alternativa manual (ex.: campo para digitar cidade/CEP).
  • Timeout: permitir “Tentar novamente” e, opcionalmente, repetir com enableHighAccuracy: false para obter uma posição aproximada mais rápido.
  • GPS desativado / localização indisponível: orientar o usuário a ativar “Serviços de localização” e testar em área aberta. Em ambiente interno, a precisão pode piorar.

Exemplo de retry com menor precisão:

const firstTry = await this.locationService.getOnce({ highAccuracy: true, timeoutMs: 8000 });
if (!firstTry.ok) {
  const fallbackTry = await this.locationService.getOnce({ highAccuracy: false, timeoutMs: 8000 });
  // Use fallbackTry se ok, senão mostre erro
}

Integração com mapa: opções e exemplo simples

Existem duas abordagens comuns para mostrar a posição em um mapa:

  • Mapa via WebView (iframe/URL): simples para protótipos e visualização rápida. Depende de conectividade e de políticas do provedor do mapa.
  • Biblioteca de mapas (ex.: Leaflet): mais controle (marcadores, camadas, eventos), mas exige configuração de assets e cuidado com tamanho/resize no mobile.

Opção A (conceitual): abrir mapa externo com coordenadas

Quando você só precisa “ver no mapa”, pode abrir um link com as coordenadas (ex.: Google Maps). Isso evita embutir mapa no app, mas tira o usuário do fluxo.

const lat = this.position?.coords.latitude;
const lng = this.position?.coords.longitude;
const url = `https://www.google.com/maps?q=${lat},${lng}`;
window.open(url, '_blank');

Opção B (componente simples): iframe com OpenStreetMap

Para uma visualização embutida e simples, você pode usar um iframe com uma URL do OpenStreetMap. É uma solução prática para demonstrar o conceito, mas não é a mais flexível para apps complexos.

Exemplo de template (renderiza quando houver posição):

<div *ngIf="position" style="height: 320px; border-radius: 12px; overflow: hidden;">
  <iframe
    width="100%"
    height="320"
    [src]="mapUrl"
    style="border:0;"
    loading="lazy"
    referrerpolicy="no-referrer-when-downgrade">
  </iframe>
</div>

Para gerar mapUrl com segurança no Angular, use DomSanitizer:

import { Component } from '@angular/core';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
import { Position } from '@capacitor/geolocation';

export class LocationPage {
  position?: Position;
  mapUrl?: SafeResourceUrl;

  constructor(private sanitizer: DomSanitizer) {}

  updateMapUrl() {
    if (!this.position) return;
    const lat = this.position.coords.latitude;
    const lng = this.position.coords.longitude;

    // bbox simples ao redor do ponto
    const delta = 0.01;
    const left = lng - delta;
    const right = lng + delta;
    const top = lat + delta;
    const bottom = lat - delta;

    const url = `https://www.openstreetmap.org/export/embed.html?bbox=${left}%2C${bottom}%2C${right}%2C${top}&layer=mapnik&marker=${lat}%2C${lng}`;
    this.mapUrl = this.sanitizer.bypassSecurityTrustResourceUrl(url);
  }
}

Chame updateMapUrl() sempre que position for atualizada (na leitura única e no monitoramento).

Tratamento de erros: cenários comuns e respostas do app

CenárioSintomaResposta recomendada
Permissão negadaErro imediato ao solicitar/obter posiçãoExplicar por que precisa, oferecer alternativa manual, orientar a habilitar nas configurações
GPS/serviços de localização desligadosLocalização indisponível ou erro do sistemaPedir para ativar serviços de localização e tentar novamente; sugerir ir para área aberta
TimeoutDemora e falhaBotão “Tentar novamente”; fallback com baixa precisão; aumentar timeout com parcimônia
Sem internet (para mapa embutido)Mapa não carregaMostrar coordenadas mesmo assim; exibir placeholder e opção de abrir mapa quando online
Monitoramento esquecidoBateria drenandoParar watch ao sair da tela; oferecer toggle claro; evitar watch sem necessidade

Boas práticas essenciais

  • Peça permissão no momento certo: solicite quando o usuário acionar uma função que depende de localização, não ao abrir o app sem contexto.
  • Prefira leitura única quando possível; use watchPosition apenas quando houver valor real em atualizações contínuas.
  • Desligue o monitoramento ao sair da tela (ngOnDestroy) e também quando o usuário não precisar mais.
  • Mostre estado e feedback: loading, erro e última posição conhecida (se fizer sentido) melhoram a UX.
  • Evite alta precisão por padrão se o caso de uso tolerar aproximação; isso reduz consumo e melhora tempo de resposta.
  • Trate mapa como dependência opcional: o app deve continuar útil exibindo coordenadas mesmo sem mapa.

Agora responda o exercício sobre o conteúdo:

Em um app Ionic com Capacitor, qual escolha é mais adequada para obter a localização na maioria dos casos, reduzindo consumo de bateria e complexidade?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

Uma leitura única com getCurrentPosition atende a maioria dos casos e evita o custo do monitoramento contínuo. O watchPosition deve ser usado apenas quando há necessidade real de atualizações constantes, pois tende a consumir mais bateria, especialmente com alta precisão.

Próximo capitúlo

Arquivos e compartilhamento no Ionic com Capacitor: leitura, escrita e exportação

Arrow Right Icon
Capa do Ebook gratuito Ionic para Iniciantes: aplicativos híbridos com HTML, CSS e TypeScript
71%

Ionic para Iniciantes: aplicativos híbridos com HTML, CSS e TypeScript

Novo curso

21 páginas

Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.