neshan_maps_flutter 1.1.0 copy "neshan_maps_flutter: ^1.1.0" to clipboard
neshan_maps_flutter: ^1.1.0 copied to clipboard

A Flutter SDK for Neshan Maps. Provides an interactive map widget and a ready-to-use location picker with reverse-geocoding and search.

Neshan Maps Flutter #

A Flutter SDK for integrating Neshan Maps into your Flutter applications.

Features #

  • Cross-platform — works on Android, iOS, Web, and all other Flutter-supported platforms
  • Interactive Map — pan, zoom, multiple map styles, traffic layer, and POI toggles
  • Markers & Controller — place markers declaratively or manage them at runtime via NeshanMapController
  • Shapes — draw polygons, polylines, and circles with full styling control and tap events
  • Location Picker — draggable centre-pin with automatic reverse-geocoding and built-in place search
  • Customisable UI — override the address bar, confirm button, and centre marker with your own widgets

Screenshots #

NeshanMap NeshanLocationPicker Location Search Shapes
Map Picker Search Shapes

Table of Contents #

Getting API Keys #

Register at platform.neshan.org and create these keys:

Key Used for Docs
mapKey Displaying the map. Required for both NeshanMap and NeshanLocationPicker.
reverseGeocodingApiKey Converting coordinates to an address string. Required only for NeshanLocationPicker. Reverse Geocoding API
searchApiKey Searching for places by name. Optional for NeshanLocationPicker. Search API

Important notes #

The mapKey must be a Web key. This package uses the Neshan Web SDK on all platforms (WebView on mobile, <iframe> on web), so generate a Web key. Android/iOS keys will not work.

Do not restrict the allowed domain / IP. Requests come from a device WebView, not a fixed backend domain/IP. Restricting domain or IP can break map loading.

Keep keys out of your source code. Never commit keys. Prefer fetching them from your backend at runtime instead of bundling them in the app.

Installation #

Run this command:

flutter pub add neshan_maps_flutter

This will add the latest version to your pubspec.yaml and install it.

Quick Start #

NeshanMap — minimal usage #

import 'package:neshan_maps_flutter/map.dart';

NeshanMap(
  mapKey: 'YOUR_MAP_KEY',
)

That's it. The map opens at the default location and zoom level.

NeshanLocationPicker — minimal usage #

import 'package:neshan_maps_flutter/location_picker.dart';

NeshanLocationPicker(
  mapKey: 'YOUR_MAP_KEY',
  reverseGeocodingApiKey: 'YOUR_REVERSE_GEOCODING_KEY',
  onLocationAccepted: (position, address) {
    print('Selected: $address at ${position.latitude}, ${position.longitude}');
  },
)

As the user pans, the address bar at the top updates automatically. Tapping the confirm button triggers onLocationAccepted.

Location Permission Setup #

This step is optional. It is only required if you want to show the user's current location on the map via showCurrentLocationButton: true in NeshanMapConfig (the default).
If you set showCurrentLocationButton: false, you can skip this section entirely.

This package uses the geolocator plugin internally to access device location. Follow the platform-specific permission instructions in the geolocator documentation to add the required entries to your AndroidManifest.xml, Info.plist, and any other platform files.

NeshanMap — Full Reference #

NeshanMap is a cross-platform widget that renders an interactive Neshan map. On mobile it uses a WebView; on web it uses an <iframe>.

Overlays on web #

On web, the map sits in an HtmlElementView behind your Flutter widgets. Overlays such as buttons, sheets, or the location FAB may not receive taps—the underlying map can consume pointer events first. Wrap interactive overlays with PointerInterceptor from pointer_interceptor — add that package to your app’s pubspec.yaml so you can import it. See the package README for usage, including the intercepting flag when interception is only needed sometimes.

Complete example #

import 'package:flutter/material.dart';
import 'package:latlong2/latlong.dart';
import 'package:neshan_maps_flutter/map.dart';

class MapPage extends StatefulWidget {
  const MapPage({super.key});

  @override
  State<MapPage> createState() => _MapPageState();
}

class _MapPageState extends State<MapPage> {
  final _controller = NeshanMapController();

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: NeshanMap(
        mapKey: 'YOUR_MAP_KEY',
        controller: _controller,
        config: NeshanMapConfig(
          initialCenter: LatLng(35.6892, 51.3890),
          initialZoom: 14.0,
          mapType: NeshanMapType.neshanVector,
          showTraffic: true,
        ),
        markers: const [
          NeshanMarker(
            id: 'hq',
            position: LatLng(35.6892, 51.3890),
            title: 'HQ',
            color: Colors.red,
          ),
        ],
        polygons: const [
          NeshanPolygon(
            id: 'zone1',
            coordinates: [
              LatLng(35.685, 51.385),
              LatLng(35.695, 51.385),
              LatLng(35.695, 51.395),
              LatLng(35.685, 51.395),
              LatLng(35.685, 51.385),
            ],
            fillColor: Colors.blue,
            fillOpacity: 0.3,
            strokeColor: Colors.blue,
          ),
        ],
        onLocationChanged: (lat, lng) {
          debugPrint('Centre moved to $lat, $lng');
        },
        onMarkerTapped: (markerId) {
          debugPrint('Marker tapped: $markerId');
        },
        onPolygonTapped: (polygonId) {
          debugPrint('Polygon tapped: $polygonId');
        },
        onError: (message, details) {
          debugPrint('Map error: $message');
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          await _controller.ready;
          _controller.moveToLocation(35.7448, 51.3753, zoom: 15);
        },
        child: const Icon(Icons.my_location),
      ),
    );
  }
}

