1. Introduzione
Flutter è l'SDK per app mobile di Google per creare esperienze native di alta qualità su iOS e Android in tempi record.
Con il plug-in Flutter di Google Maps, puoi aggiungere alla tua applicazione mappe basate sui dati di Google Maps. Il plug-in gestisce automaticamente l'accesso ai server di Google Maps, la visualizzazione della mappa e la risposta ai gesti dell'utente, come clic e trascina. Puoi anche aggiungere indicatori alla mappa. Questi oggetti forniscono informazioni aggiuntive per le posizioni sulla mappa e consentono all'utente di interagire con la mappa.
Cosa creerai
In questo codelab, creerai un'app mobile con una mappa Google utilizzando l'SDK Flutter. La tua app sarà in grado di:
|
Cos'è Flutter?
Flutter ha tre capacità principali.
- Veloce da sviluppare: crea le tue applicazioni Android e iOS in pochi millisecondi con Stateful Hot Reload.
- Espressivi e flessibili: distribuisci rapidamente le funzionalità con particolare attenzione alle esperienze native dell'utente finale.
- Prestazioni native su iOS e Android: i widget di Flutter incorporano tutte le differenze fondamentali della piattaforma, come scorrimento, navigazione, icone e caratteri, per fornire prestazioni native complete.
Google Maps offre:
- 99% di copertura nel mondo: crea dati affidabili e completi per oltre 200 paesi e territori.
- 25 milioni di aggiornamenti al giorno: puoi contare su informazioni sulla posizione precise e in tempo reale.
- 1 miliardo di utenti attivi al mese: scala in modo sicuro, con il supporto delle funzionalità di Google Maps dell'infrastruttura.
Questo codelab ti guida nella creazione di un'esperienza su Google Maps in un'app Flutter sia per iOS che per Android.
Cosa imparerai a fare
- Come creare una nuova applicazione Flutter.
- Come configurare un plug-in Flutter per Google Maps.
- Come aggiungere indicatori a una mappa utilizzando i dati sulla posizione provenienti da un servizio web.
Questo codelab è incentrato sull'aggiunta di una mappa Google a un'app Flutter. Concetti e blocchi di codice non pertinenti sono trattati solo superficialmente e sono forniti solo per operazioni di copia e incolla.
Cosa ti piacerebbe imparare da questo codelab?
2. Configurare l'ambiente Flutter
Per completare questo lab sono necessari due software: l'SDK Flutter e un editor. Questo codelab presuppone l'utilizzo di Android Studio, ma puoi utilizzare il tuo editor preferito.
Puoi eseguire questo codelab utilizzando uno dei seguenti dispositivi:
- Un dispositivo fisico (Android o iOS) collegato al computer e impostato sulla modalità sviluppatore.
- Il simulatore iOS. (è richiesta l'installazione di strumenti Xcode).
- L'emulatore Android. (la configurazione è richiesta in Android Studio).
3. Per iniziare
Guida introduttiva a Flutter
Il modo più semplice per iniziare a utilizzare Flutter è utilizzare lo strumento a riga di comando flutter per creare tutto il codice richiesto per un'esperienza introduttiva semplice.
$ flutter create google_maps_in_flutter --platforms android,ios,web Creating project google_maps_in_flutter... Resolving dependencies in `google_maps_in_flutter`... Downloading packages... Got dependencies in `google_maps_in_flutter`. Wrote 81 files. All done! You can find general documentation for Flutter at: https://docs.flutter.dev/ Detailed API documentation is available at: https://api.flutter.dev/ If you prefer video documentation, consider: https://www.youtube.com/c/flutterdev In order to run your application, type: $ cd google_maps_in_flutter $ flutter run Your application code is in google_maps_in_flutter/lib/main.dart.
Aggiunta del plug-in Flutter di Google Maps come dipendenza
È facile aggiungere ulteriori funzionalità a un'app Flutter utilizzando i pacchetti Pub. In questo codelab introduci il plug-in Flutter di Google Maps eseguendo il seguente comando dalla directory del progetto.
$ cd google_maps_in_flutter $ flutter pub add google_maps_flutter Resolving dependencies... Downloading packages... + csslib 1.0.0 + flutter_plugin_android_lifecycle 2.0.19 + flutter_web_plugins 0.0.0 from sdk flutter + google_maps 7.1.0 + google_maps_flutter 2.6.1 + google_maps_flutter_android 2.8.0 + google_maps_flutter_ios 2.6.0 + google_maps_flutter_platform_interface 2.6.0 + google_maps_flutter_web 0.5.7 + html 0.15.4 + js 0.6.7 (0.7.1 available) + js_wrapping 0.7.4 leak_tracker 10.0.4 (10.0.5 available) leak_tracker_flutter_testing 3.0.3 (3.0.5 available) material_color_utilities 0.8.0 (0.11.1 available) meta 1.12.0 (1.14.0 available) + plugin_platform_interface 2.1.8 + sanitize_html 2.1.0 + stream_transform 2.1.0 test_api 0.7.0 (0.7.1 available) + web 0.5.1 Changed 16 dependencies! 6 packages have newer versions incompatible with dependency constraints. Try `flutter pub outdated` for more information.
Configurazione di iOS platform
Per ottenere la versione più recente dell'SDK di Google Maps su iOS è richiesta una versione minima della piattaforma di iOS 14. Modifica la parte superiore del file di configurazione ios/Podfile
come segue.
ios/Podfile
# Google Maps SDK requires platform version 14
# https://developers.google.com/maps/flutter-package/config#ios
platform :ios, '14.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
Configurazione di Android minSDK
Per utilizzare l'SDK Google Maps su Android, è necessario impostare minSdk
su 21. Modifica il file di configurazione android/app/build.gradle
come segue.
android/app/build.gradle
android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.google_maps_in_flutter"
// Minimum Android version for Google Maps SDK
// https://developers.google.com/maps/flutter-package/config#android
minSdk = 21
targetSdk = flutter.targetSdkVersion
versionCode = flutterVersionCode.toInteger()
versionName = flutterVersionName
}
}
4. Aggiunta di Google Maps all'app
Si tratta di chiavi API
Per utilizzare Google Maps nell'app Flutter, devi configurare un progetto API con Google Maps Platform, seguendo le istruzioni della chiave API di Maps SDK for Android e dell'SDK Maps per iOS Utilizzo della chiave API e Utilizzo della chiave API dell'API Maps JavaScript. Tenendo le chiavi API a portata di mano, svolgi i passaggi che seguono per configurare le applicazioni per Android e iOS.
Aggiungere una chiave API per un'app Android
Per aggiungere una chiave API all'app per Android, modifica il file AndroidManifest.xml
in android/app/src/main
. Aggiungi una singola voce meta-data
all'interno del nodo application
contenente la chiave API creata nel passaggio precedente.
android/app/src/main/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="google_maps_in_flutter"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<!-- TODO: Add your Google Maps API key here -->
<meta-data android:name="com.google.android.geo.API_KEY"
android:value="YOUR-KEY-HERE"/>
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>
Aggiunta di una chiave API per un'app per iOS
Per aggiungere una chiave API all'app per iOS, modifica il file AppDelegate.swift
in ios/Runner
. A differenza di Android, l'aggiunta di una chiave API su iOS richiede modifiche al codice sorgente dell'app Runner. AppDelega è il singleton principale che fa parte del processo di inizializzazione dell'app.
Apporta due modifiche a questo file. In primo luogo, aggiungi un'istruzione #import
per estrarre le intestazioni di Google Maps, quindi chiama il metodo provideAPIKey()
del singleton GMSServices
. Questa chiave API consente a Google Maps di mostrare correttamente i riquadri delle mappe.
ios/Runner/AppDelegate.swift
import Flutter
import UIKit
import GoogleMaps // Add this import
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
// TODO: Add your Google Maps API key
GMSServices.provideAPIKey("YOUR-API-KEY") // Add this line
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
Aggiunta di una chiave API per un'app web
Per aggiungere una chiave API all'app web, modifica il file index.html
in web
. Aggiungi un riferimento allo script JavaScript di Maps nella sezione head con la tua chiave API.
web/index.html
<!DOCTYPE html>
<html>
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="google_maps_in_flutter">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<!-- Add your Google Maps API key here -->
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR-KEY-HERE"></script>
<title>google_maps_in_flutter</title>
<link rel="manifest" href="manifest.json">
</head>
<body>
<script src="flutter_bootstrap.js" async></script>
</body>
</html>
Inserire una mappa sullo schermo
È il momento di mostrare una mappa sullo schermo. Sostituisci i contenuti di lib/main.dart
con quanto segue.
lib/main.dart
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late GoogleMapController mapController;
final LatLng _center = const LatLng(45.521563, -122.677433);
void _onMapCreated(GoogleMapController controller) {
mapController = controller;
}
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
useMaterial3: true,
colorSchemeSeed: Colors.green[700],
),
home: Scaffold(
appBar: AppBar(
title: const Text('Maps Sample App'),
elevation: 2,
),
body: GoogleMap(
onMapCreated: _onMapCreated,
initialCameraPosition: CameraPosition(
target: _center,
zoom: 11.0,
),
),
),
);
}
}
Esecuzione dell'app
Esegui l'app Flutter su iOS o Android per avere un'unica visualizzazione mappa, centrata su Milano. In alternativa, esegui un emulatore Android o un simulatore iOS. Puoi modificare il centro della mappa per rappresentare la tua città natale o un luogo che è importante per te.
$ flutter run
5. Metti Google sulla mappa
Google ha molti uffici in tutto il mondo, da Nord America, America Latina, Europa, Asia Pacifico, Africa e Medio Oriente. L'aspetto positivo di queste mappe, se le esamini, è che hanno un endpoint API facilmente utilizzabile per fornire le informazioni sulla sede dell'ufficio in formato JSON. In questo passaggio, inserisci le sedi degli uffici sulla mappa. In questo passaggio, utilizzerai la generazione del codice per analizzare JSON.
Aggiungi tre nuove dipendenze Flutter al progetto come segue. Aggiungi il pacchetto http
per effettuare facilmente richieste HTTP, json_serializable
e json_annotation
per dichiarare la struttura degli oggetti per la rappresentazione di documenti JSON. Aggiungi build_runner
per il supporto della generazione di codice.
$ flutter pub add http json_annotation json_serializable dev:build_runner Resolving dependencies... Downloading packages... + _fe_analyzer_shared 67.0.0 (68.0.0 available) + analyzer 6.4.1 (6.5.0 available) + args 2.5.0 + build 2.4.1 + build_config 1.1.1 + build_daemon 4.0.1 + build_resolvers 2.4.2 + build_runner 2.4.9 + build_runner_core 7.3.0 + built_collection 5.1.1 + built_value 8.9.2 + checked_yaml 2.0.3 + code_builder 4.10.0 + convert 3.1.1 + crypto 3.0.3 + dart_style 2.3.6 + file 7.0.0 + fixnum 1.1.0 + frontend_server_client 4.0.0 + glob 2.1.2 + graphs 2.3.1 + http 1.2.1 + http_multi_server 3.2.1 + http_parser 4.0.2 + io 1.0.4 js 0.6.7 (0.7.1 available) + json_annotation 4.9.0 + json_serializable 6.8.0 leak_tracker 10.0.4 (10.0.5 available) leak_tracker_flutter_testing 3.0.3 (3.0.5 available) + logging 1.2.0 material_color_utilities 0.8.0 (0.11.1 available) meta 1.12.0 (1.14.0 available) + mime 1.0.5 + package_config 2.1.0 + pool 1.5.1 + pub_semver 2.1.4 + pubspec_parse 1.2.3 + shelf 1.4.1 + shelf_web_socket 1.0.4 + source_gen 1.5.0 + source_helper 1.3.4 test_api 0.7.0 (0.7.1 available) + timing 1.0.1 + typed_data 1.3.2 + watcher 1.1.0 + web_socket_channel 2.4.5 + yaml 3.1.2 Changed 42 dependencies! 8 packages have newer versions incompatible with dependency constraints. Try `flutter pub outdated` for more information.
Analisi di JSON con generazione del codice
Potresti aver notato che i dati JSON restituiti dall'endpoint API hanno una struttura normale. Potrebbe essere utile generare il codice per eseguire il marshal dei dati in oggetti da utilizzare nel codice.
Nella directory lib/src
, crea un file locations.dart
e descrivi la struttura dei dati JSON restituiti come segue:
lib/src/locations.dart
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:http/http.dart' as http;
import 'package:json_annotation/json_annotation.dart';
part 'locations.g.dart';
@JsonSerializable()
class LatLng {
LatLng({
required this.lat,
required this.lng,
});
factory LatLng.fromJson(Map<String, dynamic> json) => _$LatLngFromJson(json);
Map<String, dynamic> toJson() => _$LatLngToJson(this);
final double lat;
final double lng;
}
@JsonSerializable()
class Region {
Region({
required this.coords,
required this.id,
required this.name,
required this.zoom,
});
factory Region.fromJson(Map<String, dynamic> json) => _$RegionFromJson(json);
Map<String, dynamic> toJson() => _$RegionToJson(this);
final LatLng coords;
final String id;
final String name;
final double zoom;
}
@JsonSerializable()
class Office {
Office({
required this.address,
required this.id,
required this.image,
required this.lat,
required this.lng,
required this.name,
required this.phone,
required this.region,
});
factory Office.fromJson(Map<String, dynamic> json) => _$OfficeFromJson(json);
Map<String, dynamic> toJson() => _$OfficeToJson(this);
final String address;
final String id;
final String image;
final double lat;
final double lng;
final String name;
final String phone;
final String region;
}
@JsonSerializable()
class Locations {
Locations({
required this.offices,
required this.regions,
});
factory Locations.fromJson(Map<String, dynamic> json) =>
_$LocationsFromJson(json);
Map<String, dynamic> toJson() => _$LocationsToJson(this);
final List<Office> offices;
final List<Region> regions;
}
Future<Locations> getGoogleOffices() async {
const googleLocationsURL = 'https://about.google/static/data/locations.json';
// Retrieve the locations of Google offices
try {
final response = await http.get(Uri.parse(googleLocationsURL));
if (response.statusCode == 200) {
return Locations.fromJson(
json.decode(response.body) as Map<String, dynamic>);
}
} catch (e) {
if (kDebugMode) {
print(e);
}
}
// Fallback for when the above HTTP request fails.
return Locations.fromJson(
json.decode(
await rootBundle.loadString('assets/locations.json'),
) as Map<String, dynamic>,
);
}
Dopo aver aggiunto questo codice, il tuo IDE (se ne utilizzi uno) dovrebbe mostrare delle "scarabocchi" rossi, poiché fa riferimento a un file di pari livello inesistente. locations.g.dart.
Questo file generato viene convertito tra strutture JSON non digitate e oggetti con nome. Per crearlo, esegui build_runner
nel seguente modo:
$ dart run build_runner build --delete-conflicting-outputs [INFO] Generating build script... [INFO] Generating build script completed, took 357ms [INFO] Creating build script snapshot...... [INFO] Creating build script snapshot... completed, took 10.5s [INFO] There was output on stdout while compiling the build script snapshot, run with `--verbose` to see it (you will need to run a `clean` first to re-snapshot). [INFO] Initializing inputs [INFO] Building new asset graph... [INFO] Building new asset graph completed, took 646ms [INFO] Checking for unexpected pre-existing outputs.... [INFO] Deleting 1 declared outputs which already existed on disk. [INFO] Checking for unexpected pre-existing outputs. completed, took 3ms [INFO] Running build... [INFO] Generating SDK summary... [INFO] 3.4s elapsed, 0/3 actions completed. [INFO] Generating SDK summary completed, took 3.4s [INFO] 4.7s elapsed, 2/3 actions completed. [INFO] Running build completed, took 4.7s [INFO] Caching finalized dependency graph... [INFO] Caching finalized dependency graph completed, took 36ms [INFO] Succeeded after 4.8s with 2 outputs (7 actions)
Il codice dovrebbe analizzare di nuovo in modo corretto. Successivamente, dobbiamo aggiungere il file locations.json di fallback utilizzato nella funzione getGoogleOffices
. Uno dei motivi per includere questo elemento di riserva è che i dati statici caricati in questa funzione vengono forniti senza intestazioni CORS e pertanto non verranno caricati in un browser web. Le app Flutter per Android e iOS non hanno bisogno di intestazioni CORS, ma l'accesso ai dati mobili può essere spesso limitato.
Vai a https://about.google/static/data/locations.json
nel browser e salva i contenuti nella directory degli asset. In alternativa, puoi utilizzare la riga di comando come indicato di seguito.
$ mkdir assets $ cd assets $ curl -o locations.json https://about.google/static/data/locations.json % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 30348 100 30348 0 0 75492 0 --:--:-- --:--:-- --:--:-- 75492
Ora che hai scaricato il file degli asset, aggiungilo alla sezione Flutter del file pubspec.yaml
.
pubspec.yaml
flutter:
uses-material-design: true
assets:
- assets/locations.json
Modifica il file main.dart
per richiedere i dati della mappa, quindi utilizza le informazioni restituite per aggiungere uffici alla mappa:
lib/main.dart
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'src/locations.dart' as locations;
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final Map<String, Marker> _markers = {};
Future<void> _onMapCreated(GoogleMapController controller) async {
final googleOffices = await locations.getGoogleOffices();
setState(() {
_markers.clear();
for (final office in googleOffices.offices) {
final marker = Marker(
markerId: MarkerId(office.name),
position: LatLng(office.lat, office.lng),
infoWindow: InfoWindow(
title: office.name,
snippet: office.address,
),
);
_markers[office.name] = marker;
}
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
useMaterial3: true,
colorSchemeSeed: Colors.green[700],
),
home: Scaffold(
appBar: AppBar(
title: const Text('Google Office Locations'),
elevation: 2,
),
body: GoogleMap(
onMapCreated: _onMapCreated,
initialCameraPosition: const CameraPosition(
target: LatLng(0, 0),
zoom: 2,
),
markers: _markers.values.toSet(),
),
),
);
}
}
Questo codice esegue diverse operazioni:
- In
_onMapCreated
, utilizza il codice di analisi JSON del passaggio precedente,await
ing fino al caricamento. Quindi, utilizza i dati restituiti per creareMarker
all'interno di un callbacksetState()
. Una volta che l'app riceve nuovi indicatori, setState segnala a Flutter per ridipingere lo schermo, visualizzando le posizioni degli uffici. - Gli indicatori sono memorizzati in un elemento
Map
associato al widgetGoogleMap
. In questo modo, gli indicatori vengono collegati alla mappa corretta. Potresti, ovviamente, avere più mappe e visualizzare indicatori diversi in ognuna.
Ecco uno screenshot dei risultati raggiunti. A questo punto si possono aggiungere molte aggiunte interessanti. Ad esempio, potresti aggiungere una visualizzazione elenco degli uffici che si spostano e ingrandisci la mappa quando l'utente fa clic su un ufficio ma, come si suol dire, questo esercizio è lasciato al lettore.
6. Passaggi successivi
Complimenti!
Hai completato il codelab e creato un'app Flutter con una mappa di Google Maps. Hai anche interagito con un servizio web JSON.
Altri passaggi successivi
Questo codelab ha creato un'esperienza per visualizzare una serie di punti su una mappa. Esistono varie app mobile che si basano su questa funzionalità per soddisfare molte esigenze diverse degli utenti. Esistono altre risorse che possono aiutarti a raggiungere questo obiettivo:
- Creare app mobile con Flutter e Google Maps (una presentazione in occasione di Cloud Next '19)
- Il pacchetto
google_maps_webservice
di Hadrien Lejard che rende i servizi web di Google Maps, come l'API Directions, l'API Distance Matrix e l'API Places, molto facili da usare. - Se vuoi esaminare le diverse opzioni per l'utilizzo di un'API tramite REST JSON, vedi il post su Medium di Andrew Brogdon per una serie di opzioni per lavorare con le API REST JSON.