Skip to content

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:

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),
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
              ),
            ),
        ],
      ),
    );
  }
}

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


Tip: Dismiss the popup when the user taps on the map background by handling onMapClick and clearing the selected item.