Ajouter et supprimer un marqueur sur une carte avec Flutter


Avatar de Pierre Courtois

Ajouter des marqueurs à vos cartes peut-être une feature indispensable de votre application, que ce soit pour indiquer un endroit à l’utilisateur, ou le laisser sauvegarder un lieu. Dans ce guide, je vous explique comment les mettre en place.


Ajouter et supprimer des marqueurs sur une carte Google Maps

Maintenant que vous avez ajouté une carte Google Maps à votre application Flutter, voici comment ajouter des marqueurs dessus pour indiquer des points importants, ou des endroits sauvegardés par l’utilisateur :

import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'firebase_options.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MapScreen(),
    );
  }
}

class MapScreen extends StatefulWidget {
  @override
  _MapScreenState createState() => _MapScreenState();
}

class _MapScreenState extends State<MapScreen> {
  late GoogleMapController _mapController;
  Set<Marker> _markers = {};
  final FirebaseFirestore _firestore = FirebaseFirestore.instance;

  @override
  void initState() {
    super.initState();
    _loadMarkers();
  }

  // 🔥 CHARGER LES MARQUEURS DEPUIS FIRESTORE
  void _loadMarkers() async {
    var snapshot = await _firestore.collection('markers').get();
    setState(() {
      _markers = snapshot.docs.map((doc) {
        var data = doc.data();
        return Marker(
          markerId: MarkerId(doc.id),
          position: LatLng(data['lat'], data['lng']),
          infoWindow: InfoWindow(title: data['title'] ?? 'Marqueur'),
          onTap: () => _showMarkerDialog(doc.id, data['title'] ?? "Sans titre"), // 🟢 Afficher popup
        );
      }).toSet();
    });
  }

  // 🎯 AJOUTER UN MARQUEUR AU CLIC SUR LA CARTE
  void _addMarker(LatLng position) async {
    String markerId = DateTime.now().millisecondsSinceEpoch.toString();

    Marker newMarker = Marker(
      markerId: MarkerId(markerId),
      position: position,
      infoWindow: InfoWindow(title: 'Nouveau marqueur'),
      onTap: () => _showMarkerDialog(markerId, 'Nouveau marqueur'), // 🟢 Afficher popup
    );

    setState(() {
      _markers.add(newMarker);
    });

    // 🔥 Sauvegarde dans Firestore
    await _firestore.collection('markers').doc(markerId).set({
      'lat': position.latitude,
      'lng': position.longitude,
      'title': 'Nouveau marqueur'
    });
  }

  // 📌 AFFICHER BOÎTE D'INFOS + SUPPRESSION
  void _showMarkerDialog(String markerId, String title) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: Text(title),
        content: Text("Que souhaitez-vous faire ?"),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: Text("Fermer"),
          ),
          TextButton(
            onPressed: () {
              _deleteMarker(markerId);
              Navigator.pop(context);
            },
            child: Text("Supprimer", style: TextStyle(color: Colors.red)),
          ),
        ],
      ),
    );
  }

  // ❌ SUPPRIMER UN MARQUEUR
  void _deleteMarker(String markerId) async {
    setState(() {
      _markers.removeWhere((marker) => marker.markerId.value == markerId);
    });

    // 🔥 Supprime de Firestore
    await _firestore.collection('markers').doc(markerId).delete();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Carte avec marqueurs")),
      body: GoogleMap(
        initialCameraPosition: CameraPosition(
          target: LatLng(48.8566, 2.3522), // Paris en exemple
          zoom: 12,
        ),
        markers: _markers,
        onMapCreated: (controller) => _mapController = controller,
        onTap: (position) => _addMarker(position), // 🟢 Clic simple sur carte => Ajoute un marqueur
      ),
    );
  }
}

Dans cet exemple, l’utilisateur peut ajouter un marqueur sur la carte avec un simple clic. L’endroit est alors enregistré dans Firestore, pour pouvoir s’afficher en temps réel. Cliquer sur un marqueur ouvre une fenêtre contextuelle avec le titre du marqueur et un bouton « supprimer » qui retire le marqueur de Firestore, et donc de la carte.

Afficher des marqueurs sur une carte Google

Une fois votre carte Google ajouté à votre application, il vous est possible de lui ajouter des marqueurs, grâce à la propriété markers, comme suit :