NeshanMapConfig #

Controls the initial viewport and style of the map. All parameters are optional.

NeshanMapConfig(
  initialCenter: LatLng(35.6892, 51.3890), // Default initial center
  initialZoom: 14.0,                        // Default: 12.0
  mapType: NeshanMapType.neshanVectorNight, // Default: neshanVector
  minZoom: 5.0,                             // Default: 2.0
  maxZoom: 18.0,                            // Default: 21.0
  showPoi: false,                           // Default: true
  showTraffic: true,                        // Default: false
  showCurrentLocationButton: false,         // Default: true
)

NeshanMapType

Value Description
NeshanMapType.neshanVector Default vector map
NeshanMapType.neshanVectorNight Vector map in night mode
NeshanMapType.neshanRaster Raster (tile) map
NeshanMapType.neshanRasterNight Raster map in night mode

NeshanMarker #

Represents a pin on the map.

NeshanMarker(
  id: 'office',              // Required — unique identifier
  position: LatLng(35.6892, 51.3890), // Required
  color: Colors.deepPurple, // Optional — defaults to Neshan blue
  title: 'Our Office',      // Optional — popup text when tapped
  draggable: false,          // Optional — default false
)

NeshanPolygon #

Represents a filled polygon on the map.

NeshanPolygon(
  id: 'zone1',              // Required — unique identifier
  coordinates: [            // Required — list of coordinates
    LatLng(35.700, 51.400),
    LatLng(35.710, 51.410),
    LatLng(35.720, 51.400),
    LatLng(35.700, 51.400), // Close the polygon
  ],
  fillColor: Colors.blue,   // Optional — defaults to blue
  fillOpacity: 0.3,         // Optional — 0.0 to 1.0, default 0.5
  strokeColor: Colors.blue, // Optional — defaults to black
  strokeWidth: 2.0,         // Optional — default 2.0
  strokeOpacity: 1.0,       // Optional — 0.0 to 1.0, default 1.0
)

NeshanPolyline #

Represents a line or path on the map.

NeshanPolyline(
  id: 'route1',             // Required — unique identifier
  coordinates: [            // Required — list of coordinates
    LatLng(35.700, 51.400),
    LatLng(35.710, 51.410),
    LatLng(35.720, 51.420),
  ],
  color: Colors.red,        // Optional — defaults to blue
  width: 5.0,               // Optional — default 3.0
  opacity: 0.9,             // Optional — 0.0 to 1.0, default 1.0
  isDashed: false,          // Optional — default false (solid line)
)

NeshanCircle #

Represents a circle overlay on the map.

NeshanCircle(
  id: 'coverage1',          // Required — unique identifier
  center: LatLng(35.6892, 51.3890), // Required — center point
  radius: 500,              // Optional — radius in meters, default 100
  fillColor: Colors.green,  // Optional — defaults to blue
  fillOpacity: 0.2,         // Optional — 0.0 to 1.0, default 0.3
  strokeColor: Colors.green, // Optional — defaults to black
  strokeWidth: 2.0,         // Optional — default 2.0
  strokeOpacity: 1.0,       // Optional — 0.0 to 1.0, default 1.0
)

NeshanMapController #

Use NeshanMapController to control the map programmatically after it has loaded.

Important: Always await controller.ready before calling any method to ensure the map is fully initialised.

final controller = NeshanMapController();

// Pass to NeshanMap, then:
await controller.ready;

// Move camera
controller.moveToLocation(35.6892, 51.3890, zoom: 15.0);

// Change zoom only
controller.setZoom(12.0);

// Fit to bounding box (north, south, east, west)
controller.fitBounds(36.0, 35.0, 52.0, 51.0);

// Query state
final center = await controller.getCurrentLocation();
final zoom   = await controller.getCurrentZoom();

// Manage markers
controller.addMarker(NeshanMarker(id: 'new', position: LatLng(35.7, 51.4)));
controller.removeMarker('new');
controller.updateMarkers([/* replacement list */]);
controller.clearMarkers();

// Manage polygons
controller.addPolygon(NeshanPolygon(id: 'zone1', coordinates: [/* ... */]));
controller.removePolygon('zone1');
controller.updatePolygons([/* replacement list */]);
controller.clearPolygons();

