Yarrow Map Flutter SDK

Flutter SDK for integrating Yarrow maps in mobile apps, with an API aligned to the Yarrow web SDK.

This README is a quick reference. The full manual — every option table, resolution pipelines, error handling, common use cases, and troubleshooting — lives in the User Guide.

Table of Contents


Getting Started

Installation

flutter pub add yarrow_map_flutter_sdk

Requires Flutter >= 3.29. Get an API key at dashboard.yarrow.uz.

Initialization

Render the map with the YarrowMapView widget. The runtime API lives on the YarrowMap object delivered by onReady:

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

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

  @override
  State<MapScreen> createState() => _MapScreenState();
}

class _MapScreenState extends State<MapScreen> {
  final controller = YarrowMapController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: YarrowMapView(
        config: const YarrowMapConfig(
          center: (lng: 69.2401, lat: 41.2995),
          apiKey: 'YOUR_API_KEY',
          zoom: 12,
        ),
        controller: controller,
        onReady: (map) async {
          await map.zoomTo(41.2995, 69.2401, 14);
        },
      ),
    );
  }
}

YarrowMapController is optional; it holds the YarrowMap (controller.map) so other widgets can reach it.

Configuration Options

const mapConfig = YarrowMapConfig(
  center: (lng: 69.2401, lat: 41.2995), // required
  apiKey: 'YOUR_API_KEY',               // required
  zoom: 10,
  minZoom: 0,
  maxZoom: 19,
  theme: YarrowTheme.light,             // light | dark
  brandBadgePosition: BrandBadgePosition.bottomLeft,
  cache: YarrowCacheConfig(enabled: false, lifespanDays: 30),
  controls: YarrowControlsConfig(
    enabled: false,
    position: YarrowControlsPosition.right,
    zoom: true,     // zoom in/out buttons
    compass: true,  // reset-north compass
  ),
  excludePoiLayer: false,               // strip POIs from the base style
  clustering: YarrowClusteringConfig(), // clustering of highlighted POIs
);

Coordinate Order

  • YarrowMapConfig.center uses a named tuple: (lng: ..., lat: ...).
  • addMarker and all routing coordinates use [lng, lat] lists (GeoJSON order, matching the web SDK). This changed in 0.1.0 — previously [lat, lng].
  • zoomTo(lat, lng, zoom) takes separate named-position arguments (lat first), matching the web SDK.

Basic Map Manipulation

Changing the Map Style

await map.changeStyles(YarrowStyleType.satellite); // default_ | satellite | hybrid
await map.changeStyles();                          // back to default
map.getStyleType();                                // current YarrowStyleType

Changing the Map Theme

await map.changeTheme(YarrowTheme.dark);

Theme changes preserve the current style type (satellite/hybrid), and custom layers/highlights are re-applied automatically after any style or theme change.

Changing Brand Badge Position

map.changeBrandBadgePosition(BrandBadgePosition.topRight);

Moving the Map View

await map.zoomTo(41.2995, 69.2401, 14);
await map.zoomTo(41.2995, 69.2401, 14, const ZoomToOptions(durationMs: 2000));

await map.fitBounds(featureCollection); // any GeoJSON FeatureCollection
await map.fitBounds(
  featureCollection,
  const FitBoundsOptions(padding: 80, durationMs: 1000, maxZoom: 15),
);

await map.zoomIn();
await map.zoomOut();
await map.resetNorth();

Read Accessors

map.isReady();       // bool
map.getTheme();      // YarrowTheme
map.getStyleType();  // YarrowStyleType
map.getCenter();     // [lng, lat]
map.getZoom();       // double

Handling Events

All subscriptions return an unsubscribe function:

final unsubscribe = map.onMoveEnd((lat, lng, zoom) {
  print('moved to $lat, $lng @ $zoom');
});
unsubscribe(); // stop listening

map.onMapClick((lat, lng) { ... });

// POI / building icon taps. For pois, `selected` reports whether the tapped
// POI is currently highlighted (null for buildings).
map.onIconClick(YarrowLayerGroup.pois, (lat, lng, properties, selected) {
  print('POI ${properties['name']} selected=$selected');
});

// Base-layer taps: buildings, pois, and admin label layers.
map.onLayerClick(YarrowBaseLayerName.district, (lat, lng, properties) { ... });
// Targets: buildings | pois | mahalla | district | region | country

// Taps on layers you added yourself:
map.onCustomLayerClick('my-layer', (lat, lng, properties) { ... });

Streams are also available: map.onMoveEndStream, map.onMapClickStream.


Working with Layers and Data

Adding a GeoJSON Layer