GoogleMap(
  initialCameraPosition: CameraPosition(
    target: LatLng(48.8566, 2.3522),  // Position de Paris
    zoom: 12,  // Niveau de zoom
  ),
  markers: _markers,  // Marqueurs à afficher sur la carte
  onMapCreated: (controller) => _mapController = controller,  // Initialisation du contrôleur de la carte
  onTap: (position) => _addMarker(position),  // Ajouter un marqueur lors du clic sur la carte
)

Ici, la variable _markers, correspond à la liste des marqueurs à afficher, qui est de type Set<Marker>. Dans mon exemple, je les récupère dans une collection Firestore, de la manière suivante :

Set<Marker> _markers = {};
  final FirebaseFirestore _firestore = FirebaseFirestore.instance;

  @override
  void initState() {
    super.initState();
    _loadMarkers();
  }

  // 🔥 CHARGER LES MARQUEURS DEPUIS FIRESTORE
  void _loadMarkers() async {
    var snapshot = await _firestore.collection('markers').get();
    setState(() {
      _markers = snapshot.docs.map((doc) {
        var data = doc.data();
        return Marker(
          markerId: MarkerId(doc.id),
          position: LatLng(data['lat'], data['lng']),
          infoWindow: InfoWindow(title: data['title'] ?? 'Marqueur'),
          onTap: () => _showMarkerDialog(doc.id, data['title'] ?? "Sans titre"), // 🟢 Afficher popup
        );
      }).toSet();
    });
  }

Comment la position sur la carte est-elle récupérée ?

Lorsque l’utilisateur clique sur un endroit de la carte, la fonction onTap du widget GoogleMap est automatiquement appelée. Cette fonction reçoit en paramètre la position du clic sous forme d’un objet LatLng, qui est une classe définie par l’API de Google Maps. C’est cet objet LatLng qui contient les coordonnées géographiques du point cliqué, à savoir la latitude et la longitude.

Voici ce qui se passe exactement :

1. GoogleMap Widget : Dans le widget GoogleMap, on définit un gestionnaire d’événements pour la fonction onTap, comme ceci :

onTap: (position) => _addMarker(position),

Cette ligne de code signifie que chaque fois que l’utilisateur clique quelque part sur la carte, la fonction onTap récupère les coordonnées de l’endroit où l’utilisateur a cliqué et les transmet sous forme d’un objet LatLng à la fonction _addMarker.

2. LatLng : L’objet position est donc une instance de la classe LatLng, qui contient deux propriétés importantes :

  • latitude : La latitude du lieu où l’utilisateur a cliqué.
  • longitude : La longitude du lieu où l’utilisateur a cliqué.

Par exemple, si l’utilisateur clique sur un endroit particulier de la carte, position.latitude et position.longitude contiendront les valeurs exactes de latitude et de longitude correspondant à cet endroit précis.

3. Passage de la position à la fonction _addMarker : Une fois que la position est récupérée sous forme d’objet LatLng, elle est ensuite transmise à la fonction _addMarker. Cette fonction utilise les coordonnées latitude et longitude pour positionner le marqueur à l’endroit exact où l’utilisateur a cliqué sur la carte.

Créer un marqueur en cliquant sur la carte

Ensuite, on va permettre à un utilisateur d’ajouter un marqueur sur la carte, à l’endroit où il clique et de l’enregistrer dans Firestore :

//Fonction à placer au niveau de la propriété onTap de la carte

void _addMarker(LatLng position) async {
  String markerId = DateTime.now().millisecondsSinceEpoch.toString();  // Génération d'un ID unique
  Marker newMarker = Marker(
    markerId: MarkerId(markerId),
    position: position,  // Position du marqueur
    infoWindow: InfoWindow(title: 'Nouveau marqueur'),  // Titre du marqueur
    onTap: () => _showMarkerDialog(markerId, 'Nouveau marqueur'),  // Action au clic sur le marqueur
  );


  // Sauvegarde du marqueur dans Firestore pour le rendre persistant
  await _firestore.collection('markers').doc(markerId).set({
    'lat': position.latitude,
    'lng': position.longitude,
    'title': 'Nouveau marqueur',
  });
  
    setState(() {
    _markers.add(newMarker);  // Ajout du marqueur à la liste des marqueurs
  });
}

