manara_map 1.0.0 copy "manara_map: ^1.0.0" to clipboard
manara_map: ^1.0.0 copied to clipboard

A Flutter package for OpenStreetMap-based maps with address search, location services, turn-by-turn directions with live rider tracking, and customizable UI.

A drop-in Flutter widget for the Manara Maps service: an OpenStreetMap-based map with address search, device location, turn-by-turn directions, and live rider tracking β€” all wired up behind a single API key.

Features #

  • πŸ“ Current location β€” resolves the device's location on load, with automatic reverse geocoding of the map center as the user pans (debounced).
  • πŸ” Address search β€” type-ahead search-as-you-type with debounce, loading state, and a results list, or replace the field entirely with your own via searchFieldBuilder.
  • 🧭 Directions β€” a source/destination picker, route drawing, and turn-by-turn rider tracking with automatic rerouting when the rider drifts off the route.
  • πŸ›΅ Live tracking widget β€” ManaraRouteMapWidget, a watch-only map for following a delivery in progress between a fixed pickup and drop-off.
  • 🎨 Customizable UI β€” swap markers, buttons, search/directions fields, colors, and route line styling without forking the widget.
  • πŸ—ΊοΈ Built on flutter_map (OpenStreetMap tiles), so it never depends on Google Maps or an API key from Google.

Getting started #

Important β€” SDK requirements

This package requires Flutter >=3.22.0 and Dart ^3.8.0 (see environment: in pubspec.yaml); flutter pub get refuses to resolve it on an older Flutter install.

manara_map itself ships no native Android/iOS code, so it doesn't declare a compileSdk or minSdk of its own. However, its location dependency, geolocator (via geolocator_android), pulls in androidx.core:core:1.13.0, which needs Android compileSdk 34 or higher to build. Flutter's default project template already sets compileSdk/minSdk from flutter.compileSdkVersion / flutter.minSdkVersion in your app's android/app/build.gradle, and any Flutter SDK β‰₯3.22 defaults those to 34+, so most consumers won't need to change anything β€” but if your app's build.gradle pins compileSdk to a literal, older number (rather than flutter.compileSdkVersion), raise it to at least 34 or the Android build will fail.

2. Get an API key #

ManaraMapWidget and ManaraRouteMapWidget talk to the Manara Maps HTTP API for address search, reverse geocoding, and routing. You'll need an API key from Manara Maps to use these features β€” map tiles themselves are unauthenticated.

Your API key is sent as an sm-api-key request header β€” never in a URL β€” and is never logged. Keep it out of source control: supply it at build time with --dart-define, as the example app does, or fetch it from your own backend at runtime.

Alongside your API key, every API request automatically carries your app's package id (the Android applicationId / iOS bundle identifier) in an sm-app-id header, so Manara Maps can attribute traffic to your app. This needs no configuration on your side, and it is best-effort: if the id can't be read on a given platform, requests still go out without the header. Map tile requests are unaffected.

baseUrl must use https. Requests carry your API key in a header and the device's coordinates in the query string, so a plain-http base URL would put both on the wire unencrypted. Debug builds print a warning if it does not. A trailing slash is fine β€” it is stripped for you.

tileUrl is required too, and is separate from baseUrl: it is the {z}/{x}/{y} template your raster tiles are fetched from, for example https://your-tile-host.example.com/{z}/{x}/{y}.png. Tile requests carry no API key.

Both values may arrive as null or empty β€” a remote-config lookup that has not resolved yet, say β€” in which case the widget renders its plain error overlay instead of building a map.

3. Configure platform permissions #

Both widgets use geolocator to resolve the device's location, so the usual location-permission setup for that plugin applies:

Android β€” in android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

iOS β€” in ios/Runner/Info.plist:

<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs location access</string>

If you need the rider's location to keep updating while tracking runs in the background, add NSLocationAlwaysAndWhenInUseUsageDescription as well and request the corresponding permission from geolocator yourself.

Usage #

Basic map with search and location #

