Ajouter Google Maps à une application Flutter

1. Introduction

Flutter est le SDK d'applications mobiles de Google. Il permet de créer des expériences natives de haute qualité sur iOS et Android en un temps record.

Avec le plug-in Flutter pour Google Maps, vous pouvez ajouter des cartes à votre application à partir de données Google Maps. Le plug-in gère automatiquement l'accès aux serveurs Google Maps, l'affichage des cartes et la réponse aux gestes des utilisateurs (par exemple lorsqu'ils cliquent sur la carte ou la font glisser). Vous pouvez également y ajouter des repères. Ces objets fournissent des informations supplémentaires concernant les lieux affichés sur la carte et permettent à l'utilisateur d'interagir avec elle.

Objectifs de l'atelier

Dans cet atelier de programmation, vous allez créer une application mobile contenant une carte Google Maps à l'aide du SDK Flutter. Cette application pourra :

  • Afficher une carte Google Maps
  • Récupérer les données cartographiques d'un service Web
  • Afficher ces données en tant que repères sur la carte

Qu'est-ce que Flutter ?

Flutter présente trois principaux atouts.

  • Développement rapide : créez vos applications Android et iOS en quelques millisecondes avec le hot reload avec état.
  • Clair et souple : proposez rapidement des fonctionnalités axées sur les expériences natives de l'utilisateur final.
  • Performances natives sur iOS et Android : les widgets de Flutter intègrent toutes les différences majeures entre plate-formes (comme le défilement, la navigation, les icônes et les polices) afin d'offrir des performances natives optimales.

Quelques chiffres concernant Google Maps :

  • 99 % de couverture mondiale : créez des applications avec des données fiables et complètes pour plus de 200 pays et territoires.
  • 25 millions de mises à jour quotidiennes : bénéficiez d'informations de position en temps réel exactes.
  • 1 milliard d'utilisateurs actifs par mois : évoluez en toute confiance grâce à l'infrastructure de Google Maps.

Cet atelier de programmation vous explique comment créer une expérience Google Maps dans une application Flutter pour iOS et Android.

Points abordés

  • Apprendre à créer une application Flutter
  • Configurer un plug-in Flutter pour Google Maps
  • Ajouter des repères à une carte à l'aide des données de localisation d'un service Web

Cet atelier de programmation vous explique comment ajouter une carte Google à une application Flutter. Les concepts et les blocs de code qui ne concernent pas cet aspect ne sont pas abordés, et vous sont fournis afin que vous puissiez simplement les copier et les coller.

Qu'attendez-vous de cet atelier de programmation ?

Je suis novice en la matière et je voudrais avoir un bon aperçu. Je connais un peu le sujet, mais j'aimerais revoir certains points. Je cherche un exemple de code à utiliser dans mon projet. Je cherche des explications sur un point spécifique.

2. Configurer votre environnement Flutter

Pour cet atelier, vous avez besoin de deux logiciels : le SDK Flutter et un éditeur. Nous utilisons pour cet atelier Android Studio. Toutefois, vous pouvez vous servir de l'éditeur de votre choix.

Vous pouvez exécuter cet atelier de programmation sur l'un des appareils suivants :

  • Un appareil physique (Android ou iOS) en mode développeur, branché à votre ordinateur
  • Le simulateur iOS (nécessite d'installer les outils Xcode)
  • L'émulateur Android (doit être configuré dans Android Studio)

3. Premiers pas

Premiers pas avec Flutter

Pour commencer à utiliser Flutter, il vous suffit d'utiliser l'outil de ligne de commande Flutter pour créer tout le code nécessaire à une expérience de démarrage simple.

$ flutter create google_maps_in_flutter
Creating project google_maps_in_flutter...
[Listing of created files elided]
Wrote 127 files.

All done!
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.

Ajouter le plug-in Flutter pour Google Maps en tant que dépendance

Pour ajouter des fonctionnalités à une application Flutter, il vous suffit d'utiliser les packages Pub. Dans cet atelier de programmation, vous devez installer le plugin Flutter pour Google Maps en exécutant la commande suivante depuis le répertoire du projet.

$ cd google_maps_in_flutter
$ flutter pub add google_maps_flutter
Resolving dependencies...
  async 2.6.1 (2.8.2 available)
  charcode 1.2.0 (1.3.1 available)
+ flutter_plugin_android_lifecycle 2.0.3
+ google_maps_flutter 2.0.8
+ google_maps_flutter_platform_interface 2.1.1
  matcher 0.12.10 (0.12.11 available)
  meta 1.3.0 (1.7.0 available)
+ plugin_platform_interface 2.0.1
+ stream_transform 2.0.0
  test_api 0.3.0 (0.4.3 available)
Downloading google_maps_flutter 2.0.8...
Downloading flutter_plugin_android_lifecycle 2.0.3...
Changed 5 dependencies!

Vous apprendrez également à utiliser Google Maps dans la version Web de Flutter. Cependant, la version Web du plug-in n'est pas encore fédérée. Vous devez donc l'ajouter à votre projet.

$ flutter pub add google_maps_flutter_web
Resolving dependencies...
  async 2.6.1 (2.8.2 available)
  charcode 1.2.0 (1.3.1 available)
+ csslib 0.17.0
+ flutter_web_plugins 0.0.0 from sdk flutter
+ google_maps 5.3.0
+ google_maps_flutter_web 0.3.0+4
+ html 0.15.0
+ js 0.6.3
+ js_wrapping 0.7.3
  matcher 0.12.10 (0.12.11 available)
  meta 1.3.0 (1.7.0 available)
+ sanitize_html 2.0.0
  test_api 0.3.0 (0.4.3 available)
Changed 8 dependencies!

Configurer iOS platform

Pour obtenir la dernière version du SDK Google Maps sur iOS, vous devez utiliser la version minimale d'iOS 11. Modifiez le fichier ios/Podfile comme suit.

ios/Podfile

# Set platform to 11.0 to enable latest Google Maps SDK
platform :ios, '11.0' # Uncomment and set to 11.

# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

Configurer Android minSDK

Pour utiliser le SDK Google Maps sur Android, vous devez définir minSDK sur 20. Modifiez le fichier android/app/build.gradle comme suit.

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"
        minSdkVersion 20                      // Update from 16 to 20
        targetSdkVersion 30
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
    }
}