Ici, j’ajoute des marqueurs en cliquant sur la carte, mais il est tout à faire possible de le faire d’une manière différente. Par exemple avec une barre de recherche, en récupérant les coordonnées d’un lieu et en les ajoutant dans Firestore. Le marqueur va alors apparaitre de manière dynamique sur la carte.

Explication:

  • _addMarker(LatLng position) : Cette fonction est appelée lorsque l’utilisateur clique sur la carte.
  • markerId : Chaque marqueur a un identifiant unique. Ici, nous utilisons DateTime.now().millisecondsSinceEpoch.toString() pour générer un ID unique.
  • Marker : Le marqueur est créé avec sa position, un titre et une action (onTap) qui déclenche une boîte de dialogue lorsque l’utilisateur clique dessus.
  • setState : Cette méthode permet de mettre à jour l’interface utilisateur pour afficher le nouveau marqueur sur la carte.

Afficher les informations du marqueur

Dans cet exemple, je souhaite afficher les informations du marqueur dans une information contextuelle, quand l’utilisateur clique dessus :

//Fonction à placer au niveau de la propriété onTap du marqueur

void _showMarkerDialog(String markerId, String title) {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: Text(title),
      content: Text("Que souhaitez-vous faire ?"),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),  // Fermer la boîte de dialogue
          child: Text("Fermer"),
        ),
        TextButton(
          onPressed: () {
            _deleteMarker(markerId);  // Supprimer le marqueur
            Navigator.pop(context);
          },
          child: Text("Supprimer", style: TextStyle(color: Colors.red)),  // Supprimer le marqueur
        ),
      ],
    ),
  );
}
  • showDialog : Cette fonction Flutter affiche une boîte de dialogue.
  • AlertDialog : Le widget utilisé pour créer une boîte de dialogue avec un titre, un contenu et des actions.
  • onPressed : Les actions « Fermer » et « Supprimer » permettent de fermer la boîte ou de supprimer le marqueur.

Supprimer un marqueur

Pour supprimer un marqueur de la carte et de la base de données Firestore, nous utilisons une fonction dédiée. Voici comment cela fonctionne :

void _deleteMarker(String markerId) async {
  setState(() {
    _markers.removeWhere((marker) => marker.markerId.value == markerId);  // Retirer le marqueur de l'affichage
  });

  // Supprimer le marqueur de Firestore
  await _firestore.collection('markers').doc(markerId).delete();
}

La fonction _deleteMarker supprime un marqueur en deux étapes :

  1. Il est retiré de la liste _markers (qui est liée à l’interface utilisateur).
  2. Il est supprimé de Firestore, garantissant que le marqueur est supprimé de la base de données.

Personnaliser les marqueurs

Voici quelques façons de personnaliser vos marqueurs.

Changer la couleur d’un marqueur

Il est possible de modifier la couleur des marqueurs avec des couleurs prédéfinies :

Marker(
  markerId: MarkerId("id-1"),
  position: LatLng(48.8566, 2.3522),
  icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueBlue),  // Marqueur bleu
)

Voici les couleurs disponibles :

  • BitmapDescriptor.hueRed : Rouge
  • BitmapDescriptor.hueGreen : Vert
  • BitmapDescriptor.hueBlue : Bleu
  • BitmapDescriptor.hueYellow : Jaune

Utiliser une icône personnalisée

Vous pouvez également utiliser une image personnalisée comme icône pour le marqueur :

Marker(
  markerId: MarkerId("custom-marker"),
  position: LatLng(48.8566, 2.3522),
  icon: BitmapDescriptor.fromAssetImage(
    ImageConfiguration(size: Size(48, 48)),
    'assets/custom_marker.png',  // Image personnalisée
  ),
)

N’oubliez pas de placer vos images dans un dossier assets et de les déclarer dans le fichier pubspec.yaml.

Modifier la taille de l’icône

Enfin, vous pouvez aussi modifier la taille de l’icône personnalisée pour l’adapter à vos besoins :

BitmapDescriptor.fromAssetImage(
  ImageConfiguration(devicePixelRatio: 2.5),  // Ajuster la taille
  'assets/custom_marker.png',
)
Avatar de Pierre Courtois