Skip to content

Display a Popup on Long Press in Flutter

This tutorial shows how to show a popup when the user long-presses on the map — the Flutter equivalent of hover popups on web. Great for adding new markers, getting location info, or context menus.

Prerequisites

Before you begin, ensure you have:

Basic Long Press Popup

Show a dialog with coordinates when the user long-presses:

dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';

class LongPressPopupScreen extends StatefulWidget {
  @override
  _LongPressPopupScreenState createState() => _LongPressPopupScreenState();
}

class _LongPressPopupScreenState extends State<LongPressPopupScreen> {
  MapMetricsController? mapController;
  LatLng? longPressPosition;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Long Press Popup')),
      body: Stack(
        children: [
          MapMetrics(
            styleUrl:
                'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
            onMapCreated: (MapMetricsController controller) {
              mapController = controller;
            },
            initialCameraPosition: CameraPosition(
              target: LatLng(48.8566, 2.3522),
              zoom: 13.0,
            ),
            onMapLongClick: (Point point, LatLng coordinates) {
              setState(() {
                longPressPosition = coordinates;
              });
            },
            onMapClick: (Point point, LatLng coordinates) {
              // Dismiss popup on regular tap
              setState(() {
                longPressPosition = null;
              });
            },
            markers: longPressPosition != null
                ? {
                    Marker(
                      markerId: MarkerId('long_press'),
                      position: longPressPosition!,
                      icon: BitmapDescriptor.defaultMarkerWithHue(
                          BitmapDescriptor.hueViolet),
                    ),
                  }
                : {},
          ),
          // Popup card
          if (longPressPosition != 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: [
                          Icon(Icons.location_on, color: Colors.purple),
                          SizedBox(width: 8),
                          Text('Long Press Location',
                              style: TextStyle(
                                  fontSize: 16,
                                  fontWeight: FontWeight.bold)),
                          Spacer(),
                          IconButton(
                            icon: Icon(Icons.close, size: 20),
                            onPressed: () {
                              setState(() {
                                longPressPosition = null;
                              });
                            },
                          ),
                        ],
                      ),
                      SizedBox(height: 8),
                      Text(
                        'Lat: ${longPressPosition!.latitude.toStringAsFixed(6)}',
                        style: TextStyle(fontFamily: 'monospace'),
                      ),
                      Text(
                        'Lng: ${longPressPosition!.longitude.toStringAsFixed(6)}',
                        style: TextStyle(fontFamily: 'monospace'),
                      ),
                    ],
                  ),
                ),
              ),
            ),
        ],
      ),
    );
  }
}

Long Press Context Menu

Show an action menu with options like "Add Marker", "Get Directions", etc.:

dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';

class ContextMenuScreen extends StatefulWidget {
  @override
  _ContextMenuScreenState createState() => _ContextMenuScreenState();
}

class _ContextMenuScreenState extends State<ContextMenuScreen> {
  MapMetricsController? mapController;
  Set<Marker> userMarkers = {};
  int markerCount = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Context Menu'),
        actions: [
          if (userMarkers.isNotEmpty)
            TextButton(
              onPressed: () {
                setState(() {
                  userMarkers.clear();
                  markerCount = 0;
                });
              },
              child: Text('Clear All',
                  style: TextStyle(color: Colors.white)),
            ),
        ],
      ),
      body: MapMetrics(
        styleUrl:
            'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
        onMapCreated: (MapMetricsController controller) {
          mapController = controller;
        },
        initialCameraPosition: CameraPosition(
          target: LatLng(48.8566, 2.3522),
          zoom: 13.0,
        ),
        onMapLongClick: (Point point, LatLng coordinates) {
          _showContextMenu(coordinates);
        },
        markers: userMarkers,
      ),
    );
  }

  void _showContextMenu(LatLng position) {
    showModalBottomSheet(
      context: context,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
      ),
      builder: (context) {
        return SafeArea(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              // Drag handle
              Container(
                width: 40,
                height: 4,
                margin: EdgeInsets.only(top: 12),
                decoration: BoxDecoration(
                  color: Colors.grey[300],
                  borderRadius: BorderRadius.circular(2),
                ),
              ),
              Padding(
                padding: EdgeInsets.all(16),
                child: Text(
                  '${position.latitude.toStringAsFixed(4)}, '
                  '${position.longitude.toStringAsFixed(4)}',
                  style: TextStyle(
                      color: Colors.grey[600], fontFamily: 'monospace'),
                ),
              ),
              ListTile(
                leading: Icon(Icons.add_location, color: Colors.blue),
                title: Text('Add Marker'),
                onTap: () {
                  Navigator.pop(context);
                  _addMarker(position);
                },
              ),
              ListTile(
                leading: Icon(Icons.directions, color: Colors.green),
                title: Text('Get Directions Here'),
                onTap: () {
                  Navigator.pop(context);
                  // Handle directions
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(
                        content: Text(
                            'Directions to ${position.latitude.toStringAsFixed(4)}, ${position.longitude.toStringAsFixed(4)}')),
                  );
                },
              ),
              ListTile(
                leading: Icon(Icons.copy, color: Colors.orange),
                title: Text('Copy Coordinates'),
                onTap: () {
                  Navigator.pop(context);
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(content: Text('Coordinates copied!')),
                  );
                },
              ),
              ListTile(
                leading: Icon(Icons.info_outline, color: Colors.purple),
                title: Text('What\'s Here?'),
                onTap: () {
                  Navigator.pop(context);
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(
                        content: Text('Searching for nearby places...')),
                  );
                },
              ),
              SizedBox(height: 8),
            ],
          ),
        );
      },
    );
  }

  void _addMarker(LatLng position) {
    markerCount++;
    setState(() {
      userMarkers.add(
        Marker(
          markerId: MarkerId('user_$markerCount'),
          position: position,
          icon: BitmapDescriptor.defaultMarkerWithHue(
              BitmapDescriptor.hueBlue),
          infoWindow: InfoWindow(
            title: 'Marker $markerCount',
            snippet:
                '${position.latitude.toStringAsFixed(4)}, ${position.longitude.toStringAsFixed(4)}',
          ),
        ),
      );
    });
  }
}

Long Press vs Tap Comparison

GestureCallbackBest For
TaponMapClickSelect, dismiss, quick actions
Long pressonMapLongClickContext menus, add markers, advanced actions
Marker tapMarker.onTapShow details for a specific marker

Next Steps


Tip: Long press is the mobile convention for "right-click" context menus. Use showModalBottomSheet for a native-feeling action menu — it's easier to reach with one hand than a popup card at the top of the screen.