Geolocalizar un usuario con Flutter y el paquete Geolocator


Avatar de Pedro Cortez

Geolocalizar a un usuario puede ser una funcionalidad importante en muchas aplicaciones. En este tutorial, te explico cómo hacerlo utilizando el paquete Geolocator.


Geolocalizar un usuario con Flutter

Implementación del paquete Geolocator

Para poder geolocalizar a un usuario, necesitarás el paquete Geolocator de Flutter. El primer paso será configurarlo.

¿Para qué sirve el paquete Geolocator?

El paquete Geolocator te permite obtener información importante relacionada con la geolocalización de los usuarios en una aplicación Flutter. Entre las funcionalidades que soporta se encuentran:

  • Obtener la posición actual del usuario.
  • Seguir sus desplazamientos en tiempo real.
  • Obtener su última ubicación conocida.
  • Verificar el estado de los servicios de localización en el dispositivo.
  • Calcular la distancia entre dos puntos geográficos y la dirección entre ellos.

Instalación y configuración

Para agregar Geolocator a tu aplicación Flutter, comienza por dirigirte al archivo pubspec.yaml de tu proyecto Flutter y añade la siguiente dependencia:

dependencies:
  geolocator: ^13.0.2

Luego, ejecuta el comando flutter pub get para descargar e instalar el paquete.

Configuración para Android

Para que Geolocator funcione en Android, sigue estos pasos:

1. El plugin Geolocator requiere que tu proyecto Android use las bibliotecas AndroidX. Asegúrate de que tu proyecto esté configurado añadiendo las siguientes líneas en el archivo android>gradle.properties:

android.useAndroidX=true
android.enableJetifier=true

2. Luego, agrega los permisos necesarios para acceder a la localización en el archivo AndroidManifest.xml bajo android/app/src/main/:

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

Finalmente, no olvides verificar que tu aplicación soporte las API de Android 12 y versiones superiores. También actualiza la versión de compileSdkVersion a 34 en tu archivo build.gradle, de esta forma:

android {
    namespace "com.monapp.app"
    compileSdkVersion flutter.compileSdkVersion
    ndkVersion = "26.1.10909125"
}

Configuración para iOS

Para que Geolocator funcione con iOS, sigue estos pasos:

1. Primero, configura tu aplicación para solicitar el permiso de uso de la geolocalización. Añade las siguientes claves en tu archivo Info.plist (dentro de ios/Runner/):

<key>NSLocationUsageDescription</key>
<string>This app needs access to location.</string>

Este bloque permitirá mostrar un mensaje al usuario para explicar por qué tu aplicación necesita la localización.

2. (Opcional) Si no deseas recibir actualizaciones de localización cuando la aplicación esté en segundo plano, puedes evitar pedir un permiso adicional configurando un flag de preprocesador en tu proyecto Xcode. Para hacerlo:

  • Abre tu proyecto en Xcode.
  • En la sección Pods, selecciona el target geolocator_apple.
  • Ve a los Build Settings.
  • En la barra de búsqueda, busca Preprocessor Macros y añade la siguiente línea:
BYPASS_PERMISSION_LOCATION_ALWAYS=1

Esto desactiva la solicitud de permiso para el acceso a la localización en segundo plano y evita que Apple haga preguntas adicionales cuando envíes tu aplicación.

3. En cambio, si necesitas seguir recibiendo actualizaciones de localización en segundo plano (por ejemplo, para aplicaciones de seguimiento en tiempo real), debes añadir capacidades de fondo en Xcode:

  • Ve a tu proyecto en Xcode.
  • Selecciona Project > Signing and Capabilities.
  • Haz clic en el botón + Capability y añade Location Updates.
  • Deberás proporcionar una explicación detallada al enviar la aplicación a la App Store para justificar el uso de esta funcionalidad. Si Apple no está convencido, tu aplicación podría ser rechazada.

4. Luego, debes añadir otra entrada en el archivo Info.plist para especificar el permiso de acceso a la localización en segundo plano:

<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Your app needs location access even when in the background.</string> //Reemplazar con tu explicación

5. Finalmente, si tu aplicación requiere acceso temporal a la localización precisa del usuario (por ejemplo, para búsqueda de dirección o cálculo de ruta), también deberás configurar una entrada NSLocationTemporaryUsageDescriptionDictionary en el archivo Info.plist. Esto informa al usuario que la aplicación está solicitando acceso a su localización precisa por un tiempo limitado:

<key>NSLocationTemporaryUsageDescriptionDictionary</key>
<dict>
  <key>YourPurposeKey</key>
  <string>The example app requires temporary access to the device's precise location.</string>
</dict>

YourPurposeKey debe coincidir con el valor de la clave purposeKey pasada en el método requestTemporaryFullAccuracy() del paquete Geolocator.

Al hacer la primera solicitud de acceso a la localización precisa, puede haber un pequeño retraso antes de que aparezca la ventana emergente, ya que iOS debe determinar primero la localización exacta del usuario.

Configuración para macOS

Para la plataforma macOS, el enfoque es similar al de iOS. Deberás añadir las siguientes claves en tu archivo Info.plist:

<key>NSLocationUsageDescription</key>
<string>This app needs access to location.</string>

Luego, para permitir el acceso temporal a una localización precisa, añade también:

<key>NSLocationTemporaryUsageDescriptionDictionary</key>
<dict>
  <key>YourPurposeKey</key>
  <string>The example App requires temporary access to the device's precise location.</string>
</dict>

Configuración para la Web

El plugin Geolocator soporta la Web, pero únicamente en contextos seguros (HTTPS). Al agregar el paquete geolocator en tu archivo pubspec.yaml, Flutter incluirá automáticamente el paquete geolocator_web.

Las siguientes funciones no son soportadas en la Web y causarán errores:

  • getServiceStatusStream
  • getLastKnownPosition
  • openAppSettings
  • openLocationSettings

También debes saber que el acceso a la localización a través de Geolocator en la Web puede no ser tan preciso como en otras plataformas, debido a las limitaciones de las API de geolocalización de los navegadores.

Obtener la posición de un usuario

Ahora que has configurado el paquete Geolocator, aquí están las funcionalidades que puedes utilizar:

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Geolocator Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: LocationScreen(),
    );
  }
}

class LocationScreen extends StatefulWidget {
  @override
  _LocationScreenState createState() => _LocationScreenState();
}

class _LocationScreenState extends State<LocationScreen> {
  String _locationMessage = 'No location data';
  String _permissionMessage = 'Location permission not verified';

  @override
  void initState() {
    super.initState();
    // Ask for location permission when the app starts
    checkAndRequestPermission();
  }

  // 1. Get current location
  Future<void> getCurrentLocation() async {
    try {
      final LocationSettings locationSettings = LocationSettings(
        accuracy: LocationAccuracy.high,
        distanceFilter: 100,
      );
      Position position = await Geolocator.getCurrentPosition(locationSettings: locationSettings);
      setState(() {
        _locationMessage = 'Latitude: ${position.latitude}, Longitude: ${position.longitude}';
      });
    } catch (e) {
      setState(() {
        _locationMessage = 'Error: $e';
      });
    }
  }

  // 2. Get last known position
  Future<void> getLastKnownLocation() async {
    try {
      Position? position = await Geolocator.getLastKnownPosition();
      if (position != null) {
        setState(() {
          _locationMessage = 'Last known position: Latitude: ${position.latitude}, Longitude: ${position.longitude}';
        });
      } else {
        setState(() {
          _locationMessage = 'No previous position available.';
        });
      }
    } catch (e) {
      setState(() {
        _locationMessage = 'Error: $e';
      });
    }
  }

  // 3. Listen to location updates
  Future<void> listenToLocationUpdates() async {
    final LocationSettings locationSettings = LocationSettings(
      accuracy: LocationAccuracy.high,
      distanceFilter: 100,
    );
    StreamSubscription<Position> positionStream = Geolocator.getPositionStream(locationSettings: locationSettings).listen(
      (Position? position) {
        setState(() {
          _locationMessage = position == null
              ? 'Unknown'
              : 'Latitude: ${position.latitude}, Longitude: ${position.longitude}';
        });
      },
    );
  }

  // 4. Check if location services are enabled
  Future<void> checkLocationService() async {
    bool isLocationServiceEnabled = await Geolocator.isLocationServiceEnabled();
    setState(() {
      _locationMessage = isLocationServiceEnabled
          ? 'Location services are enabled.'
          : 'Location services are disabled.';
    });
  }

