Release 0.27.0
The plugin has a documentation site now, at maplibre.org/flutter-maplibre-gl: a guide for every part of the API, each with a live map you can pan and click.
This release regenerates the whole style property surface from the current MapLibre style spec, closes long-standing gaps between the three platforms, and cuts start-up time and the cost of large data updates. Upgrading costs little: the only compile-time change is SourceProperties.copyWith moving to named parameters, plus one small change for Android apps and one or two for web apps, all below.
Actions needed
- Android: a map now survives its activity being destroyed and recreated, but your style content does not come back with it. Add sources, layers and images inside
onStyleLoadedCallback, which fires again after every recreation, rather than inonMapCreatedorinitState. See the migration guide (#805). - Web: the plugin now loads MapLibre GL JS itself. Delete the
maplibre-gl.jsscript tag and themaplibre-gl.csslink tag fromweb/index.html: if they stay, your pinned copy silently overrides the version the plugin is tested against. See the migration guide (#928). - Web: MapLibre GL JS 6 requires WebGL2 and no longer falls back to WebGL1, so a browser without it (Safari and iOS before 15) shows no map. Point
MapLibreMap.webLibrarySourceat a version 5 build to keep those browsers working. Version 6 also slices vector tiles instead of overscaling them, soqueryRenderedFeaturescan return a different set of features. See the migration guide (#943). SourceProperties.copyWithtakes named parameters instead of a dozen required positional ones, soprops.copyWith(cluster: true)works and future spec additions stop breaking every caller. Calls that passed values positionally need the parameter names added (#957).Expressions.xornever meant xor:^is the style spec's exponentiation operator, so the expression raised the first input to the power of the second. It is nowExpressions.power, andExpressions.moduloreplaces the misspelledExpressions.precent. The old names still work as deprecated aliases, so nothing stops compiling, but any style built withxorwas doing something else (#957).- Android, iOS: the two offline download errors that carried a vendor name are renamed,
mapboxTileCountLimitExceededtotileCountLimitExceededandmapboxInvalidRegionDefinitiontoinvalidRegionDefinition. When they fire does not change, but acatchmatching either oldPlatformExceptioncode needs the new one (#956).
Added
- Android, iOS, Web:
addColorReliefLayer()colours the terrain by elevation from a raster DEM source, the hypsometric tint of a physical map. The colour ramp is acolorReliefColorexpression overExpressions.elevation, andcolorReliefOpacitysets how strongly it covers what is below. See the color relief guide (#958). - Android, iOS, Web:
addBackgroundLayer()paints the whole map withbackgroundColoror tiles it withbackgroundPattern, the one layer type with no source. PassbelowLayerIdto place it under the layers it backs. Most published styles already carry a layer calledbackground, so reusing that id fails withlayerAlreadyExists. Changing its properties after adding it can stop it drawing while the style's own background layer is still there (maplibre-native#4502). See the background layer guide (#959). - Web:
setProjection()switches between themercator,globeandvertical-perspectiveprojections, or interpolates between them by zoom.setTerrain()raises the map by the elevation of a raster DEM source, withnullto flatten it again, andsetSky()draws the sky and the atmosphere above the horizon. All three throw anUnsupportedErroron Android and iOS, where MapLibre Native implements none of them yet. See the globe, terrain and sky guide (#960). - Android, iOS, Web: the style property surface is regenerated from the current MapLibre style spec instead of a four-year-old copy. New layer properties:
textVariableAnchorOffseton symbols,hillshadeMethodandhillshadeIlluminationAltitudefor multidirectional hillshading. New expressions:Expressions.distance,within,indexOf,sliceandelevation(#957). MapLibreMap.preWarm()starts the map engine during app start-up, for apps whose first screen is a map. Saves roughly 170 to 480 ms on Android, 45 to 165 ms on iOS and 10 to 50 ms on web (#867).- Android: feature state (
setFeatureState,getFeatureState,removeFeatureState) works on Android as well as web, so single features can be restyled without re-feeding the source. iOS throws; its SDK does not expose the API yet.promoteIdstays web-only, so Android features need a top-levelidin the GeoJSON. See the feature state guide (#889). - Android, iOS: offline regions can move between devices.
exportOfflineDatabase()writes a shareable copy,mergeOfflineRegions()imports one (now on iOS too), andgetOfflineDatabasePath()locates the store. See the offline regions guide (#886). - Android, iOS, Web: the location puck can be driven from your own position updates instead of the device GPS, with no location permission needed:
locationSource: ManualLocationSource()pluscontroller.updateManualLocation(). See the user location guide (#840). - Android, iOS, Web:
getClusterExpansionZoom()gives the zoom at which a cluster splits, so a cluster tap can zoom to exactly that instead of guessing withzoom + 2.getClusterLeaves()andgetClusterChildren()read the points behind a cluster. All three take the cluster feature'scluster_id. See the cluster guide (#896). getLayerProperties()andgetSourceProperties()read a layer's or source's properties by id, in the same shape on every platform, ornullif the id is unknown. On iOS only what came with the style is readable, so a layer or source you added at runtime answersnullthere while Android and web answer normally (#513, #985).- Android, iOS, Web:
setLight()sets the style's light source, which shades extruded geometries:anchor,position,colorandintensity. Android and iOS take constant values, web also accepts expressions; Android answersINVALID_ARGUMENTfor a value it cannot take. See the globe, terrain and sky guide (#960). setPadding()keeps map content centred while a bottom sheet or side panel covers part of the map, instead of passing padding to every camera call (#258).pauseMap()andresumeMap()stop and restart rendering for a map that is alive but off screen, such as one on an inactive tab. No-op on web (#805).- Android, iOS:
setTrackingCameraOptions()pitches the camera without giving up the active tracking mode, for a navigation-style view that stays tilted while it keeps following the user. On web it throws anUnsupportedError. See the user location guide (#888). - Web:
setGlobalStateProperty()sets a value in the style's global state, which theExpressions.globalStateexpression reads, so one switch restyles any number of layers at once. Android and iOS throw anUnsupportedError. See the expressions guide (#960). - Web:
MapLibreMap.webLibrarySourcechooses where MapLibre GL JS comes from: the build the plugin is tested against, a self-hosted copy, or one the page loads itself.MapLibreMap.ensureWebLibraryLoaded()completes once MapLibre GL JS is loaded, for calling into it directly, for exampleaddProtocol(#928). - Android, iOS, Web:
fillExtrusionRoundedCornerDistancerounds the corners of extruded shapes. Its generated dartdoc still readsnot on js, because the pinned style spec records MapLibre GL JS support for it as an open issue; it shipped in GL JS 6.2.0 and works on web (#957). - Android, iOS, Web:
clusterMinPointson a GeoJSON source sets how many points have to fall together before they become a cluster, instead of the fixed default of two (#957, #981). - Android, iOS:
attributionButtonColortints the attribution button, for styles where the default tint is hard to see. No effect on web (#805). - iOS:
LocationEnginePlatforms.iOStakesintervalMsandpulseWindowMsto pulse GPS instead of tracking continuously, easing battery use on maps that stay open a long time. The default keeps continuous tracking (#901). - Android:
volatileon vector, raster and raster-dem sources keeps their tiles out of the on-disk cache, for tiles that change often or must not be stored. The iOS SDK exposes no equivalent and MapLibre GL JS will not implement one, so it has no effect there (#957, #981). - Web: properties MapLibre GL JS supports but MapLibre Native does not implement yet are exposed too:
iconOverlapandtextOverlapon symbols,fillLayerOpacityandlineLayerOpacity,resamplingon raster and hillshade layers,filteron GeoJSON sources, thecustomraster-dem encoding with its factors, and theExpressions.globalState,joinandsplitexpressions. Android and iOS ignore them, except the custom encoding, which now throws, andjoinandsplit, which work on Android since 13.5.0 (#957, #981).
Fixed
- Android: a map no longer stays blank for good after its activity is recreated, whether by "Don't keep activities", memory pressure or rotation; the camera position comes back with it (#805).
- Android: icons added with
addImageare visible again, at the right size on high-density screens. Since 0.26.0 they could be dropped whenever draggable annotations were in use, which is the default. Undecodable bytes now report a clear error instead of crashing (#866, #868). - Android, iOS: symbols added with
addSymbolare visible again on styles whose glyph server does not host the old default font; a missing font used to hide the whole symbol, icon included. The default is nowNoto Sans Regular; for another font useaddSymbolLayerwithtextFont(#940). - Android, iOS: losing the network no longer cancels an offline region download, nor deletes the region with it on iOS. The SDK retries those failures itself once the connection is back; only the tile count limit stops a download now (#986).
- Android, iOS: adding or updating a GeoJSON source with a large payload no longer blocks the UI for the whole encode. Large payloads are encoded in the background, cutting the blocking time by a factor of two to three (#366).
- Android: a hillshade layer takes its colours again.
hillshadeShadowColorand its three companions became arrays in MapLibre Native 6.24 for multidirectional hillshading, so a single value was rejected withExpected array<color> but found string insteadand the layer silently kept the default black and white. A single value is now wrapped for you on Android and iOS, and a list is passed through (#957). - Android, iOS, Web:
queryRenderedFeaturesInRect()applies thefilterit is given; no platform decoded it, so the answer held every feature in the rectangle. Note that this call takes the filter as a JSON string, unlikequeryRenderedFeatures()(#949, #953). - A map that fails to be created now reports the failure. Code awaiting the controller,
onMapCreatedincluded, used to wait forever for a map that never arrived, with only an unhandled error in the console (#943). - Android, iOS: downloading an area that is already downloaded replaces its region instead of adding a duplicate. iOS keeps the region id the app already knows; Android assigns a new one, so read it back from the returned
OfflineRegion(#886). - Android, iOS:
setLayerProperties()works on fill-extrusion and heatmap layers too; both used to answerUNSUPPORTED_LAYER_TYPE(#960). - Android:
maxzoomon a GeoJSON source is applied again. The converter read a camel-cased key the Dart side never sends, so the value was dropped on Android while iOS and web honoured it (#981). - Android:
queryRenderedFeatures()andquerySourceFeatures()say what is wrong with a call they cannot answer:STYLE_NOT_READYbefore the style has loaded,INVALID_ARGUMENTfor a missingsourceId,layerIdsor query geometry. The failure used to arrive as a bareerrorcarrying aNullPointerExceptionmessage (#954). - Android: merging an offline database whose regions carry no metadata, such as one produced by maplibre-native, no longer fails with
type 'Null' is not a subtype of type 'Map<String, dynamic>'(#865). - iOS:
queryRenderedFeatures()andquerySourceFeatures()no longer skip a feature that failed to serialize without telling you, and no longer hang on a call they cannot answer, such as one made before the style has loaded (#949). - iOS:
queryCameraPosition()returns the camera position even whentrackCameraPositionisfalse, matching Android. It used to returnnull(#892). - iOS:
controller.cameraPositionno longer sticks on aNaNzoom from a camera event that arrived before the first layout, which misplaced camera-anchored content on maps the user had not touched (#903). - Web:
onMapIdlenow fires, matching Android and iOS; code waiting on it never ran (#857). - Web:
getFeatureState()returns the state instead of throwing, and reports no state as null rather than an empty map.removeFeatureState(sourceId)with no feature id now resets the whole source instead of doing nothing. AstateKeywith nofeatureIdis rejected. Both match Android (#889). - Web:
querySourceFeatures()now reports an error when it is called before the style has loaded, matching Android and iOS. It answered with an empty list, which the caller cannot tell apart from a source holding no features (#952). - Web:
queryCameraPosition()is implemented; it used to throwUnimplementedError(#892). - Web:
updateContentInsetsand the newsetPaddingno longer throwUnimplementedError(#258). - The bundled LICENSE no longer breaks Flutter's license collector, which showed an untitled, truncated first entry on every dependent app's
showLicensePage()(#895).
Changed
- Web: MapLibre GL JS upgraded from 5 to 6.4.1; see Actions needed above. Over 6.2 this fixes a permanent frame-rate drop after a style switch, a rejected missing-image resolver taking the rest of its batch down with it, globe zoom drifting away from the pointer, globe panning stalling at the poles, and an attribute-sanitising hole in popup and marker HTML (#943).
- Android: MapLibre Native upgraded from 13.3.0 to 13.5.0 (13.4.0, 13.4.1, 13.5.0), plus OkHttp 5.4.0, Play Services Location 21.4.0 and androidx.core-ktx 1.18.0. 13.5.0 repaints symbol paint properties driven by feature state, fixes a surface-changed ANR and works around a device-lost crash on Adreno GPUs (#877, #919, #929).
- iOS: MapLibre Native upgraded from 6.27.0 to 6.28.0 (release notes): the map view is no longer blurry in landscape on iPad (#929).
- iOS: the plugin supports Flutter's Swift Package Manager integration. The CocoaPods podspec still ships, so CocoaPods apps need no migration (#891).
- Android, iOS: a raster-dem source with
encoding: "custom"now throws anUnsupportedErrorfromaddSource(). MapLibre Native decodes only themapboxandterrariumformulas and used to read custom tiles as mapbox-encoded, drawing a plausible map from wrong elevations. OnlyaddSource()is checked: the same encoding declared insideMapLibreMap.styleStringis still decoded as mapbox without an error. Custom encoding keeps working on web (#981). - Android, iOS: panning a pitched map no longer moves the camera past the horizon, and changing a layer's
source-layerorsource-idnow takes effect (#929). - The style spec the code is generated from is now a verbatim copy of a pinned upstream release (
@maplibre/maplibre-gl-style-spec, currently 26.2.1). A weekly workflow reports what a newer release would bring to each platform and opens the update PR, and properties the native SDKs do not implement yet are generated for web only, so upgrading the SDKs unlocks them automatically (#957). - Every generated layer property, source option and expression now documents which platforms implement it, from the spec's own
sdk-supportmetadata:iconOverlapreadsbasic functionality with js (not on android, ios)instead of claiming all three. Where the spec credits a platform whose SDK exposes no such API, the docs say so rather than repeat the spec (#957, #981). - The error from
addSymbol,addCircle,addLineandaddFillwhen the annotation manager is missing now names the type and both causes: the style has not finished loading, or that type is not in the widget'sannotationOrder(#910). - Android: the Kotlin Gradle Plugin is applied only below AGP 9, so builds on AGP 9 or later no longer break. Nothing changes on AGP 8 (#905).
- Android: the unused
android-plugin-annotation-v9andandroid-plugin-offline-v9dependencies are dropped, so apps pull two fewer artifacts (#929). - Android: the plugin builds against NDK 28.2.13676358, matching Flutter's default (#956).
Docs
- New guides for user location, startup and performance and feature state (#840, #867, #889).
- Annotations: the two conditions every annotation depends on, and the constraints of both APIs, are now stated in one place (#910).
- Android:
MapLibreMap.useHybridCompositionwas documented with the wrong default; it has beenfalsesince 0.16.0. Its docs and the performance page now say what each value selects (#816). - Example app: new Manual Location Source and Feature State pages, and the Offline Regions demo now walks through the export and import round-trip (#840, #886, #889).
- Example app: the Data-Driven Expressions demo drew the USA as a triangle across the Atlantic, because one coordinate was missing its minus sign. Its polygon is now a bounding box over the contiguous states (#956).
Contributors
- @m-schmale made their first contribution in #824
- @RyanHolanda made their first contribution in #867
- @Nico04 made their first contribution in #887
- @AlexRepaire made their first contribution in #901
- @JulianBissekkou
- @gabbopalma
Full Changelog: v0.26.2...v0.27.0