Popup on Click in Flutter
This tutorial shows how to display a popup with detailed information when the user taps on a map feature like a marker or polygon.
Prerequisites
Before you begin, ensure you have:
- Completed the Flutter Setup Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
Popup on Marker Tap
Show a detail card when any marker is tapped:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class PopupOnClickScreen extends StatefulWidget {
@override
_PopupOnClickScreenState createState() => _PopupOnClickScreenState();
}
class _PopupOnClickScreenState extends State<PopupOnClickScreen> {
MapMetricsController? mapController;
Map<String, dynamic>? selectedPlace;
final List<Map<String, dynamic>> places = [
{
'id': 'eiffel',
'name': 'Eiffel Tower',
'description': 'Iconic iron lattice tower built in 1889.',
'category': 'Landmark',
'position': LatLng(48.8584, 2.2945),
'rating': 4.7,
},
{
'id': 'louvre',
'name': 'Louvre Museum',
'description': 'World\'s largest art museum, home to the Mona Lisa.',
'category': 'Museum',
'position': LatLng(48.8606, 2.3376),
'rating': 4.8,
},
{
'id': 'notre_dame',
'name': 'Notre-Dame Cathedral',
'description': 'Medieval Catholic cathedral, a masterpiece of Gothic architecture.',
'category': 'Church',
'position': LatLng(48.8530, 2.3499),
'rating': 4.6,
},
{
'id': 'sacre_coeur',
'name': 'Sacré-Cœur',
'description': 'White-domed basilica on the highest point in Paris.',
'category': 'Church',
'position': LatLng(48.8867, 2.3431),
'rating': 4.5,
},
];
Set<Marker> get markers => places.map((place) {
return Marker(
markerId: MarkerId(place['id']),
position: place['position'],
icon: BitmapDescriptor.defaultMarkerWithHue(
place['category'] == 'Museum'
? BitmapDescriptor.hueBlue
: place['category'] == 'Church'
? BitmapDescriptor.hueViolet
: BitmapDescriptor.hueRed,
),
);
}).toSet();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Tap a Marker')),
body: Stack(
children: [
MapMetrics(
styleUrl: 'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (controller) => mapController = controller,
onMarkerTapped: (MarkerId markerId) {
final place = places.firstWhere(
(p) => p['id'] == markerId.value,
);
setState(() => selectedPlace = place);
},
onMapClick: (Point point, LatLng coordinates) {
// Dismiss popup when tapping the map
setState(() => selectedPlace = null);
},
initialCameraPosition: CameraPosition(
target: LatLng(48.8620, 2.3200),
zoom: 13.0,
),
markers: markers,
),
// Popup card
if (selectedPlace != null)
Positioned(
bottom: 24,
left: 16,
right: 16,
child: Card(
elevation: 6,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
selectedPlace!['name'],
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
IconButton(
icon: Icon(Icons.close, size: 20),
onPressed: () =>
setState(() => selectedPlace = null),
padding: EdgeInsets.zero,
constraints: BoxConstraints(),
),
],
),
SizedBox(height: 4),
Container(
padding:
EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: Colors.blue[50],
borderRadius: BorderRadius.circular(12),
),
child: Text(
selectedPlace!['category'],
style: TextStyle(fontSize: 12, color: Colors.blue),
),
),
SizedBox(height: 8),
Text(
selectedPlace!['description'],
style: TextStyle(
fontSize: 14, color: Colors.grey[700]),
),
SizedBox(height: 8),
Row(
children: [
Icon(Icons.star, color: Colors.amber, size: 18),
SizedBox(width: 4),
Text(
'${selectedPlace!['rating']}',
style: TextStyle(fontWeight: FontWeight.w500),
),
],
),
],
),
),
),
),
],
),
);
}
}Popup on Map Tap (Any Location)
Show coordinates and reverse-geocoded info when tapping anywhere:
dart
MapMetrics(
// ... config
onMapClick: (Point point, LatLng coordinates) {
showModalBottomSheet(
context: context,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
builder: (context) => Padding(
padding: EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Tapped Location',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
SizedBox(height: 12),
_infoRow(Icons.location_on, 'Latitude',
coordinates.latitude.toStringAsFixed(6)),
_infoRow(Icons.location_on, 'Longitude',
coordinates.longitude.toStringAsFixed(6)),
_infoRow(Icons.touch_app, 'Screen X', '${point.x.toInt()}'),
_infoRow(Icons.touch_app, 'Screen Y', '${point.y.toInt()}'),
SizedBox(height: 12),
],
),
),
);
},
)
Widget _infoRow(IconData icon, String label, String value) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 4),
child: Row(
children: [
Icon(icon, size: 16, color: Colors.grey),
SizedBox(width: 8),
Text('$label: ', style: TextStyle(fontWeight: FontWeight.w500)),
Text(value, style: TextStyle(fontFamily: 'monospace')),
],
),
);
}Next Steps
- Add a Popup — InfoWindow-based popups
- Show Polygon Info on Click — Popup for polygon features
- Map Interactions — Full interaction handling
Tip: Dismiss the popup when the user taps on the map background by handling onMapClick and clearing the selected item.