await map.addLayer(
  layerName: 'my-points',
  layerType: 'circle', // circle | symbol | line | fill | heatmap | ...
  featureCollection: geojson,
  paint: {'circle-radius': 8, 'circle-color': '#ef4444'},
  layout: {},
);

await map.removeLayer('my-points');

Clustering options for the backing GeoJSON source: cluster, clusterRadius, clusterMaxZoom, clusterProperties. Use YarrowAddLayerOptions(sourceId: ..., filter: ...) to attach the layer to a shared source or filter it.

Custom layers are re-added automatically after changeStyles / changeTheme.

Managing Sources Explicitly

await map.addSource('vehicles', geojson);
await map.updateSourceData('vehicles', newGeojson); // efficient live updates
await map.addLayer(
  layerName: 'vehicles-layer',
  layerType: 'circle',
  featureCollection: geojson,
  options: const YarrowAddLayerOptions(sourceId: 'vehicles'),
);

Adding and Removing Markers

final marker = await map.addMarker(
  [69.2401, 41.2995], // [lng, lat]
  options: YarrowMapMarkerOptions(
    color: Colors.purple,      // tint of the default pin
    draggable: true,
    anchor: YarrowMarkerAnchor.bottom,
    onClick: () => print('marker tapped'),
    // iconImage: 'my-registered-image', // use a custom style image instead
    // iconSize: 1.2, rotation: 45,
  ),
);

await map.removeMarker(marker);

Querying Rendered Features

final features = await map.queryRenderedFeatures(
  point: const Point<double>(120, 80), // or rect: Rect.fromLTWH(...)
  options: const YarrowRenderedFeaturesQueryOptions(layers: ['my-points']),
);

setFeatureState(...) is also exposed, but maplibre_gl currently implements it on web only — see Platform Limitations.


POI Highlighting

Highlight POIs from the base map — the POI is hidden from the base layer and re-rendered as a prominent marker:

final highlight = map.highlightPOI(
  poiId,
  HighlightPOIOptions(
    location: (lat: 41.31, lng: 69.28), // skip lookup if you have coords
    title: 'Coffee House',
    icon: 'https://.../icon.png',       // optional explicit icon URL
    iconSize: 56,                       // logical px
    panTo: true,
    zoom: 15,
    replace: false,                     // true replaces existing highlights
    onClick: (info) => print('tapped highlight ${info.id}'),
  ),
);

highlight.remove(); // or:
map.resetPOIHighlight(poiId);       // one
map.resetPOIHighlight([id1, id2]);  // several
map.resetPOIHighlight();            // all

map.isPOIHighlighted(poiId);
map.getHighlightedPOIs();

// Multiple at once (Dart has no overloads — separate method):
map.highlightPOIs([id1, id2, id3], const HighlightPOIOptions(panTo: true));

When no location is given, coordinates and icon are resolved from loaded map tiles first, then from the Yarrow API. If no icon can be resolved, a blue dot marker is shown.

Clustering

Nearby highlight markers can collapse into numbered badges:

map.setClustering(YarrowClusteringConfig(
  enabled: true,
  radius: 60,    // screen px
  maxZoom: 16,   // above this zoom all markers show individually
  minPoints: 2,
  onClusterClick: (children) {
    print('cluster of ${children.length}: ${children.map((c) => c.id)}');
    return false; // suppress the default zoom-to-expand
  },
));

map.getClustering();

Can also be set at startup via YarrowMapConfig(clustering: ...).

POI Base Layer Toggle

// At startup:
YarrowMapConfig(excludePoiLayer: true, ...);

// At runtime:
map.disablePoiLayer();
map.enablePoiLayer();
map.setPoiLayerEnabled(true);

Routing

final result = await map.buildRoute(BuildRouteOptions(
  profile: 'car', // car | bicycle | pedestrian | foot ...
  coordinates: [
    [69.2797, 41.3111], // [lng, lat] — 2 or more waypoints
    [69.2332, 41.3267],
  ],
  routeColor: '#22c55e',
  routeWidth: 5,
  alternativeColor: '#aaaaaa',
  fitBounds: true,
  clearOldRoutes: true,
  language: 'ru',                 // Accept-Language for directions
  cancelToken: myCancelToken,     // dio CancelToken to abort the request
  onRouteClick: (ctx) => print('route ${ctx.index}, best=${ctx.isBest}'),
));

print(result.meta.distance);   // meters (best route)
print(result.meta.duration);   // minutes (best route)
print(result.features);        // GeoJSON LineStrings, best first
print(result.directions);      // turn-by-turn steps per route

await map.clearAllRoutes();

Pre-computed routes can be drawn with renderRoutes(routes, baseLayerName: 'route') using a distinct-series palette.

