Skip to content

Draggable Marker in Flutter

This tutorial shows how to create markers that users can drag to a new position on the map. This is useful for letting users pick a location, adjust a pin, or reposition points of interest.

Prerequisites

Before you begin, ensure you have:

Basic Draggable Marker

Set draggable: true and use onDragEnd to get the new position:

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

class DraggableMarkerScreen extends StatefulWidget {
  @override
  _DraggableMarkerScreenState createState() => _DraggableMarkerScreenState();
}

class _DraggableMarkerScreenState extends State<DraggableMarkerScreen> {
  MapMetricsController? mapController;
  LatLng markerPosition = LatLng(48.8566, 2.3522);

  Set<Marker> get markers => {
    Marker(
      markerId: MarkerId('draggable'),
      position: markerPosition,
      draggable: true,
      onDragEnd: (LatLng newPosition) {
        setState(() {
          markerPosition = newPosition;
        });
      },
      infoWindow: InfoWindow(
        title: 'Drag me!',
        snippet: 'Long press and drag to move',
      ),
      icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueRed),
    ),
  };

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Draggable Marker')),
      body: Column(
        children: [
          // Coordinate display
          Container(
            width: double.infinity,
            padding: EdgeInsets.all(12),
            color: Colors.grey[100],
            child: Text(
              'Position: ${markerPosition.latitude.toStringAsFixed(6)}, '
              '${markerPosition.longitude.toStringAsFixed(6)}',
              style: TextStyle(fontFamily: 'monospace', fontSize: 14),
            ),
          ),
          // Map
          Expanded(
            child: MapMetrics(
              styleUrl: 'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
              onMapCreated: (controller) => mapController = controller,
              initialCameraPosition: CameraPosition(
                target: LatLng(48.8566, 2.3522),
                zoom: 14.0,
              ),
              markers: markers,
            ),
          ),
        ],
      ),
    );
  }
}

Long-press the marker and drag it to a new location. The coordinate display updates in real time.

Drag Events

Track all stages of the drag — start, move, and end:

dart
Marker(
  markerId: MarkerId('tracked_drag'),
  position: markerPosition,
  draggable: true,
  onDragStart: (LatLng startPosition) {
    print('Drag started at: $startPosition');
    // You could show a visual indicator like a shadow
  },
  onDrag: (LatLng currentPosition) {
    // Called continuously while dragging
    // Useful for updating a real-time coordinate display
    setState(() {
      markerPosition = currentPosition;
    });
  },
  onDragEnd: (LatLng endPosition) {
    print('Drag ended at: $endPosition');
    setState(() {
      markerPosition = endPosition;
    });
  },
)

Complete Example: Location Picker

A practical example where the user drags a marker to select a delivery address:

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

class LocationPickerScreen extends StatefulWidget {
  @override
  _LocationPickerScreenState createState() => _LocationPickerScreenState();
}

class _LocationPickerScreenState extends State<LocationPickerScreen> {
  MapMetricsController? mapController;
  LatLng selectedPosition = LatLng(48.8566, 2.3522);
  bool isDragging = false;

  Set<Marker> get markers => {
    Marker(
      markerId: MarkerId('picker'),
      position: selectedPosition,
      draggable: true,
      onDragStart: (LatLng position) {
        setState(() {
          isDragging = true;
        });
      },
      onDragEnd: (LatLng newPosition) {
        setState(() {
          selectedPosition = newPosition;
          isDragging = false;
        });
      },
      icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueBlue),
    ),
  };

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Pick a Location')),
      body: Stack(
        children: [
          MapMetrics(
            styleUrl: 'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
            onMapCreated: (controller) => mapController = controller,
            initialCameraPosition: CameraPosition(
              target: selectedPosition,
              zoom: 15.0,
            ),
            markers: markers,
          ),
          // Bottom card with confirm button
          Positioned(
            bottom: 24,
            left: 16,
            right: 16,
            child: Card(
              elevation: 4,
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(12),
              ),
              child: Padding(
                padding: EdgeInsets.all(16),
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    Text(
                      isDragging ? 'Release to select...' : 'Drag the pin to your location',
                      style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500),
                    ),
                    SizedBox(height: 8),
                    Text(
                      'Lat: ${selectedPosition.latitude.toStringAsFixed(6)}\n'
                      'Lng: ${selectedPosition.longitude.toStringAsFixed(6)}',
                      style: TextStyle(
                        fontFamily: 'monospace',
                        fontSize: 13,
                        color: Colors.grey[600],
                      ),
                    ),
                    SizedBox(height: 12),
                    SizedBox(
                      width: double.infinity,
                      child: ElevatedButton(
                        onPressed: isDragging
                            ? null
                            : () {
                                // Use selectedPosition for your app logic
                                ScaffoldMessenger.of(context).showSnackBar(
                                  SnackBar(
                                    content: Text(
                                      'Location confirmed: '
                                      '${selectedPosition.latitude.toStringAsFixed(4)}, '
                                      '${selectedPosition.longitude.toStringAsFixed(4)}',
                                    ),
                                  ),
                                );
                              },
                        child: Text('Confirm Location'),
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

Marker Drag Properties

PropertyTypeDescription
draggableboolEnable/disable dragging (default: false)
onDragStartValueChanged<LatLng>Called when the user starts dragging
onDragValueChanged<LatLng>Called continuously while dragging
onDragEndValueChanged<LatLng>Called when the user releases the marker

Next Steps


Tip: Long-press on the marker to start dragging it. A regular tap will open the info window if one is set.