[{"data":1,"prerenderedAt":27},["ShallowReactive",2],{"articolo-geolocalizzazione-in-flutter-con-geolocator-posizione-tracking-e-background":3,"comments-article-geolocalizzazione-in-flutter-con-geolocator-posizione-tracking-e-background":26},{"id":4,"title":5,"slug":6,"excerpt":7,"body":8,"cover_image":9,"cover_remote_url":10,"cover_credit":11,"video_url":15,"status":16,"published_at":17,"meta_title":18,"meta_description":19,"category":20,"author":24},73,"Geolocalizzazione in Flutter con geolocator: posizione, tracking e background","geolocalizzazione-in-flutter-con-geolocator-posizione-tracking-e-background","Guida pratica a geolocator: configurazione Android e iOS, richiesta dei permessi, posizione singola, stream continuo, tracking in background, calcolo delle distanze e strategie per risparmiare batteria.","## Perché geolocator\n\nDalle app di delivery ai fitness tracker, dalla ricerca di punti di interesse alla geofencing, la posizione dell'utente è uno dei dati più richiesti in mobile. In Flutter il pacchetto di riferimento è **geolocator**, mantenuto dal team Baseflow: offre un'API unificata su Android, iOS, web, macOS e Windows, gestisce i permessi runtime, espone uno stream di posizioni e permette di configurare accuratezza, filtri di distanza e comportamento in background.\n\nIn questa guida vediamo come integrarlo correttamente, evitando gli errori più comuni (permessi mancanti, stream che si spengono, batteria prosciugata).\n\n## Installazione e configurazione\n\nAggiungiamo le dipendenze:\n\n```yaml\ndependencies:\n  geolocator: ^13.0.2\n  geocoding: ^3.0.0 # opzionale, per reverse geocoding\n```\n\n### Android\n\nNel file `android\u002Fapp\u002Fsrc\u002Fmain\u002FAndroidManifest.xml`, dentro il tag `\u003Cmanifest>`:\n\n```xml\n\u003Cuses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" \u002F>\n\u003Cuses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\" \u002F>\n\n\u003C!-- Solo se serve il tracking a schermo spento \u002F app in background -->\n\u003Cuses-permission android:name=\"android.permission.ACCESS_BACKGROUND_LOCATION\" \u002F>\n\u003Cuses-permission android:name=\"android.permission.FOREGROUND_SERVICE\" \u002F>\n\u003Cuses-permission android:name=\"android.permission.FOREGROUND_SERVICE_LOCATION\" \u002F>\n```\n\nRicorda che `compileSdk` deve essere almeno 34 e che su Android 12+ l'utente può concedere solo la posizione **approssimata**: in quel caso `ACCESS_FINE_LOCATION` risulta negato anche se l'app riceve comunque coordinate a bassa precisione.\n\n### iOS\n\nIn `ios\u002FRunner\u002FInfo.plist`:\n\n```xml\n\u003Ckey>NSLocationWhenInUseUsageDescription\u003C\u002Fkey>\n\u003Cstring>Usiamo la tua posizione per mostrarti i locali più vicini.\u003C\u002Fstring>\n\u003Ckey>NSLocationAlwaysAndWhenInUseUsageDescription\u003C\u002Fkey>\n\u003Cstring>Usiamo la posizione anche in background per tracciare i tuoi percorsi.\u003C\u002Fstring>\n```\n\nSe serve il background, abilita in Xcode *Signing & Capabilities → Background Modes → Location updates*. Le descrizioni devono essere specifiche: Apple rifiuta le app con testi generici tipo \"serve la posizione\".\n\n## Il flusso corretto dei permessi\n\nPrima di chiedere una posizione bisogna sempre verificare **due cose distinte**: che il servizio di localizzazione del dispositivo sia acceso e che l'app abbia il permesso.\n\n```dart\nimport 'package:geolocator\u002Fgeolocator.dart';\n\nFuture\u003Cvoid> ensureLocationReady() async {\n  final serviceEnabled = await Geolocator.isLocationServiceEnabled();\n  if (!serviceEnabled) {\n    \u002F\u002F Opzionale: Geolocator.openLocationSettings();\n    throw const LocationException('Servizi di localizzazione disattivati');\n  }\n\n  var permission = await Geolocator.checkPermission();\n\n  if (permission == LocationPermission.denied) {\n    permission = await Geolocator.requestPermission();\n  }\n\n  if (permission == LocationPermission.deniedForever) {\n    \u002F\u002F L'utente ha bloccato definitivamente: si può solo aprire le impostazioni\n    await Geolocator.openAppSettings();\n    throw const LocationException('Permesso negato permanentemente');\n  }\n\n  if (permission == LocationPermission.denied) {\n    throw const LocationException('Permesso negato');\n  }\n}\n\nclass LocationException implements Exception {\n  const LocationException(this.message);\n  final String message;\n  @override\n  String toString() => message;\n}\n```\n\n> Chiedi il permesso **nel contesto giusto**: non all'avvio dell'app, ma quando l'utente tocca \"Trova vicino a me\". Il tasso di accettazione cambia drasticamente.\n\n## Ottenere la posizione corrente\n\n```dart\nFuture\u003CPosition> getPosition() async {\n  await ensureLocationReady();\n\n  return Geolocator.getCurrentPosition(\n    locationSettings: const LocationSettings(\n      accuracy: LocationAccuracy.high,\n      timeLimit: Duration(seconds: 10),\n    ),\n  );\n}\n```\n\nIl `timeLimit` è fondamentale: senza di esso, in una zona con segnale GPS scarso la Future può restare pendente a lungo. In caso di scadenza viene lanciata una `TimeoutException`, quindi conviene avere un piano B:\n\n```dart\nFuture\u003CPosition?> getPositionWithFallback() async {\n  try {\n    return await getPosition();\n  } on TimeoutException {\n    \u002F\u002F Ultima posizione nota: istantanea, ma potenzialmente vecchia\n    return Geolocator.getLastKnownPosition();\n  }\n}\n```\n\nL'oggetto `Position` contiene `latitude`, `longitude`, `accuracy` (in metri), `altitude`, `speed`, `heading`, `timestamp` e i flag `isMocked` (utile per bloccare i fake GPS in app sensibili come i time tracker).\n\n### I livelli di accuratezza\n\n| Valore | Precisione tipica | Uso consigliato |\n|---|---|---|\n| `lowest` \u002F `low` | 500 m – 3 km | Meteo, contenuti regionali |\n| `medium` | ~100 m | Suggerimenti di città\u002Fquartiere |\n| `high` | ~10 m | Ricerca POI, mappe |\n| `best` \u002F `bestForNavigation` | \u003C 5 m | Navigazione turn-by-turn, sport |\n\nOgni scalino verso l'alto costa batteria: usa `high` come default e sali solo quando serve davvero.\n\n## Stream continuo di posizioni\n\nPer il tracking usa `getPositionStream`, configurando impostazioni specifiche per piattaforma:\n\n```dart\nStream\u003CPosition> trackPosition() {\n  late final LocationSettings settings;\n\n  if (defaultTargetPlatform == TargetPlatform.android) {\n    settings = AndroidSettings(\n      accuracy: LocationAccuracy.high,\n      distanceFilter: 25, \u002F\u002F metri minimi tra due aggiornamenti\n      intervalDuration: const Duration(seconds: 5),\n      foregroundNotificationConfig: const ForegroundNotificationConfig(\n        notificationTitle: 'Tracciamento attivo',\n        notificationText: 'Stiamo registrando il tuo percorso',\n        enableWakeLock: true,\n      ),\n    );\n  } else if (defaultTargetPlatform == TargetPlatform.iOS) {\n    settings = AppleSettings(\n      accuracy: LocationAccuracy.high,\n      distanceFilter: 25,\n      pauseLocationUpdatesAutomatically: true,\n      showBackgroundLocationIndicator: true,\n      allowBackgroundLocationUpdates: true,\n      activityType: ActivityType.fitness,\n    );\n  } else {\n    settings = const LocationSettings(\n      accuracy: LocationAccuracy.high,\n      distanceFilter: 25,\n    );\n  }\n\n  return Geolocator.getPositionStream(locationSettings: settings);\n}\n```\n\nIl `distanceFilter` è la leva più efficace sul consumo: se l'utente è fermo, non arrivano eventi e la radio GPS lavora meno.\n\nSu Android il `foregroundNotificationConfig` crea un *foreground service* e mostra una notifica persistente: senza di esso il sistema uccide gli aggiornamenti pochi minuti dopo che l'app va in background.\n\n### Consumare lo stream nella UI\n\n```dart\nclass TrackingPage extends StatefulWidget {\n  const TrackingPage({super.key});\n  @override\n  State\u003CTrackingPage> createState() => _TrackingPageState();\n}\n\nclass _TrackingPageState extends State\u003CTrackingPage> {\n  StreamSubscription\u003CPosition>? _sub;\n  final _points = \u003CPosition>[];\n\n  Future\u003Cvoid> _start() async {\n    await ensureLocationReady();\n    _sub?.cancel();\n    _sub = trackPosition().listen(\n      (p) => setState(() => _points.add(p)),\n      onError: (Object e) => debugPrint('Errore GPS: $e'),\n    );\n  }\n\n  @override\n  void dispose() {\n    _sub?.cancel(); \u002F\u002F fondamentale: evita leak e consumo inutile\n    super.dispose();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    final distance = _totalDistance();\n    return Scaffold(\n      appBar: AppBar(title: const Text('Tracking')),\n      body: Center(\n        child: Text('${(distance \u002F 1000).toStringAsFixed(2)} km'),\n      ),\n      floatingActionButton: FloatingActionButton(\n        onPressed: _start,\n        child: const Icon(Icons.play_arrow),\n      ),\n    );\n  }\n\n  double _totalDistance() {\n    var total = 0.0;\n    for (var i = 1; i \u003C _points.length; i++) {\n      total += Geolocator.distanceBetween(\n        _points[i - 1].latitude, _points[i - 1].longitude,\n        _points[i].latitude, _points[i].longitude,\n      );\n    }\n    return total;\n  }\n}\n```\n\nSe usi Riverpod, lo stream si incapsula bene in uno `StreamProvider`: la cancellazione della subscription avviene automaticamente quando il provider viene dismesso.\n\n## Distanze, direzione e filtri di qualità\n\n`Geolocator` espone due utility statiche molto usate:\n\n```dart\nfinal metri = Geolocator.distanceBetween(45.4642, 9.1900, 41.9028, 12.4964);\nfinal gradi = Geolocator.bearingBetween(45.4642, 9.1900, 41.9028, 12.4964);\n```\n\nNel tracking reale conviene **scartare i campioni rumorosi**, altrimenti il percorso registrato diventa una scia di zig-zag:\n\n```dart\nbool isReliable(Position p, Position? previous) {\n  if (p.accuracy > 50) return false; \u002F\u002F troppo impreciso\n  if (p.isMocked) return false;\n  if (previous == null) return true;\n\n  final seconds = p.timestamp.difference(previous.timestamp).inSeconds;\n  if (seconds \u003C= 0) return false;\n\n  final d = Geolocator.distanceBetween(\n    previous.latitude, previous.longitude, p.latitude, p.longitude,\n  );\n  return d \u002F seconds \u003C 55; \u002F\u002F ~200 km\u002Fh: oltre è un salto irreale\n}\n```\n\n## Da coordinate a indirizzo: il geocoding\n\ngeolocator non fa geocoding; per questo esiste il pacchetto gemello `geocoding`:\n\n```dart\nimport 'package:geocoding\u002Fgeocoding.dart';\n\nFuture\u003CString> addressOf(Position p) async {\n  final places = await placemarkFromCoordinates(p.latitude, p.longitude);\n  final place = places.first;\n  return '${place.street}, ${place.locality} (${place.administrativeArea})';\n}\n```\n\nUsa il geocoding con parsimonia: è una chiamata di sistema che può richiedere rete e ha limiti di rate. Mettila in cache per coordinate arrotondate a 4 decimali (~11 metri).\n\n## Strategie per la batteria\n\n- **Chiudi sempre lo stream** quando l'utente lascia la schermata o mette in pausa il tracking.\n- Alza il `distanceFilter` e abbassa l'accuratezza quando l'app va in background, se il caso d'uso lo consente.\n- Preferisci `getLastKnownPosition()` per il primo render della UI e poi aggiorna con la posizione fresca.\n- Su iOS attiva `pauseLocationUpdatesAutomatically` e imposta l'`activityType` corretto: il sistema sospende gli aggiornamenti quando rileva che l'utente è fermo.\n- Evita di richiedere `ACCESS_BACKGROUND_LOCATION` se non è indispensabile: sia Google Play sia App Store richiedono una giustificazione dettagliata in fase di review.\n\n## Testare il codice che usa la posizione\n\nNon dipendere direttamente dalle API statiche di `Geolocator` nei widget: incapsulale in un servizio con interfaccia, così i test diventano banali.\n\n```dart\nabstract interface class LocationService {\n  Future\u003CPosition> current();\n  Stream\u003CPosition> watch();\n}\n\nclass GeolocatorLocationService implements LocationService {\n  @override\n  Future\u003CPosition> current() => getPosition();\n  @override\n  Stream\u003CPosition> watch() => trackPosition();\n}\n\n\u002F\u002F Nei test\nclass FakeLocationService implements LocationService {\n  @override\n  Future\u003CPosition> current() async => Position(\n    latitude: 45.4642, longitude: 9.19, timestamp: DateTime.now(),\n    accuracy: 5, altitude: 120, altitudeAccuracy: 3, heading: 0,\n    headingAccuracy: 1, speed: 0, speedAccuracy: 0,\n  );\n\n  @override\n  Stream\u003CPosition> watch() => Stream.fromIterable([]);\n}\n```\n\nPer i test manuali, su iOS puoi simulare percorsi da Xcode (*Debug → Simulate Location*), su Android dall'emulatore (*Extended controls → Location*, con import di file GPX\u002FKML).\n\n## Errori comuni da evitare\n\n1. **Chiamare `getCurrentPosition` senza controllare i permessi**: su iOS la Future resta pendente finché l'utente non risponde, su Android lancia un'eccezione.\n2. **Dimenticare `deniedForever`**: continuare a chiamare `requestPermission()` non mostra più alcun dialog, l'unica via è `openAppSettings()`.\n3. **Non cancellare la subscription**: la radio GPS resta attiva e la batteria crolla.\n4. **Assumere che l'accuratezza sia costante**: sempre validare `position.accuracy` prima di prendere decisioni di business.\n5. **Nessun fallback offline**: se il GPS non aggancia, mostra all'utente un messaggio chiaro e un'alternativa (inserimento manuale dell'indirizzo).\n\n## Conclusioni\n\ngeolocator copre il 90% dei casi d'uso legati alla posizione con un'API pulita e multipiattaforma. Le chiavi per un'integrazione solida sono tre: un flusso di permessi esplicito e contestuale, impostazioni di accuratezza calibrate sul caso d'uso reale e una gestione rigorosa del ciclo di vita degli stream. Se il tuo scenario richiede geofencing o tracking persistente anche a app terminata, valuta soluzioni complementari come `flutter_background_geolocation`, ma per tutto il resto geolocator è la scelta più semplice e mantenibile.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Farticles\u002Fde9adcc1-d9f3-44f4-9fae-976bdb3bd6d1.jpg","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1553903148-895cebaaab4b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzA2NTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3ODczMTU4Nzh8&ixlib=rb-4.1.0&q=80&w=1080",{"name":12,"author_url":13,"photo_url":14},"Aubrey Odom","https:\u002F\u002Funsplash.com\u002F@octoberroses","https:\u002F\u002Funsplash.com\u002Fphotos\u002Fgreen-and-blue-map-rCx_m5bcDgk",null,"published","2026-08-21T04:01:32+00:00","Geolocalizzazione in Flutter con geolocator","Come usare geolocator in Flutter: permessi Android e iOS, posizione corrente, stream di tracking, background, distanze e risparmio batteria.",{"id":21,"name":22,"slug":23},1,"Guide","guide",{"id":21,"name":25},"Flutter Bot",[],1789120586628]