buildRouteWithLabels and buildMultiSegmentRouteWithLabels still work but are deprecated delegates of buildRoute (and now take [lng, lat]).


final search = map.highlightSearchResults(
  'cafe',
  options: YarrowHighlightOptions(
    layerName: 'search-results',
    iconImage: 'geo-icon',   // fallback style image; null skips icons
    zoomToResults: true,
    page: 1,
    pageSize: 15,
    onResultsUpdate: (results) => print('${results.length} results'),
    onLoadingStateChange: (state) {
      // YarrowSearchLoadingState.firstRender | .rerender | null (done)
    },
    onIconClick: (lat, lng, properties) => print(properties['name']),
  ),
);

search.cancel(); // remove result layers and stop refreshing

Results refresh automatically when the map center moves at least 500 m.


Public Transport

final bus = await map.showBusRoute();                // all buses near viewport
final one = await map.showBusRoute(routeId: '52');   // one route + its geometry
bus.cancel();

Positions are polled every 16 s and animated smoothly between updates.


Utility Methods

final bbox = map.getBoundingBox(featureCollection);
// YarrowBoundingBox(xMin, yMin, xMax, yMax) — lng/lat extents

await map.clearCache(); // clear the HTTP cache (when cache.enabled)
map.destroy();          // alias of dispose(); the view calls dispose for you

API Reference

YarrowMap (via onReady / controller.map)

Method Description
isReady() / getTheme() / getStyleType() / getCenter() / getZoom() Read accessors
changeStyles([YarrowStyleType?]) Switch base style
changeTheme(YarrowTheme) Switch light/dark, keeps style type
changeBrandBadgePosition(BrandBadgePosition) Move the badge
changeBackgroundColor(Color) Overlay a background fill
zoomTo(lat, lng, zoom, [ZoomToOptions]) Fly to a point
fitBounds(geojson, [FitBoundsOptions]) Fit camera to data
zoomIn() / zoomOut() / resetNorth() Camera helpers
onMoveEnd / onMapClick / onIconClick / onLayerClick / onCustomLayerClick Events; return unsubscribe fn
addLayer(...) / removeLayer(name) Custom GeoJSON layers
addSource(id, data) / updateSourceData(id, data) Explicit sources
queryRenderedFeatures(...) / setFeatureState(...) Feature queries/state
addMarker([lng, lat], options) / removeMarker(handle) Markers
highlightPOI(id, options) / highlightPOIs(ids, options) POI highlighting
resetPOIHighlight([idOrIds]) / isPOIHighlighted(id) / getHighlightedPOIs() Highlight management
setClustering(config) / getClustering() Highlight clustering
enablePoiLayer() / disablePoiLayer() / setPoiLayerEnabled(bool) POI base layer
buildRoute(BuildRouteOptions) / renderRoutes(...) / clearAllRoutes() Routing
highlightSearchResults(query, options) Search
showBusRoute({routeId}) Live buses
getBoundingBox(geojson) / clearCache() / destroy() Utilities

Key Types

  • YarrowThemelight, dark
  • YarrowStyleTypedefault_, satellite, hybrid (wireName gives the API string)
  • BrandBadgePositiontopLeft, topRight, bottomLeft, bottomRight
  • YarrowControlsPositionleft, leftTop, leftBottom, right, rightTop, rightBottom
  • YarrowLayerGrouppois, buildings
  • YarrowBaseLayerNamebuildings, pois, mahalla, district, region, country
  • YarrowMarkerAnchorcenter, top, bottom, left, right, corners
  • HighlightPOIOptions, POIHighlight, PoiHighlightClickInfo
  • YarrowClusteringConfig, ClusterChild
  • BuildRouteOptions, BuildRouteResult, BuildRouteMeta, RouteEventContext
  • ZoomToOptions, FitBoundsOptions
  • YarrowHighlightOptions, YarrowSearchLoadingState
  • YarrowCancelable — handle with a cancel() function

Platform Limitations

Differences from the web SDK that stem from the native platform:

  • Hover effects (hover config, onRouteHover) are not implemented — mouse hover has no equivalent on touch devices.
  • setFeatureState is exposed but only functional on web; Android/iOS throw UnimplementedError (upstream maplibre_gl limitation).
  • Highlight markers appear without the web SDK's enter/exit scale animation.
  • Highlight clusters recompute on camera idle and throttled camera movement, not per rendered frame.
  • ZoomToOptions.speed approximates the web SDK's flyTo speed by scaling the animation duration.
  • FitBoundsOptions.maxZoom is applied as a post-animation clamp.

Support

Issues and questions: git.yarrow.uz