ManaraMapWidget needs an apiKey and a ManaraMapController, which requires a TickerProvider (typically your State's TickerProviderStateMixin):

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

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

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

class _MapPageState extends State<MapPage> with TickerProviderStateMixin {
  late final ManaraMapController _mapController;

  @override
  void initState() {
    super.initState();
    _mapController = ManaraMapController(this);
  }

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

  @override
  Widget build(BuildContext context) {
    return ManaraMapWidget(
      apiKey: 'your-api-key',
      baseUrl: 'https://your-manara-maps-api.example.com/api',
      tileUrl: 'https://your-tile-host.example.com/{z}/{x}/{y}.png',
      mapController: _mapController,
      onLocationChanged: (lat, lng) => debugPrint('Location: $lat, $lng'),
      onAddressChanged: (lat, lng, address) => debugPrint('Address: $address'),
    );
  }
}

By default this renders a map centered on the device's current location, a search bar, a "locate me" button, and the resolved address underneath the map. Pan the map to reverse-geocode a new center; tap a search result to jump to it.

Turn-by-turn directions #

showDirectionsButton defaults to true, which adds a directions toggle next to the search bar. Tapping it swaps in a source/destination picker; once both points are chosen, the widget fetches and draws the route, and the current-location button turns into a "Start Routing" button:

ManaraMapWidget(
  apiKey: 'your-api-key',
  baseUrl: 'https://your-manara-maps-api.example.com/api',
  tileUrl: 'https://your-tile-host.example.com/{z}/{x}/{y}.png',
  mapController: _mapController,
  onDirectionsRouteFound: (route) => debugPrint('Route with ${route.points.length} points'),
  onDirectionsRouteError: (message) => debugPrint('Directions failed: $message'),
  onStartRouting: () {
    // The user tapped "Start Routing". Begin feeding live GPS updates into
    // trackingLocation below (your own location stream, a background
    // service, or test data).
  },
  trackingLocation: _riderLocation,
  onTrackingCompleted: () => debugPrint('Rider arrived'),
  onRerouting: () => debugPrint('Off-route β€” rerouting…'),
  onRerouted: (route) => debugPrint('Rerouted with ${route.points.length} points'),
)

While tracking is active, feed the rider's current position into trackingLocation by rebuilding the widget with a new LocationEntity (e.g. from a Geolocator.getPositionStream() listener). The widget automatically triggers a reroute if the rider drifts more than offRouteThresholdMeters (default 50.0) from the current route, and fires onTrackingCompleted once the rider reaches the destination.

Watch-only tracking: ManaraRouteMapWidget #

Use ManaraRouteMapWidget when you already know the fixed pickup and drop-off β€” for example, a customer watching their delivery in progress. It has no buttons or manual interaction: it finds the route and starts tracking as soon as it's built, and re-fits the camera whenever the points change.

ManaraRouteMapWidget(
  apiKey: 'your-api-key',
  baseUrl: 'https://your-manara-maps-api.example.com/api',
  tileUrl: 'https://your-tile-host.example.com/{z}/{x}/{y}.png',
  mapController: _mapController,
  startLocation: pickupLocation,
  endLocation: dropoffLocation,
  onRouteFound: (route) => debugPrint('Route found'),
  onRouteError: (message) => debugPrint('Route error: $message'),
  onStartRoute: () => debugPrint('Tracking started'),
  onRouteEnd: () => debugPrint('Rider arrived'),
  onRerouting: () => debugPrint('Off-route β€” rerouting…'),
  onRerouted: (route) => debugPrint('Rerouted'),
)

Rebuild with a new startLocation to feed live rider updates in β€” the marker glides smoothly to each new position rather than snapping. Rebuilding with a new endLocation (or a changed startLocation before tracking has begun) resets and re-finds the route.

Custom search field #

Replace the default search field with any widget via searchFieldBuilder. The package still owns debounce, loading state, and rendering of search results β€” only the field itself is swapped out:

ManaraMapWidget(
  apiKey: 'your-api-key',
  baseUrl: 'https://your-manara-maps-api.example.com/api',
  tileUrl: 'https://your-tile-host.example.com/{z}/{x}/{y}.png',
  mapController: _mapController,
  searchFieldBuilder: (context, search) {
    return TextField(
      controller: search.controller,
      focusNode: search.focusNode,
      onChanged: search.onChanged,
      onTap: search.onTap,
      decoration: InputDecoration(
        hintText: 'Search for a location',
        suffixIcon: search.query.isEmpty
            ? IconButton(icon: const Icon(Icons.search), onPressed: search.onSearch)
            : IconButton(icon: const Icon(Icons.clear), onPressed: search.onClear),
      ),
    );
  },
)

search.controller and search.focusNode are owned and disposed by the package β€” reuse them, don't create or dispose your own. Use search.isLoading and search.isFocused to render custom state.

directionsFieldBuilder follows the same contract for the directions picker's source/destination field pair (DirectionsFieldContext exposes a controller/focus node/query/loading flag for each of source and destination, plus onClose to exit the picker).

Custom markers and buttons #

  • customMarkerWidget replaces the center pin (or every marker in markers, when supplied).
  • directionsMarkerBuilder / ManaraRouteMapWidget.markerBuilder build a custom start/end route marker, called once per point with a RouteMarkerType.
  • directionsRiderMarkerBuilder / ManaraRouteMapWidget.riderMarkerBuilder build a custom marker for the rider's live position while tracking, given the current heading in degrees (0Β° = North, clockwise) to rotate against.
  • directionsButtonBuilder builds a custom directions toggle button, given onPressed and whether the picker isActive.

Displaying a fixed set of markers #

Pass markers to show a static list of pins instead of the draggable center marker β€” useful for e.g. showing nearby saved addresses:

ManaraMapWidget(
  apiKey: 'your-api-key',
  baseUrl: 'https://your-manara-maps-api.example.com/api',
  tileUrl: 'https://your-tile-host.example.com/{z}/{x}/{y}.png',
  mapController: _mapController,
  markers: [
    LocationEntity(latitude: 24.8607, longitude: 67.0011),
    LocationEntity(latitude: 24.9056, longitude: 67.0822),
  ],
)

Programmatic map control #

ManaraMapController wraps flutter_map_animations for panning/zooming outside of user gestures:

_mapController.moveTo(24.8607, 67.0011, 16.0);      // jump immediately
_mapController.animateTo(24.8607, 67.0011, 16.0);   // animate over ~1s

Handling errors #

Failures surface as typed exceptions exported from manara_map:

  • ApiException β€” the Manara Maps API call failed; statusCode is populated when available (a 401/403 typically means an invalid or expired API key). ManaraMapWidget shows an ApiErrorOverlay automatically when address lookup fails this way.
  • NetworkException β€” no network connectivity.
  • LocationException β€” device location services are disabled.
  • PermissionException β€” location permission was denied.

Search failures are reported inline in the search results list rather than through the address-lookup error overlay β€” a broken search shouldn't blank out the whole map. Directions failures are reported via onDirectionsRouteError / onRouteError.

Widget reference #

ManaraMapWidget #

Parameter Type Description
apiKey String Required. Manara Maps API key, sent as the sm-api-key header.
baseUrl String? Required. Base URL of your Manara Maps environment. Must use https.
tileUrl String? Required. {z}/{x}/{y} template the map's raster tiles are fetched from.
mapController ManaraMapController Required.
initialLocation LocationEntity? Initial center, e.g. when editing an existing address.
defaultLocation LocationEntity? Fallback center when device location is unavailable.
showSearchBar, showCurrentLocationButton, showAddressText, showDirectionsButton bool Toggle default UI pieces. All default to true.
showCustomLocationButton bool Use the alternate "Locate me" button style. Default false.
customMarkerWidget Widget? Replaces the center pin (or every pin in markers).
markers List<LocationEntity>? Static pins instead of the draggable center marker.
initialZoom double? Default 16.0.
searchFieldBuilder MapSearchFieldBuilder? Custom search field; see Custom search field.
directionsFieldBuilder DirectionsFieldBuilder? Custom source/destination fields.
directionsButtonBuilder DirectionsButtonBuilder? Custom directions toggle button.
directionsMarkerBuilder RouteMarkerBuilder? Custom start/end route marker.
directionsRiderMarkerBuilder RiderMarkerBuilder? Custom live-rider marker while tracking.
trackingLocation LocationEntity? Feeds the rider's live position in while tracking is active.
offRouteThresholdMeters double? Drift distance (meters) that triggers a reroute. Default 50.0.
onLocationChanged OnLocationChanged? (lat, lng) β€” fires on any map-center change.
onAddressChanged OnAddressChanged? (lat, lng, address) β€” fires once the address resolves.
onSearchActiveChanged OnSearchActiveChanged? Search results visibility changed.
onDirectionsRouteFound OnDirectionsRouteFound? A route was found for the picked source/destination.
onDirectionsRouteError OnDirectionsRouteError? The directions request failed.
onDirectionsModeChanged ValueChanged<bool>? The directions picker opened/closed.
onStartRouting VoidCallback? The "Start Routing" button was tapped.
onTrackingCompleted VoidCallback? The rider reached the destination.
onRerouting / onRerouted VoidCallback? / OnDirectionsRouteFound? A reroute started / succeeded.

Plus a large set of search*/directions* styling parameters (colors, hint text, fill/border colors, line color/width) β€” see the dartdoc on ManaraMapWidget for the full list.

ManaraRouteMapWidget #

Parameter Type Description
apiKey String Required. Manara Maps API key, sent as the sm-api-key header.
baseUrl String? Required. Base URL of your Manara Maps environment. Must use https.
tileUrl String? Required. {z}/{x}/{y} template the map's raster tiles are fetched from.
mapController ManaraMapController Required.
startLocation / endLocation LocationEntity Required. Fixed pickup/drop-off; rebuild with a new startLocation to feed live rider updates.
markerBuilder RouteMarkerBuilder? Custom start/end marker.
riderMarkerBuilder RiderMarkerBuilder? Custom live-rider marker.
routeLineColor / routeLineWidth Color? / double? Defaults to the package accent color / 6.0.
offRouteThresholdMeters double? Default 50.0.
onRouteFound / onRouteError OnRouteFound? / OnRouteError?
onStartRoute / onRouteEnd VoidCallback? Tracking started / rider arrived.
onRerouting / onRerouted VoidCallback? / OnRouteFound?

Entities and exceptions #

  • LocationEntity({ latitude, longitude })
  • SearchResultEntity({ displayName, latitude, longitude })
  • RouteEntity({ points: List<LocationEntity> })
  • ApiException, NetworkException, LocationException, PermissionException β€” see Handling errors.

Additional information #

The example/ app in this repository demonstrates both widgets, including a real GPS-driven tracking flow. File issues or questions with the Manara Maps team.

0
likes
140
points
267
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Flutter package for OpenStreetMap-based maps with address search, location services, turn-by-turn directions with live rider tracking, and customizable UI.

Repository (GitHub)

License

BSD-3-Clause (license)

Dependencies

dio, flutter, flutter_bloc, flutter_map, flutter_map_animations, flutter_map_cancellable_tile_provider, freezed_annotation, geolocator, json_annotation, latlong2, package_info_plus, shimmer

More

Packages that depend on manara_map