Show Polygon Info on Click in Flutter
This tutorial shows how to display information about a polygon when the user taps on it — useful for showing zone details, area statistics, or district information.
Prerequisites
Before you begin, ensure you have:
- Completed the Flutter Setup Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
Tappable Polygons with Info Card
Tap a polygon to see its details in a bottom card:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class PolygonInfoScreen extends StatefulWidget {
@override
_PolygonInfoScreenState createState() => _PolygonInfoScreenState();
}
class _PolygonInfoScreenState extends State<PolygonInfoScreen> {
MapMetricsController? mapController;
String? selectedZoneId;
final List<Map<String, dynamic>> zones = [
{
'id': 'zone_a',
'name': '1st Arrondissement',
'description': 'Historic center with the Louvre, Tuileries Garden, and Palais Royal.',
'population': '17,600',
'area': '1.83 km²',
'color': Colors.blue,
'points': [
LatLng(48.865, 2.327),
LatLng(48.865, 2.345),
LatLng(48.856, 2.345),
LatLng(48.856, 2.327),
],
},
{
'id': 'zone_b',
'name': '4th Arrondissement',
'description': 'Home to Notre-Dame, Île de la Cité, and the Marais district.',
'population': '28,600',
'area': '1.60 km²',
'color': Colors.green,
'points': [
LatLng(48.858, 2.345),
LatLng(48.858, 2.365),
LatLng(48.848, 2.365),
LatLng(48.848, 2.345),
],
},
{
'id': 'zone_c',
'name': '5th Arrondissement',
'description': 'The Latin Quarter with the Panthéon and Luxembourg Gardens.',
'population': '60,200',
'area': '2.54 km²',
'color': Colors.orange,
'points': [
LatLng(48.852, 2.335),
LatLng(48.852, 2.360),
LatLng(48.842, 2.360),
LatLng(48.842, 2.335),
],
},
];
Map<String, dynamic>? get selectedZone =>
selectedZoneId != null
? zones.firstWhere((z) => z['id'] == selectedZoneId)
: null;
Set<Polygon> get polygons => zones.map((zone) {
final isSelected = zone['id'] == selectedZoneId;
final Color color = zone['color'];
return Polygon(
polygonId: PolygonId(zone['id']),
points: zone['points'],
strokeWidth: isSelected ? 3 : 2,
strokeColor: isSelected ? color : color.withOpacity(0.6),
fillColor: isSelected ? color.withOpacity(0.35) : color.withOpacity(0.15),
consumeTapEvents: true,
onTap: () {
setState(() {
selectedZoneId = selectedZoneId == zone['id'] ? null : zone['id'];
});
},
);
}).toSet();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('District Info')),
body: Stack(
children: [
MapMetrics(
styleUrl: 'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (controller) => mapController = controller,
onMapClick: (Point point, LatLng coordinates) {
// Dismiss info when tapping outside polygons
setState(() => selectedZoneId = null);
},
initialCameraPosition: CameraPosition(
target: LatLng(48.855, 2.347),
zoom: 14.0,
),
polygons: polygons,
),
// Info card
if (selectedZone != 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: [
Container(
width: 14,
height: 14,
decoration: BoxDecoration(
color: selectedZone!['color'],
borderRadius: BorderRadius.circular(3),
),
),
SizedBox(width: 8),
Expanded(
child: Text(
selectedZone!['name'],
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
IconButton(
icon: Icon(Icons.close, size: 20),
onPressed: () =>
setState(() => selectedZoneId = null),
padding: EdgeInsets.zero,
constraints: BoxConstraints(),
),
],
),
SizedBox(height: 8),
Text(
selectedZone!['description'],
style: TextStyle(color: Colors.grey[700], fontSize: 14),
),
SizedBox(height: 12),
Row(
children: [
_statChip(Icons.people, 'Population',
selectedZone!['population']),
SizedBox(width: 16),
_statChip(
Icons.square_foot, 'Area', selectedZone!['area']),
],
),
],
),
),
),
),
],
),
);
}
Widget _statChip(IconData icon, String label, String value) {
return Row(
children: [
Icon(icon, size: 16, color: Colors.grey),
SizedBox(width: 4),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: TextStyle(fontSize: 11, color: Colors.grey)),
Text(value,
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)),
],
),
],
);
}
}Highlighting the Selected Polygon
The selected polygon gets:
- Thicker border (
strokeWidth: 3) - More opaque fill (
0.35vs0.15) - Full-color stroke instead of semi-transparent
This visual feedback makes it clear which zone is selected.
Next Steps
- Add a Polygon — Basic polygon drawing
- Popup on Click — Popups on markers
- Multiple Geometries — Mix polygons with other shapes
Tip: Use consumeTapEvents: true on polygons so taps are captured by the polygon and don't fall through to the map's onMapClick handler.