Lavorare con la geolocalizzazione dell’utente è uno dei task più comuni all’interno di un’app mobile in quanto permette di migliorare l’esperienza utente fornendo informazioni basate sulla sua posizione.
In questa lezione vedremo come integrare questa funzionalità in un’app Flutter.
I plugin per la geolocalizzazione
Flutter, come tutti i framework per lo sviluppo mobile, offre questa possibilità attraverso diversi plugin forniti dalla propria community, ognuno dei quali offre una soluzione ottimizzata e semplice da utilizzare nelle proprie app.
Tra i plugin più utilizzati e apprezzati all’interno della community troviamo:
- location, realizzata da uno sviluppatore indipendente;
- geolocator, sviluppata da un’azienda.
Entrambe sono librerie valide e aggiornate frequentemente.
In questa lezione vedremo come utilizzare geolocator, lasciando al lettore come esercizio quello di modificare il codice di questa lezione per utilizzare la libreria location e di trarre le proprie decisioni in merito a quale sia più congeniale per le proprie attività.
Per utilizzare geolocator versione 3.0.0 o superiori è necessario il supporto alla versione AndroidX delle Android Support Librieries e quindi il progetto deve essere aggiornato per il supporto ad AndroidX. Ulteriori informazioni sono disponibili sulla documentazione ufficiale di Flutter.
Esempio pratico
Come sempre, creiamo un nuovo progetto come descritto nella lezione 6 e procediamo con una semplice applicazione che mostrerà un RaisedButton
per recuperare:
- la posizione corrente in coordinate spaziali (latitudine, longitudine);
- l’indirizzo associato a quelle coordinate.
La struttura del nostro progetto sarà quindi la seguente
lib/
│── screens/
│ │── screen1
│ │ │── screen1.dart
│── theme/
│ │── style.dart
│── routes.dart
Diversamente dalle ultime lezioni, in questo esempio ci basterà lavorare all’interno di una singola schermata per estrapolare l’informazione di interesse. La definizione circa lo style
e le routes
è disponibile sul repository GitHub.
Installazione del plugin geolocator
Apriamo il file pubspec.yaml e inseriamo sotto la voce dependencies
il nome del plugin di interesse come segue.
dependencies:
# . . .
geolocator: ^5.3.1
Eseguiamo dal nostro terminale il comando
flutter pub get
per installare il plugin.
Definizione dei permessi
Per permettere alla nostra applicazione di utilizzare la geolocalizzazione è necessario impostare i permessi per iOS e Android.
Per iOS è necessario aggiungere nel file Info.plist in ios/Runner le seguenti righe di configurazione:
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs access to location when open.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>This app needs access to location when in the background.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This app needs access to location when open and in the background.</string>
di cui è possibile ovviamente modificare i messaggi definiti nel tag <string>
. In particolare definendo le chiavi NSLocationAlwaysUsageDescription
e NSLocationAlwaysAndWhenInUseUsageDescription
abbiamo abilitato il supporto a versioni di iOS antecedenti alla 10 e la possibilità di recuperare la posizione anche quando l’applicazione non è in uso.
Per Android, apriamo il file AndroidManifest.xml in android/app/src/main e aggiungiamo all’interno del tag <manifest>
quanto segue
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
Ulteriori dettagli sui permessi per la geolocalizzazione con Android sono disponibili nella nostra guida sullo sviluppo Android.
Definizione dell’UI
Apriamo adesso il file screen1.dart e definiamo il widget stateful Screen
che si comporrà di due informazioni:
- due
Text
che mostreranno le coordinate spaziali e l’indirizzo; - un
RaisedButton
per richiamare i metodi per l’estrazione dei dati.
Vediamo una semplice implementazione della nostra schermata.
class Screen1 extends StatefulWidget {
@override
_Screen1State createState() => _Screen1State();
}
class _Screen1State extends State<Screen1> {
// . . .
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Lesson 36"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(_currentAddress),
Text(
"LAT: ${_currentPosition.latitude}, LNG: ${_currentPosition.longitude}"),
RaisedButton(
child: Text("Get location"),
onPressed: () {
// . . .
},
),
],
),
),
);
}
}
Definizione dei metodi per il recupero della posizione e dell’indirizzo
Definiamo adesso i metodi e le variabili necessarie per l’estrapolazione delle informazioni riguardanti la posizione corrente dell’utente e l’indirizzo associato a quella posizione.
All’interno della classe _Screen1State
aggiungiamo le seguenti tre variabili globali
Position _currentPosition;
String _currentAddress;
final Geolocator geolocator = Geolocator();
dove:
Variabile | Descrizione |
---|---|
_currentPosition |
è di tipo Position e rappresenta una coordinata spaziale |
_currentAddress |
è di tipo String e manterrà le informazioni circa l’indirizzo associato alla posizione corrente dell’utente |
geolocator |
definisce un’istanza dell’oggetto Geolocator . Di default, l’oggetto Geolocator utilizzerà il FusedLocationProviderClient su Android quando sono disponibili i servizi di Google Play, altrimenti verrà utilizzato il LocationManager .È possibile ignorare il comportamento forzando l’utilizzo del LocationManager come segue Geolocator()..forceAndroidLocationManager . |
Definiamo adesso il metodo _getCurrentLocation
che permetterà di:
- estrapolare la posizione corrente dell’utente tramite il metodo
getCurrentPosition
specificando quale livello di accuratezza utilizzare; - impostare lo stato corrente per aggiornare l’informazione gestita dalla variabile
_currentPosition
; - effettuare il
catch
di possibili errori durante il recupero della posizione.
Di seguito una possibile implementazione.
void _getCurrentLocation() {
geolocator
.getCurrentPosition(desiredAccuracy: LocationAccuracy.best)
.then((Position position) {
setState(() {
_currentPosition = position;
});
}).catchError((e) {
print(e);
});
}
Aggiungiamo adesso il metodo per estrapolare l’indirizzo corrente dell’utente a partire dalla sua posizione. Definiamo il metodo async
_getAddressFromLatLng
che si occuperà di:
- verificare se
_currentPosition
è diverso danull
, e nel caso in cui lo fosse di restituire informazioni circa l’impossibilità di trovare un indirizzo; - invocare il metodo
placemarkFromCoordinates
diGeolocator
per ottenere una lista diPlacemark
a partire dalle informazioni di_currentPosition
; - impostare lo stato per
_currentAddress
; - gestire possibili errori durante l’estrazione dell’indirizzo.
Si riporta di seguito una possibile implementazione.
_getAddressFromLatLng() async {
try {
if (_currentPosition == null) {
_currentAddress = 'No address found';
return;
}
List<Placemark> p = await geolocator.placemarkFromCoordinates(
_currentPosition.latitude, _currentPosition.longitude);
Placemark place = p[0];
setState(() {
_currentAddress =
"${place.locality}, ${place.postalCode}, ${place.country}";
});
} catch (e) {
_currentAddress = 'No address found';
return;
print(e);
}
}
Per essere sicuri di invocarlo solo quando è disponibile la posizione dell’utente, modifichiamo il metodo _getCurrentLocation
in modo da invocare nel corpo del metodo then
il metodo _getAddressFromLatLng
.
void _getCurrentLocation() {
geolocator
.getCurrentPosition(desiredAccuracy: LocationAccuracy.best)
.then((Position position) {
setState(() {
_currentPosition = position;
});
_getAddressFromLatLng();
}).catchError((e) {
print(e);
});
}
Aggiornamento della UI
Definiti i metodi, non ci resta che modificare l’interfaccia come segue.
Aggiungiamo dei controlli sui widget Text
in modo da mostrare il widget se e solo se le variabili _currentAddress
e _currentPosition
sono diverse da null
. Ad esempio avremo:
if (_currentAddress != null) Text(_currentAddress),
if (_currentPosition != null)
Text(
"LAT: ${_currentPosition.latitude}, LNG: ${_currentPosition.longitude}"),
Aggiorniamo infine la proprietà RaisedButton.onPressed
come segue.
RaisedButton(
child: Text("Get location"),
onPressed: () {
_getCurrentLocation();
},
)
Ora che è tutto pronto eseguiamo la nostra app per vedere i risultati delle modifiche fatte finora.
Come possiamo vedere, se l’utente clicca sul RaisedButton
verrà invocato il metodo _getCurrentLocation()
che popolerà le variabili _currentPosition
e _currentAddress
qualora venga individuata la posizione dell’utente, e mostrerà tali dati nei relativi widget Text
.
Il codice di questa lezione è disponibile su GitHub.