  // 5. Check and request location permission
  Future<void> checkAndRequestPermission() async {
    LocationPermission permission = await Geolocator.checkPermission();

    if (permission == LocationPermission.denied) {
      setState(() {
        _permissionMessage = 'Location permission denied. Requesting permission...';
      });
      permission = await Geolocator.requestPermission();
    }

    setState(() {
      if (permission == LocationPermission.whileInUse || permission == LocationPermission.always) {
        _permissionMessage = 'Location permission granted.';
      } else {
        _permissionMessage = 'Location permission permanently denied.';
      }
    });
  }

  // 6. Calculate distance between two points
  Future<void> calculateDistance() async {
    double distanceInMeters = Geolocator.distanceBetween(
      52.2165157, 6.9437819, 52.3546274, 4.8285838,
    );
    setState(() {
      _locationMessage = 'Calculated distance: $distanceInMeters meters';
    });
  }

  // 7. Open app or location settings
  Future<void> openSettings() async {
    await Geolocator.openAppSettings();
    await Geolocator.openLocationSettings();
  }

  // 8. Check location accuracy
  Future<void> checkLocationAccuracy() async {
    var accuracy = await Geolocator.getLocationAccuracy();
    setState(() {
      _locationMessage = 'Location accuracy: $accuracy';
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Geolocator Example'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Text(_permissionMessage),
            SizedBox(height: 20),
            Text(_locationMessage),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: getCurrentLocation,
              child: Text('Get current location'),
            ),
            ElevatedButton(
              onPressed: getLastKnownLocation,
              child: Text('Get last known position'),
            ),
            ElevatedButton(
              onPressed: listenToLocationUpdates,
              child: Text('Listen to location updates'),
            ),
            ElevatedButton(
              onPressed: checkLocationService,
              child: Text('Check if location services are enabled'),
            ),
            ElevatedButton(
              onPressed: checkAndRequestPermission,
              child: Text('Check and request permissions'),
            ),
            ElevatedButton(
              onPressed: calculateDistance,
              child: Text('Calculate distance between two points'),
            ),
            ElevatedButton(
              onPressed: openSettings,
              child: Text('Open settings'),
            ),
            ElevatedButton(
              onPressed: checkLocationAccuracy,
              child: Text('Check location accuracy'),
            ),
          ],
        ),
      ),
    );
  }
}

Este código permitirá realizar operaciones como obtener la localización actual del usuario, escuchar cambios en la localización y acceder a otros detalles importantes como la precisión de la localización.

Solicitar permisos al usuario

Antes de poder geolocalizar al usuario, será necesario pedirle permiso para hacerlo. Aquí están las diferentes funciones proporcionadas por Geolocator para ello.

Verificar si los servicios de localización están habilitados

La función checkLocationService() permite verificar si los servicios de localización del dispositivo están habilitados. Sin esto, no sería posible acceder a la ubicación del usuario.

bool isLocationServiceEnabled = await Geolocator.isLocationServiceEnabled();
setState(() {
  _locationMessage = isLocationServiceEnabled
      ? 'Los servicios de localización están habilitados.'
      : 'Los servicios de localización están deshabilitados.';
});

bool isLocationServiceEnabled = await Geolocator.isLocationServiceEnabled() verifica si el usuario ha activado los servicios de localización en su dispositivo. Se mostrará un mensaje diferente según el resultado.

Verificar y solicitar permisos

La función checkAndRequestPermission() permite verificar si la aplicación tiene permiso para acceder a la localización del usuario. Si no es el caso, solicita el permiso.

LocationPermission permission = await Geolocator.checkPermission();

if (permission == LocationPermission.denied) {
  setState(() {
    _permissionMessage = 'Permiso de localización denegado. Solicitud de permiso...';
  });
  permission = await Geolocator.requestPermission();
}

setState(() {
  if (permission == LocationPermission.whileInUse || permission == LocationPermission.always) {
    _permissionMessage = 'Permiso de localización concedido.';
  } else {
    _permissionMessage = 'Permiso de localización permanentemente denegado.';
  }
});
  • La función Geolocator.checkPermission() verifica si la aplicación ya tiene permiso para usar la localización del usuario.
  • Si el permiso es denegado (permission == LocationPermission.denied), el código intentará solicitarlo usando Geolocator.requestPermission().
  • Después de verificar o solicitar el permiso, se muestra un mensaje que indica si el permiso fue concedido, denegado o permanentemente denegado.

Abrir los ajustes de la aplicación o de localización

