# Simple User Interface

## Introduction

This Example shows how to display simple mapview and perform basic map actions:

-   Display MapView Widget
-   User Interface on MapView
    -   Customize MapView Size
    -   MapView UI Settings
    -   MapView GestureDetector

| ![documentation image](https://static.nextbillion.io/docs-next/docs/maps/flutter/examples/simple-user-interface-2.webp) | ![documentation image](https://static.nextbillion.io/docs-next/docs/maps/flutter/examples/simple-user-interface-1.webp) |
| --- | --- |
| **Android snapshot** | **iOS snapshot** |



For all code examples, refer to [Flutter Maps SDK Code Examples](https://github.com/nextbillion-ai/nb-maps-flutter)

**MapUiPage** [view source](https://github.com/nextbillion-ai/nb-maps-flutter/blob/main/example/lib/map_ui.dart)

```dart
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:nb_maps_flutter/nb_maps_flutter.dart';

import 'main.dart';
import 'page.dart';

final LatLngBounds sydneyBounds = LatLngBounds(
  southwest: const LatLng(-34.022631, 150.620685),
  northeast: const LatLng(-33.571835, 151.325952),
);

class MapUiPage extends ExamplePage {
  MapUiPage() : super(const Icon(Icons.map), 'User interface');

  @override
  Widget build(BuildContext context) {
    return const MapUiBody();
  }
}

class MapUiBody extends StatefulWidget {
  const MapUiBody();

  @override
  State<StatefulWidget> createState() => MapUiBodyState();
}

class MapUiBodyState extends State<MapUiBody> {
  MapUiBodyState();

  static final CameraPosition _kInitialPosition = const CameraPosition(
    target: LatLng(-33.852, 151.211),
    zoom: 11.0,
  );

  NextbillionMapController? mapController;
  CameraPosition _position = _kInitialPosition;
  bool _isMoving = false;
  bool _compassEnabled = true;
  bool _mapExpanded = true;
  CameraTargetBounds _cameraTargetBounds = CameraTargetBounds.unbounded;
  MinMaxZoomPreference _minMaxZoomPreference = MinMaxZoomPreference.unbounded;
  int _styleStringIndex = 0;
  // Style string can a reference to a local or remote resources.
  // On Android the raw JSON can also be passed via a styleString, on iOS this is not supported.
  List<String> _styleStrings = [
    "https://api.nextbillion.io/maps/streets/style.json",
    "https://api.nextbillion.io/maps/streets/style.json",
    "https://api.nextbillion.io/maps/streets/style.json"
  ];
  List<String> _styleStringLabels = [
    "NBMAP_STREETS",
    "SATELLITE",
    "LOCAL_ASSET"
  ];
  bool _rotateGesturesEnabled = true;
  bool _scrollGesturesEnabled = true;
  bool? _doubleClickToZoomEnabled;
  bool _tiltGesturesEnabled = true;
  bool _zoomGesturesEnabled = true;
  bool _myLocationEnabled = true;
  bool _telemetryEnabled = true;
  MyLocationTrackingMode _myLocationTrackingMode = MyLocationTrackingMode.None;
  List<Object>? _featureQueryFilter;
  Fill? _selectedFill;

  @override
  void initState() {
    super.initState();
  }

  void _onMapChanged() {
    setState(() {
      _extractMapInfo();
    });
  }

  void _extractMapInfo() {
    final position = mapController!.cameraPosition;
    if (position != null) _position = position;
    _isMoving = mapController!.isCameraMoving;
  }

  @override
  void dispose() {
    mapController?.removeListener(_onMapChanged);
    super.dispose();
  }

  Widget _myLocationTrackingModeCycler() {
    final MyLocationTrackingMode nextType = MyLocationTrackingMode.values[
        (_myLocationTrackingMode.index + 1) %
            MyLocationTrackingMode.values.length];
    return TextButton(
      child: Text('change to $nextType'),
      onPressed: () {
        setState(() {
          _myLocationTrackingMode = nextType;
        });
      },
    );
  }

  Widget _queryFilterToggler() {
    return TextButton(
      child: Text(
          'filter zoo on click ${_featureQueryFilter == null ? 'disabled' : 'enabled'}'),
      onPressed: () {
        setState(() {
          if (_featureQueryFilter == null) {
            _featureQueryFilter = [
              "==",
              ["get", "type"],
              "zoo"
            ];
          } else {
            _featureQueryFilter = null;
          }
        });
      },
    );
  }

  Widget _mapSizeToggler() {
    return TextButton(
      child: Text('${_mapExpanded ? 'shrink' : 'expand'} map'),
      onPressed: () {
        setState(() {
          _mapExpanded = !_mapExpanded;
        });
      },
    );
  }

  Widget _compassToggler() {
    return TextButton(
      child: Text('${_compassEnabled ? 'disable' : 'enable'} compasss'),
      onPressed: () {
        setState(() {
          _compassEnabled = !_compassEnabled;
        });
      },
    );
  }

  Widget _latLngBoundsToggler() {
    return TextButton(
      child: Text(
        _cameraTargetBounds.bounds == null
            ? 'bound camera target'
            : 'release camera target',
      ),
      onPressed: () {
        setState(() {
          _cameraTargetBounds = _cameraTargetBounds.bounds == null
              ? CameraTargetBounds(sydneyBounds)
              : CameraTargetBounds.unbounded;
        });
      },
    );
  }

  Widget _zoomBoundsToggler() {
    return TextButton(
      child: Text(_minMaxZoomPreference.minZoom == null
          ? 'bound zoom'
          : 'release zoom'),
      onPressed: () {
        setState(() {
          _minMaxZoomPreference = _minMaxZoomPreference.minZoom == null
              ? const MinMaxZoomPreference(12.0, 16.0)
              : MinMaxZoomPreference.unbounded;
        });
      },
    );
  }

  Widget _setStyleToSatellite() {
    return TextButton(
      child: Text(
          'change map style to ${_styleStringLabels[(_styleStringIndex + 1) % _styleStringLabels.length]}'),
      onPressed: () {
        setState(() {
          _styleStringIndex = (_styleStringIndex + 1) % _styleStrings.length;
        });
      },
    );
  }

  Widget _rotateToggler() {
    return TextButton(
      child: Text('${_rotateGesturesEnabled ? 'disable' : 'enable'} rotate'),
      onPressed: () {
        setState(() {
          _rotateGesturesEnabled = !_rotateGesturesEnabled;
        });
      },
    );
  }

  Widget _scrollToggler() {
    return TextButton(
      child: Text('${_scrollGesturesEnabled ? 'disable' : 'enable'} scroll'),
      onPressed: () {
        setState(() {
          _scrollGesturesEnabled = !_scrollGesturesEnabled;
        });
      },
    );
  }

  Widget _doubleClickToZoomToggler() {
    final stateInfo = _doubleClickToZoomEnabled == null
        ? "disable"
        : _doubleClickToZoomEnabled!
            ? 'unset'
            : 'enable';
    return TextButton(
      child: Text('$stateInfo double click to zoom'),
      onPressed: () {
        setState(() {
          if (_doubleClickToZoomEnabled == null) {
            _doubleClickToZoomEnabled = false;
          } else if (!_doubleClickToZoomEnabled!) {
            _doubleClickToZoomEnabled = true;
          } else {
            _doubleClickToZoomEnabled = null;
          }
        });
      },
    );
  }

  Widget _tiltToggler() {
    return TextButton(
      child: Text('${_tiltGesturesEnabled ? 'disable' : 'enable'} tilt'),
      onPressed: () {
        setState(() {
          _tiltGesturesEnabled = !_tiltGesturesEnabled;
        });
      },
    );
  }

  Widget _zoomToggler() {
    return TextButton(
      child: Text('${_zoomGesturesEnabled ? 'disable' : 'enable'} zoom'),
      onPressed: () {
        setState(() {
          _zoomGesturesEnabled = !_zoomGesturesEnabled;
        });
      },
    );
  }

  Widget _myLocationToggler() {
    return TextButton(
      child: Text('${_myLocationEnabled ? 'disable' : 'enable'} my location'),
      onPressed: () {
        setState(() {
          _myLocationEnabled = !_myLocationEnabled;
        });
      },
    );
  }

  Widget _telemetryToggler() {
    return TextButton(
      child: Text('${_telemetryEnabled ? 'disable' : 'enable'} telemetry'),
      onPressed: () {
        setState(() {
          _telemetryEnabled = !_telemetryEnabled;
        });
        mapController?.setTelemetryEnabled(_telemetryEnabled);
      },
    );
  }

  Widget _visibleRegionGetter() {
    return TextButton(
      child: Text('get currently visible region'),
      onPressed: () async {
        var result = await mapController!.getVisibleRegion();
        ScaffoldMessenger.of(context).showSnackBar(SnackBar(
          content: Text(
              "SW: ${result.southwest.toString()} NE: ${result.northeast.toString()}"),
        ));
      },
    );
  }

  _clearFill() {
    if (_selectedFill != null) {
      mapController!.removeFill(_selectedFill!);
      setState(() {
        _selectedFill = null;
      });
    }
  }

  _drawFill(List<dynamic> features) async {
    Map<String, dynamic>? feature =
        features.firstWhereOrNull((f) => f['geometry']['type'] == 'Polygon');

    if (feature != null) {
      List<List<LatLng>> geometry = feature['geometry']['coordinates']
          .map(
              (ll) => ll.map((l) => LatLng(l[1], l[0])).toList().cast<LatLng>())
          .toList()
          .cast<List<LatLng>>();
      Fill fill = await mapController!.addFill(FillOptions(
        geometry: geometry,
        fillColor: "#FF0000",
        fillOutlineColor: "#FF0000",
        fillOpacity: 0.6,
      ));
      setState(() {
        _selectedFill = fill;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    final NBMap nbMap = NBMap(
      onMapCreated: onMapCreated,
      initialCameraPosition: _kInitialPosition,
      trackCameraPosition: true,
      compassEnabled: _compassEnabled,
      cameraTargetBounds: _cameraTargetBounds,
      minMaxZoomPreference: _minMaxZoomPreference,
      styleString: _styleStrings[_styleStringIndex],
      rotateGesturesEnabled: _rotateGesturesEnabled,
      scrollGesturesEnabled: _scrollGesturesEnabled,
      tiltGesturesEnabled: _tiltGesturesEnabled,
      zoomGesturesEnabled: _zoomGesturesEnabled,
      doubleClickZoomEnabled: _doubleClickToZoomEnabled,
      myLocationEnabled: _myLocationEnabled,
      myLocationTrackingMode: _myLocationTrackingMode,
      myLocationRenderMode: MyLocationRenderMode.GPS,
      onMapClick: (point, latLng) async {
        print(
            "Map click: ${point.x},${point.y}   ${latLng.latitude}/${latLng.longitude}");
        print("Filter $_featureQueryFilter");
        List features = await mapController!
            .queryRenderedFeatures(point, ["landuse"], _featureQueryFilter);
        print('# features: ${features.length}');
        _clearFill();
        if (features.isEmpty && _featureQueryFilter != null) {
          ScaffoldMessenger.of(context).showSnackBar(SnackBar(
              content: Text('QueryRenderedFeatures: No features found!')));
        } else if (features.isNotEmpty) {
          _drawFill(features);
        }
      },
      onMapLongClick: (point, latLng) async {
        print(
            "Map long press: ${point.x},${point.y}   ${latLng.latitude}/${latLng.longitude}");
        Point convertedPoint = await mapController!.toScreenLocation(latLng);
        LatLng convertedLatLng = await mapController!.toLatLng(point);
        print(
            "Map long press converted: ${convertedPoint.x},${convertedPoint.y}   ${convertedLatLng.latitude}/${convertedLatLng.longitude}");
        double metersPerPixel =
            await mapController!.getMetersPerPixelAtLatitude(latLng.latitude);

        print(
            "Map long press The distance measured in meters at latitude ${latLng.latitude} is $metersPerPixel m");

        List features =
            await mapController!.queryRenderedFeatures(point, [], null);
        if (features.length > 0) {
          print(features[0]);
        }
      },
      onCameraTrackingDismissed: () {
        this.setState(() {
          _myLocationTrackingMode = MyLocationTrackingMode.None;
        });
      },
      onUserLocationUpdated: (location) {
        print(
            "new location: ${location.position}, alt.: ${location.altitude}, bearing: ${location.bearing}, speed: ${location.speed}, horiz. accuracy: ${location.horizontalAccuracy}, vert. accuracy: ${location.verticalAccuracy}");
      },
    );

    final List<Widget> listViewChildren = <Widget>[];

    if (mapController != null) {
      listViewChildren.addAll(
        <Widget>[
          Text('camera bearing: ${_position.bearing}'),
          Text('camera target: ${_position.target.latitude.toStringAsFixed(4)},'
              '${_position.target.longitude.toStringAsFixed(4)}'),
          Text('camera zoom: ${_position.zoom}'),
          Text('camera tilt: ${_position.tilt}'),
          Text(_isMoving ? '(Camera moving)' : '(Camera idle)'),
          _mapSizeToggler(),
          _queryFilterToggler(),
          _compassToggler(),
          _myLocationTrackingModeCycler(),
          _latLngBoundsToggler(),
          _setStyleToSatellite(),
          _zoomBoundsToggler(),
          _rotateToggler(),
          _scrollToggler(),
          _doubleClickToZoomToggler(),
          _tiltToggler(),
          _zoomToggler(),
          _myLocationToggler(),
          _telemetryToggler(),
          _visibleRegionGetter(),
        ],
      );
    }
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        Center(
          child: SizedBox(
            width: _mapExpanded ? null : 300.0,
            height: 200.0,
            child: nbMap,
          ),
        ),
        Expanded(
          child: ListView(
            children: listViewChildren,
          ),
        )
      ],
    );
  }

  void onMapCreated(NextbillionMapController controller) {
    mapController = controller;
    mapController!.addListener(_onMapChanged);
    _extractMapInfo();

    mapController!.getTelemetryEnabled().then((isEnabled) => setState(() {
          _telemetryEnabled = isEnabled;
        }));
  }
}
```

## Code summary

The above code snippet defines a MapUiBody widget that displays a map using the NBMap widget from the nb_maps_flutter package. The widget allows users to interact with the map and control various aspects of its user interface.

1.  **Display MapView Widget:**

    -   The NBMap widget is used to display the map on the screen.
    -   The onMapCreated callback is used to get access to the NextbillionMapController, which allows interaction with the map.
    -   The initialCameraPosition property sets the initial position of the camera when the map is first displayed.
2.  **User Interface on MapView:** The MapUiBody widget provides several buttons and toggles that allow users to interact with the map and customize its behavior:

    -   **Camera Information**: Displays the current camera bearing, target latitude and longitude, zoom level, and tilt. Also shows whether the camera is moving or idle.
    -   **Map Size Toggler**: Toggles between expanding and shrinking the map view.
    -   **Query Filter Toggler**: Toggles the filter for querying features on the map.
    -   **Compass Toggler**: Toggles the compass display on the map.
    -   **My Location Tracking Mode Cycler**: Cycles through different tracking modes for user location.
    -   **Camera Target Bounds Toggler**: Toggles between bounding and releasing the camera target within specific bounds.
    -   **Set Style To Satellite**: Cycles through different map styles (NBMAP_STREETS, SATELLITE, LOCAL_ASSET).
    -   **Zoom Bounds Toggler**: Toggles between bounding and releasing the zoom level within specific bounds.
    -   **Rotate Toggler**: Toggles the rotate gesture on the map.
    -   **Scroll Toggler**: Toggles the scroll gesture on the map.
    -   **Double Click To Zoom Toggler**: Toggles the double click to zoom gesture on the map.
    -   **Tilt Toggler**: Toggles the tilt gesture on the map.
    -   **Zoom Toggler**: Toggles the zoom gesture on the map.
    -   **My Location Toggler**: Toggles the display of the user's location on the map.
    -   **Telemetry Toggler**: Toggles the telemetry tracking on the map.
    -   **Visible Region Getter**: Retrieves the currently visible region on the map and displays it in a snackbar.

To use this widget in your Flutter app, make sure you have installed the **nb_maps_flutter** package and added the necessary dependencies to your pubspec.yaml file.

Overall, this code provides a comprehensive example of how to use the NBMap widget and interact with the map's user interface in a Flutter app using the NextbillionMap SDK.