4. Ajouter Google Maps à l'application

L'importance des clés API

Pour utiliser Google Maps dans votre application Flutter, vous devez configurer un projet d'API avec Google Maps Platform, en suivant les instructions des pages Utiliser la clé API du SDK Maps pour Android, Utiliser la clé API du SDK Maps pour iOS et Utiliser la clé API du SDK Maps pour JavaScript. À l'aide des clés API, configurez les applications Android et iOS comme suit.

Ajouter une clé API pour une application Android

Pour ajouter une clé API à l'application Android, modifiez le fichier AndroidManifest.xml dans android/app/src/main. Ajoutez une seule entrée meta-data contenant la clé API (créée à l'étape précédente) dans le nœud application.

android/app/src/main/AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.google_maps_in_flutter">
    <application
        android:label="google_maps_in_flutter"
        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:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <meta-data
              android:name="io.flutter.embedding.android.NormalTheme"
              android:resource="@style/NormalTheme"
              />
            <meta-data
              android:name="io.flutter.embedding.android.SplashScreenDrawable"
              android:resource="@drawable/launch_background"
              />
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
    </application>
</manifest>

Ajouter une clé API pour une application iOS

Pour ajouter une clé API à l'application iOS, modifiez le fichier AppDelegate.swift dans ios/Runner. Contrairement à Android, ajouter une clé API sur iOS nécessite de modifier le code source de l'application Runner. AppDelegate est le singleton principal qui fait partie du processus d'initialisation de l'application.

Vous devez apporter deux modifications à ce fichier. Tout d'abord, ajoutez une instruction #import pour extraire les en-têtes Google Maps, puis appelez la méthode provideAPIKey() du singleton GMSServices. Cette clé API permet à Google Maps de diffuser correctement les tuiles de carte.

ios/Runner/AppDelegate.swift

import UIKit
import Flutter
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")

    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

Ajouter une clé API pour une application Web