La función openSettings() permite abrir los ajustes de la aplicación o los ajustes de localización del dispositivo, para permitir al usuario modificar los ajustes si es necesario.

Future<void> openSettings() async {
  await Geolocator.openAppSettings();
  await Geolocator.openLocationSettings();
}
  • await Geolocator.openAppSettings(): Abre los ajustes de la aplicación para que el usuario pueda modificar los permisos.
  • await Geolocator.openLocationSettings(): Abre los ajustes de localización para que el usuario pueda activar o desactivar los servicios de localización.

Recuperar una ubicación

Ahora que tenemos el permiso del usuario, podemos pasar a la parte que nos interesa: recuperar su ubicación.

Obtener la localización actual

La función getCurrentLocation() permite obtener la posición actual del usuario.

Future<void> getCurrentLocation() async {
  try {
    final LocationSettings locationSettings = LocationSettings(
      accuracy: LocationAccuracy.high,
      distanceFilter: 100,
    );
    Position position = await Geolocator.getCurrentPosition(locationSettings: locationSettings);
    setState(() {
      _locationMessage = 'Latitud: ${position.latitude}, Longitud: ${position.longitude}';
    });
  } catch (e) {
    setState(() {
      _locationMessage = 'Error: $e';
    });
  }
}
  • final LocationSettings locationSettings permite definir los parámetros de la localización. Por ejemplo, aquí elegimos una precisión alta (LocationAccuracy.high) y especificamos que queremos una actualización de la localización cada 100 metros (distanceFilter: 100).
  • Position position = await Geolocator.getCurrentPosition(locationSettings: locationSettings) permite obtener la posición actual del usuario en función de los parámetros definidos.
  • setState() actualiza la interfaz con la información de la posición. Si la posición se obtiene con éxito, se muestran la latitud y longitud. Si ocurre un error, se muestra un mensaje de error en su lugar.

Obtener la última posición conocida

La función getLastKnownLocation() permite obtener la última posición registrada en el dispositivo.

Future<void> getLastKnownLocation() async {
  try {
    Position? position = await Geolocator.getLastKnownPosition();
    if (position != null) {
      setState(() {
        _locationMessage = 'Última posición conocida: Latitud: ${position.latitude}, Longitud: ${position.longitude}';
      });
    } else {
      setState(() {
        _locationMessage = 'No hay posición anterior disponible.';
      });
    }
  } catch (e) {
    setState(() {
      _locationMessage = 'Error: $e';
    });
  }
}
  • Position? position = await Geolocator.getLastKnownPosition() intenta obtener la última posición conocida. Esta posición generalmente se almacena cuando la aplicación obtiene la localización, pero puede ser nula si el dispositivo nunca ha obtenido una posición antes.
  • Si se encuentra una posición, se muestra con la latitud y longitud mediante setState(). En cambio, si la posición es nula, se muestra un mensaje indicando que no hay ninguna posición disponible.

Escuchar las actualizaciones de localización

La función listenToLocationUpdates() permite seguir en tiempo real los cambios de la posición del usuario.

Future<void> listenToLocationUpdates() async {
  final LocationSettings locationSettings = LocationSettings(
    accuracy: LocationAccuracy.high,
    distanceFilter: 100,
  );
  StreamSubscription<Position> positionStream = Geolocator.getPositionStream(locationSettings: locationSettings).listen(
    (Position? position) {
      setState(() {
        _locationMessage = position == null
            ? 'Desconocido'
            : 'Latitud: ${position.latitude}, Longitud: ${position.longitude}';
      });
    },
  );
}

StreamSubscription<Position> positionStream = Geolocator.getPositionStream(locationSettings: locationSettings).listen(...) crea un flujo que escuchará las actualizaciones de la posición del usuario. Cada vez que la posición cambie, se ejecutará la función especificada en listen().

Calcular la distancia entre dos puntos

Finalmente, la función calculateDistance() permite calcular la distancia entre dos coordenadas geográficas.

Future<void> calculateDistance() async {
  double distanceInMeters = Geolocator.distanceBetween(
    52.2165157, 6.9437819, 52.3546274, 4.8285838,
  );
  setState(() {
    _locationMessage = 'Distancia calculada: $distanceInMeters metros';
  });
}

distanceInMeters toma cuatro parámetros: la latitud y longitud de dos puntos geográficos. Devuelve la distancia en metros entre estos dos puntos.

Avatar de Pedro Cortez