Layer Interactivity

Layer interactivity is different to map interactivity. See Interaction Options to control map interactivity.

The following layers support 'interactivity':


  • These layers don't provide their own 'gesture' callbacks, such as onTap

  • These layers automatically perform with Flutter APIs

    • This means layers report hit on elements through the standard Flutter hit system, and can therefore be detected & handled externally through standard widgets: see Detecting hits & gestures

    • For advanced information about how flutter_map hit tests, see Hit Testing Behaviour

  • This may optionally be combined with flutter_map APIs

    • This allows individual hit elements to be identified externally, through a mechanism of a notifier and element metadata: see Identifying hit elements

Detecting hits & gestures

You may be used to using widgets such as GestureDetector or MouseRegion to detect gestures on other normal widgets. These widgets ask the child to decide whether they were hit, before doing their own logic - e.g. converting the hit to the appropriate callback depending on the gesture.

Because flutter_map's layers are just widgets, they can also be wrapped with other widgets and inserted into the map's children.

This means you can simply wrap layers with GestureDetectors (for example) which will execute callbacks when the layer is hit. Layers tell Flutter they were hit only if at least one of their elements (such as a Polygon) were hit.

Here's an example of how you would detect taps/clicks on polygons, and convert a cursor to a click indicator when hovering over a polygon:

class _InteractivityDemoState extends State<InteractivityDemo> {
    Widget build(BuildContext context) {
        return FlutterMap(
            // ...
            children: [
                // ...
                MouseRegion(
                    hitTestBehavior: HitTestBehavior.deferToChild,
                    cursor: SystemMouseCursors.click,
                    child: GestureDetector(
                        onTap: () {
                            // ...
                        },
                        child: PolygonLayer(
                            // ...
                        ),
                    ),
                ),
            ],
        );
    }
);

Identifying hit elements

To identify which elements (such as Polygons) were hit, flutter_map APIs are required:

  • A LayerHitNotifier exposes results of hit tests

  • Elements may have metadata known as hitValues attached, which identify that specific element - these are then exposed by the hit notifier's events/values.

  • The entire system may be strongly typed through type parameters on various parts, if all the hitValues within a layer share the same type

1

Create a hit notifier

In your widget, define a new field to hold the notifier:

class _InteractivityDemoState extends State<InteractivityDemo> {
    final LayerHitNotifier<String> hitNotifier = ValueNotifier(null);
}

In this example, the types of the hitValue identifiers will be Strings.

(Advanced) Listening to the notifier directly

If you wish to be notified about all* hit testing events, you could use the Listener widget.

If you need to identify hit elements and don't necessarily need the output of a Listener, it's possible to listen to the notifier directly:

class _InteractivityDemoState extends State<InteractivityDemo> {
    final LayerHitNotifier<String> hitNotifier = ValueNotifier(null)
        ..addListener(() {
            final LayerHitResult<String>? result = hitNotifier.value;
            // ...
        });
}

This also allows handling of null notifier values (results). A null result means that the last hit test executed determined there was no hit on the layer at all. Note that the listener's callback is only executed if the previous value was not null (i.e. it will not be repeatedly executed for every missed hit).

2

Attach the hit notifier to a layer

Pass the notifier to the hitNotifier parameter of supported layers. You'll also need to set the type parameter of the layer.

For example, for the PolygonLayer:

class _InteractivityDemoState extends State<InteractivityDemo> {
    Widget build(BuildContext context) {
        return FlutterMap(
            // ...
            children: [
                // ...
                PolygonLayer<String>(
                    hitNotifier: hitNotifier,
                    polygons: [
                        // ...
                    ],
                ),
            ],
        );
    }
}
3

Add hit values to elements

These can be anything useful, and are exposed when their element is hit. Remember to set the element's type parameter.

polygons: [
    Polygon<String>(
        points: [],
        label: "Horse Field",
        hitValue: "Horse",
    ),
    Polygon<String>(
        points: [],
        label: "Hedgehog House",
        hitValue: "Hedgehog",
    ),
    // ...
],
5

Handle hits on elements

Once you have a callback (such as the callback to GestureDetector.onTap), you can handle individual hit events.

To do this, the notifier exposes events of type LayerHitResult when the layer is hit. These results can be retrieved through the notifier's value getter:

final LayerHitResult<String>? result = hitNotifier.value;

Most users can ignore results which are null (when getting the result within a gesture callback, for example).

The result exposes 3 properties:

  • hitValues: the hit values of all elements that were hit, ordered by their corresponding element, first-to-last, visually top-to-bottom

  • coordinate: the geographic coordinate of the hit location (which may not lie on any element)

  • point: the screen point of the hit location

Therefore, it's unnecessary to use MapOptions.on... in combination with layer interactivity to detect the position of a tap.

Elements without a hit value are not included in hitValues. Therefore, it may be empty if elements were hit but no hitValues were defined.

Example

class _InteractivityDemoState extends State<InteractivityDemo> {
    final LayerHitNotifier<String> hitNotifier = ValueNotifier(null);

    Widget build(BuildContext context) {
        return FlutterMap(
            // ...
            children: [
                // ...
                MouseRegion(
                    hitTestBehavior: HitTestBehavior.deferToChild,
                    cursor: SystemMouseCursors.click,
                    child: GestureDetector(
                        onTap: () {
                            final LayerHitResult<String>? result = hitNotifier.value;
                            if (result == null) return;
                            
                            for (final hitValue in result.hitValues) {
                                print('Tapped on a $hitValue');
                            }
                            print('Eating the grass at ${result.coordinate}');
                        },
                        child: PolygonLayer<String>(
                            hitNotifier: hitNotifier,
                            polygons: [
                                Polygon<String>(
                                    points: [], // overlapping coordinates with 2nd
                                    label: "Horse Field",
                                    hitValue: "Horse",
                                ),
                                Polygon<String>(
                                    points: [], // overlapping coordinates with 1st
                                    label: "Hedgehog House",
                                    hitValue: "Hedgehog",
                                ),
                            ],
                        ),
                    ),
                ),
            ],
        );
    }
);

Last updated

Was this helpful?