Pour ajouter une clé API à l'application Web, modifiez le fichier index.html dans web. Ajoutez une référence au script Maps JavaScript dans la section "head" avec votre clé API.

web/index.html

<head>
  <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">

  <!-- TODO: 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>

Ajouter une carte à l'écran

À présent, ajoutons une carte à l'écran. Modifiez lib/main.dart comme suit :

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({Key? key}) : super(key: key);

  @override
  _MyAppState 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(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Maps Sample App'),
          backgroundColor: Colors.green[700],
        ),
        body: GoogleMap(
          onMapCreated: _onMapCreated,
          initialCameraPosition: CameraPosition(
            target: _center,
            zoom: 11.0,
          ),
        ),
      ),
    );
  }
}

Exécuter l'application

Exécutez l'application Flutter sur iOS ou Android pour afficher une seule vue de la carte, centrée sur Portland. Vous pouvez aussi exécuter un émulateur Android ou un simulateur iOS. N'hésitez pas à modifier le centre de la carte pour afficher votre ville natale ou un autre endroit important pour vous.

$ flutter run

5. Positionner Google sur la carte

Google dispose de nombreux bureaux dans le monde entier : en Amérique du Nord, en Amérique latine, en Europe, en Asie-Pacifique, et en Afrique et au Moyen-Orient. Ces cartes présentent un avantage certain : elles disposent d'un point de terminaison d'API facile à utiliser afin de fournir des informations sur la position des bureaux au format JSON. Au cours de cette étape, vous placerez ces adresses sur la carte. Par ailleurs, vous utiliserez la génération de code pour analyser le contenu au format JSON.

Ajoutez trois dépendances Flutter au projet comme suit. Tout d'abord, ajoutez le package http pour effectuer facilement des requêtes HTTP.

$ flutter pub add http
Resolving dependencies...
  async 2.8.1 (2.8.2 available)
+ http 0.13.3
+ http_parser 4.0.0
  matcher 0.12.10 (0.12.11 available)
+ pedantic 1.11.1
  test_api 0.4.2 (0.4.3 available)
Changed 3 dependencies!

Ensuite, ajoutez json_serializable pour déclarer la structure d'objets représentant des documents JSON.

$ flutter pub add json_serializable
Resolving dependencies...
+ _fe_analyzer_shared 25.0.0
+ analyzer 2.2.0
+ args 2.2.0
  async 2.8.1 (2.8.2 available)
+ build 2.1.0
+ build_config 1.0.0
+ checked_yaml 2.0.1
+ cli_util 0.3.3
+ convert 3.0.1
+ crypto 3.0.1
+ dart_style 2.0.3
+ file 6.1.2
+ glob 2.0.1
+ json_annotation 4.1.0
+ json_serializable 5.0.0
+ logging 1.0.1
  matcher 0.12.10 (0.12.11 available)
+ package_config 2.0.0
+ pub_semver 2.0.0
+ pubspec_parse 1.0.0
+ source_gen 1.1.0
+ source_helper 1.2.1
  test_api 0.4.2 (0.4.3 available)
+ watcher 1.0.0
+ yaml 3.1.0
Downloading analyzer 2.2.0...
Downloading _fe_analyzer_shared 25.0.0...
Changed 22 dependencies!

Enfin, ajoutez build_runner comme dépendance au moment du développement. Elle sera utilisée pour générer du code ultérieurement.

$ flutter pub add --dev build_runner
Resolving dependencies...
  async 2.8.1 (2.8.2 available)
+ build_daemon 3.0.0
+ build_resolvers 2.0.4
+ build_runner 2.1.1
+ build_runner_core 7.1.0
+ built_collection 5.1.0
+ built_value 8.1.2
+ code_builder 4.1.0
+ fixnum 1.0.0
+ frontend_server_client 2.1.2
+ graphs 2.0.0
+ http_multi_server 3.0.1
+ io 1.0.3
+ js 0.6.3
  matcher 0.12.10 (0.12.11 available)
+ mime 1.0.0
+ pool 1.5.0
+ shelf 1.2.0
+ shelf_web_socket 1.0.1
  test_api 0.4.2 (0.4.3 available)
