Añadir y eliminar marcadores en un mapa de Google Maps
Ahora que has añadido un mapa de Google Maps a tu aplicación Flutter, aquí te mostramos cómo añadir marcadores para indicar puntos importantes o lugares guardados por el usuario:
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();
}
// 🔥 CARGAR LOS MARCADORES DESDE 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'] ?? 'Marcador'),
onTap: () => _showMarkerDialog(doc.id, data['title'] ?? "Sin título"), // 🟢 Mostrar popup
);
}).toSet();
});
}
// 🎯 AÑADIR UN MARCADOR AL HACER CLIC EN EL MAPA
void _addMarker(LatLng position) async {
String markerId = DateTime.now().millisecondsSinceEpoch.toString();
Marker newMarker = Marker(
markerId: MarkerId(markerId),
position: position,
infoWindow: InfoWindow(title: 'Nuevo marcador'),
onTap: () => _showMarkerDialog(markerId, 'Nuevo marcador'), // 🟢 Mostrar popup
);
setState(() {
_markers.add(newMarker);
});
// 🔥 Guardar en Firestore
await _firestore.collection('markers').doc(markerId).set({
'lat': position.latitude,
'lng': position.longitude,
'title': 'Nuevo marcador'
});
}
// 📌 MOSTRAR CUADRO DE INFORMACIÓN + ELIMINAR
void _showMarkerDialog(String markerId, String title) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(title),
content: Text("¿Qué deseas hacer?"),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text("Cerrar"),
),
TextButton(
onPressed: () {
_deleteMarker(markerId);
Navigator.pop(context);
},
child: Text("Eliminar", style: TextStyle(color: Colors.red)),
),
],
),
);
}
// ❌ ELIMINAR UN MARCADOR
void _deleteMarker(String markerId) async {
setState(() {
_markers.removeWhere((marker) => marker.markerId.value == markerId);
});
// 🔥 Eliminar de Firestore
await _firestore.collection('markers').doc(markerId).delete();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Mapa con marcadores")),
body: GoogleMap(
initialCameraPosition: CameraPosition(
target: LatLng(48.8566, 2.3522), // París como ejemplo
zoom: 12,
),
markers: _markers,
onMapCreated: (controller) => _mapController = controller,
onTap: (position) => _addMarker(position), // 🟢 Clic en el mapa => Añadir un marcador
),
);
}
}
En este ejemplo, el usuario puede añadir un marcador en el mapa con un simple clic. El lugar se guarda en Firestore para poder visualizarse en tiempo real. Al hacer clic en un marcador, se abre una ventana emergente con el título del marcador y un botón de «eliminar» que quita el marcador de Firestore, y por ende, del mapa.
Mostrar marcadores en un mapa de Google
Una vez añadido tu mapa de Google a tu aplicación, puedes agregarle marcadores utilizando la propiedad markers
, de la siguiente forma:
GoogleMap(
initialCameraPosition: CameraPosition(
target: LatLng(48.8566, 2.3522), // Posición de París
zoom: 12, // Nivel de zoom
),
markers: _markers, // Marcadores a mostrar en el mapa
onMapCreated: (controller) => _mapController = controller, // Inicializar el controlador del mapa
onTap: (position) => _addMarker(position), // Añadir un marcador al hacer clic en el mapa
)
Aquí, la variable _markers
corresponde a la lista de los marcadores a mostrar, que es de tipo Set<Marker>
. En mi ejemplo, los recupero desde una colección de Firestore, de la siguiente manera:
Set<Marker> _markers = {};
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
@override
void initState() {
super.initState();
_loadMarkers();
}
// 🔥 CARGAR LOS MARCADORES DESDE 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'] ?? 'Marcador'),
onTap: () => _showMarkerDialog(doc.id, data['title'] ?? "Sin título"), // 🟢 Mostrar popup
);
}).toSet();
});
}
¿Cómo se obtiene la posición en el mapa?
Cuando el usuario hace clic en un lugar del mapa, la función onTap
del widget GoogleMap
se llama automáticamente. Esta función recibe como parámetro la posición del clic en forma de un objeto LatLng
, que es una clase definida por la API de Google Maps. Es este objeto LatLng
el que contiene las coordenadas geográficas del punto clicado, es decir, la latitud y longitud.
Esto es lo que sucede exactamente:
1. Widget GoogleMap: En el widget GoogleMap
, definimos un controlador de eventos para la función onTap
, de la siguiente manera:
onTap: (position) => _addMarker(position),
Esta línea de código significa que cada vez que el usuario haga clic en algún lugar del mapa, la función onTap
recuperará las coordenadas del lugar donde hizo clic el usuario y las pasará en forma de un objeto LatLng
a la función _addMarker
.
2. LatLng: El objeto position
es una instancia de la clase LatLng
, que contiene dos propiedades importantes:
- latitude: La latitud del lugar donde el usuario hizo clic.
- longitude: La longitud del lugar donde el usuario hizo clic.
Por ejemplo, si el usuario hace clic en un lugar determinado del mapa, position.latitude
y position.longitude
contendrán los valores exactos de latitud y longitud correspondientes a ese lugar específico.
3. Pasar la posición a la función _addMarker: Una vez que se recupera la posición en forma de objeto LatLng
, esta se pasa a la función _addMarker
. Esta función utiliza las coordenadas de latitud y longitud para colocar el marcador en el lugar exacto donde el usuario hizo clic en el mapa.
Crear un marcador al hacer clic en el mapa
Luego, vamos a permitir que un usuario añada un marcador en el mapa, en el lugar donde haga clic, y lo guarde en Firestore:
// Función que se coloca en la propiedad onTap del mapa
void _addMarker(LatLng position) async {
String markerId = DateTime.now().millisecondsSinceEpoch.toString(); // Generación de un ID único
Marker newMarker = Marker(
markerId: MarkerId(markerId),
position: position, // Posición del marcador
infoWindow: InfoWindow(title: 'Nuevo marcador'), // Título del marcador
onTap: () => _showMarkerDialog(markerId, 'Nuevo marcador'), // Acción al hacer clic en el marcador
);
// Guardar el marcador en Firestore para hacerlo persistente
await _firestore.collection('markers').doc(markerId).set({
'lat': position.latitude,
'lng': position.longitude,
'title': 'Nuevo marcador',
});
setState(() {
_markers.add(newMarker); // Añadir el marcador a la lista de marcadores
});
}
Explicación:
- _addMarker(LatLng position): Esta función se llama cuando el usuario hace clic en el mapa.
- markerId: Cada marcador tiene un identificador único. Aquí, utilizamos
DateTime.now().millisecondsSinceEpoch.toString()
para generar un ID único. - Marker: El marcador se crea con su posición, un título y una acción (
onTap
) que muestra un cuadro de diálogo cuando el usuario hace clic en él. - setState: Este método permite actualizar la interfaz de usuario para mostrar el nuevo marcador en el mapa.
Mostrar la información del marcador
En este ejemplo, quiero mostrar la información del marcador en una ventana emergente cuando el usuario hace clic en él:
// Función que se coloca en la propiedad onTap del marcador
void _showMarkerDialog(String markerId, String title) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(title),
content: Text("¿Qué deseas hacer?"),
actions: [
TextButton(
onPressed: () => Navigator.pop(context), // Cerrar la ventana emergente
child: Text("Cerrar"),
),
TextButton(
onPressed: () {
_deleteMarker(markerId); // Eliminar el marcador
Navigator.pop(context);
},
child: Text("Eliminar", style: TextStyle(color: Colors.red)), // Eliminar el marcador
),
],
),
);
}
- showDialog: Esta función de Flutter muestra una ventana emergente.
- AlertDialog: El widget utilizado para crear una ventana emergente con un título, contenido y acciones.
- onPressed: Las acciones «Cerrar» y «Eliminar» permiten cerrar la ventana o eliminar el marcador.
Eliminar un marcador
Para eliminar un marcador del mapa y de la base de datos de Firestore, usamos una función dedicada. Así es como funciona:
void _deleteMarker(String markerId) async {
setState(() {
_markers.removeWhere((marker) => marker.markerId.value == markerId); // Eliminar el marcador de la interfaz
});
// Eliminar el marcador de Firestore
await _firestore.collection('markers').doc(markerId).delete();
}
La función _deleteMarker
elimina un marcador en dos pasos:
- Se elimina de la lista
_markers
(que está vinculada a la interfaz de usuario). - Se elimina de Firestore, asegurando que el marcador se elimina de la base de datos.
Personalizar los marcadores
Aquí hay algunas maneras de personalizar tus marcadores.
Cambiar el color de un marcador
Es posible modificar el color de los marcadores con colores predefinidos:
Marker(
markerId: MarkerId("id-1"),
position: LatLng(48.8566, 2.3522),
icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueBlue), // Marcador azul
)
Colores disponibles:
BitmapDescriptor.hueRed
: RojoBitmapDescriptor.hueGreen
: VerdeBitmapDescriptor.hueBlue
: AzulBitmapDescriptor.hueYellow
: Amarillo
Usar un icono personalizado
También puedes usar una imagen personalizada como icono para el marcador:
Marker(
markerId: MarkerId("custom-marker"),
position: LatLng(48.8566, 2.3522),
icon: BitmapDescriptor.fromAssetImage(
ImageConfiguration(size: Size(48, 48)),
'assets/custom_marker.png', // Imagen personalizada
),
)
No olvides colocar tus imágenes en una carpeta de assets y declararlas en el archivo pubspec.yaml
.
Modificar el tamaño del icono
Finalmente, también puedes modificar el tamaño del icono personalizado para adaptarlo a tus necesidades:
BitmapDescriptor.fromAssetImage(
ImageConfiguration(devicePixelRatio: 2.5), // Ajustar el tamaño
'assets/custom_marker.png',
)