// Manage polylines
controller.addPolyline(NeshanPolyline(id: 'route1', coordinates: [/* ... */]));
controller.removePolyline('route1');
controller.updatePolylines([/* replacement list */]);
controller.clearPolylines();

// Manage circles
controller.addCircle(NeshanCircle(id: 'poi1', center: LatLng(35.7, 51.4), radius: 500));
controller.removeCircle('poi1');
controller.updateCircles([/* replacement list */]);
controller.clearCircles();

// Cleanup
controller.dispose();

NeshanLocationPicker — Full Reference #

The picker is ready to use out of the box — no extra configuration is needed beyond the API keys.
For deeper understanding of the underlying APIs, refer to the official Neshan documentation:

NeshanLocationPicker wraps NeshanMap and adds:

  • A centre-pin overlay indicating the selected location.
  • An address bar at the top that updates via reverse-geocoding as the user pans.
  • A search button (when searchApiKey is provided) that opens a full-screen search UI.
  • A confirm button that calls onLocationAccepted with the final LatLng and address string.

Complete example #

import 'package:flutter/material.dart';
import 'package:latlong2/latlong.dart';
import 'package:neshan_maps_flutter/location_picker.dart';

class PickerPage extends StatelessWidget {
  const PickerPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: NeshanLocationPicker(
        mapKey: 'YOUR_MAP_KEY',
        reverseGeocodingApiKey: 'YOUR_REVERSE_GEOCODING_KEY',
        searchApiKey: 'YOUR_SEARCH_KEY', // Optional — enables search
        mapConfig: NeshanMapConfig(
          initialCenter: LatLng(35.6892, 51.3890),
          initialZoom: 14.0,
        ),
        locationPickerConfig: NeshanLocationPickerConfig(
          geocodingDebounce: Duration(milliseconds: 400),
          searchDebounce: Duration(milliseconds: 400),
        ),
        onLocationAccepted: (position, address) {
          Navigator.pop(context);
          print('Picked: $address (${position.latitude}, ${position.longitude})');
        },
        onAddressChanged: (address, response) {
          debugPrint('Address: $address | City: ${response.city}');
        },
        onApiError: (error) {
          debugPrint('API error [${error.statusCode}]: ${error.message}');
        },
      ),
    );
  }
}

NeshanLocationPickerConfig #

Controls how aggressively the widget calls the Neshan APIs.

NeshanLocationPickerConfig(
  geocodingDebounce: Duration(milliseconds: 500), // Default: 300 ms
  searchDebounce: Duration(milliseconds: 400),    // Default: 300 ms
)

NeshanLocationPickerUiConfig — UI customization #

Override any of the three overlay widgets with your own builders. Omitting a builder falls back to the default implementation.

NeshanLocationPicker(
  mapKey: 'YOUR_MAP_KEY',
  reverseGeocodingApiKey: 'YOUR_REVERSE_GEOCODING_KEY',
  onLocationAccepted: (position, address) { /* ... */ },
  locationPickerUiConfig: NeshanLocationPickerUiConfig(
    // Custom address bar
    addressDisplayBuilder: (context, data) {
      return Container(
        margin: const EdgeInsets.all(12),
        padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(12),
          boxShadow: [BoxShadow(blurRadius: 8, color: Colors.black12)],
        ),
        child: data.isLoading
            ? const LinearProgressIndicator()
            : Text(data.formattedAddress ?? 'Move the map to select a location'),
      );
    },

    // Custom confirm button
    acceptButtonBuilder: (context, data) {
      return Padding(
        padding: const EdgeInsets.all(16),
        child: ElevatedButton.icon(
          onPressed: data.onPressed,
          icon: const Icon(Icons.check),
          label: Text(data.isEnabled ? 'Confirm' : 'Loading…'),
        ),
      );
    },

    // Custom centre pin
    centerMarkerBuilder: (context) {
      return const Icon(Icons.location_pin, size: 48, color: Colors.red);
    },
  ),
)

The AddressDisplayData object passed to addressDisplayBuilder contains formattedAddress, fullResponse, isLoading, hasError, isSearchEnabled, and openSearchScreen fields.

The AcceptButtonData object passed to acceptButtonBuilder contains onPressed, isEnabled, currentLocation, and currentAddress fields.

Contributing #

Contributions are welcome! If you have suggestions for improvements, feature requests, or bug fixes, feel free to open an issue or submit a pull request on the GitHub repository.

License #

This project is licensed under the BSD 3-Clause License. See the LICENSE file for details.

2
likes
160
points
309
downloads
screenshot

Documentation

API reference

Publisher

verified publisherhosseinzarei.com

Weekly Downloads

A Flutter SDK for Neshan Maps. Provides an interactive map widget and a ready-to-use location picker with reverse-geocoding and search.

Repository (GitHub)
View/report issues

Topics

#map #geocoding #neshan

License

BSD-3-Clause (license)

Dependencies

flutter, geolocator, http, latlong2, pointer_interceptor, web, webview_flutter

More

Packages that depend on neshan_maps_flutter