+ timing 1.0.0
+ web_socket_channel 2.1.0
Changed 19 dependencies!

Analyser le contenu JSON avec la génération de code

Notez que les données JSON renvoyées par le point de terminaison de l'API présentent une structure régulière. Vous pouvez générer le code afin de rassembler ces données dans des objets que vous pouvez utiliser dans le code.

Dans le répertoire lib/src, créez un fichier locations.dart et décrivez la structure des données JSON renvoyées comme suit :

lib/src/locations.dart

import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:json_annotation/json_annotation.dart';
import 'package:flutter/services.dart' show rootBundle;

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));
    }
  } catch (e) {
    print(e);
  }

  // Fallback for when the above HTTP request fails.
  return Locations.fromJson(
    json.decode(
      await rootBundle.loadString('assets/locations.json'),
    ),
  );
}

Une fois que vous avez ajouté ce code, votre IDE (si vous en utilisez un) doit afficher des vaguelettes rouges, car il fait référence à un fichier correspondant inexistant (locations.g.dart.). Le fichier généré est converti entre des structures JSON non saisies et des objets nommés. Créez-le en exécutant la commande build_runner :

$ flutter pub 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)

Le code devrait à nouveau être analysé correctement. Vous devez ensuite ajouter le fichier locations.json utilisé dans la fonction getGoogleOffices. En effet, les données statiques chargées dans cette fonction sont diffusées sans en-têtes CORS. Elles ne seront donc pas chargées dans un navigateur Web. Les applications Flutter sur Android et iOS n'ont pas besoin d'en-têtes CORS, mais l'accès aux données mobiles est souvent difficile.

Accédez à https://about.google/static/data/locations.json dans votre navigateur, puis enregistrez le contenu dans le répertoire des éléments. Vous pouvez également utiliser la ligne de commande suivante :

$ 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

Une fois que vous avez téléchargé le fichier contenant les éléments, ajoutez-le à la section Flutter du fichier pubspec.yaml.

pubspec.yaml

flutter:
  uses-material-design: true

  assets:
    - assets/locations.json

Modifiez le fichier main.dart pour demander les données cartographiques, puis utilisez les informations renvoyées pour ajouter les adresses à la carte :

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({Key? key}) : super(key: key);

  @override
  _MyAppState 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(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Google Office Locations'),
          backgroundColor: Colors.green[700],
        ),
        body: GoogleMap(
          onMapCreated: _onMapCreated,
          initialCameraPosition: const CameraPosition(
            target: LatLng(0, 0),
            zoom: 2,
          ),
          markers: _markers.values.toSet(),
        ),
      ),
    );
  }
}

Ce code effectue plusieurs opérations :

  • Dans _onMapCreated, il utilise le code d'analyse JSON de l'étape précédente, et affiche l'état await jusqu'à ce qu'elle soit chargée. Il utilise ensuite les données renvoyées pour créer des Marker dans un rappel setState(). Une fois que l'application reçoit de nouveaux repères, setState indique à Flutter de repeindre l'écran pour afficher les adresses.
  • Les repères sont stockés dans un Map associé au widget GoogleMap. Les repères sont ainsi associés à la bonne carte. Vous pouvez tout à fait disposer de plusieurs cartes et afficher des repères différents dans chacune d'elles.

71c460c73b1e061e.png

Voici une capture d'écran de ce que vous avez réussi à faire : À ce stade, vous pouvez y ajouter de nombreux éléments intéressants. Par exemple, vous pouvez ajouter une vue sous forme de liste qui permette de déplacer la carte et d'effectuer un zoom lorsque l'utilisateur clique sur une adresse. C'est à vous de tester !

6. Étapes suivantes

Félicitations !

Vous avez terminé l'atelier de programmation et créé une application Flutter qui inclut une carte Google. Vous avez également interagi avec un service Web JSON.

Pour aller plus loin

Cet atelier de programmation vous a appris à visualiser plusieurs points sur une carte. Beaucoup d'applications mobiles s'appuient sur cette fonctionnalité pour répondre à un grand nombre de besoins différents. D'autres ressources peuvent vous aider à aller plus loin :