diff --git a/CHANGELOG.md b/CHANGELOG.md
index afe44df8d..1bc5c5856 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,9 @@
-### main
+### 2.25.0-rc.1
+
+* Deprecate `MapboxMap.onTapListener` and `MapboxMap.onLongTapListener` in favor of the `MapboxMap.addInterfaction` API.
+* Add `MapboxMap.httpService.setMaxRequestsPerHost` to cap the number of concurrent HTTP requests per host issued by the underlying HTTP service. Useful for reducing the chance of hitting per-token rate limits during offline tile region downloads.
+
+### 2.24.0-rc.1
* Add `TileStore.setOptionForKey` to allow setting custom tile store options by arbitrary string key, in addition to the existing predefined options (`diskQuota`, `mapboxApiUrl`, `tileUrlTemplate`).
diff --git a/LICENSE b/LICENSE
index d693897ea..dcd9a9d96 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
## License
-Mapbox Maps for Flutter version 2.24.0
+Mapbox Maps for Flutter version 2.25.0
Mapbox Maps Flutter SDK
Copyright © 2022 - 2026 Mapbox, Inc. All rights reserved.
@@ -194,6 +194,149 @@ https://www.mapbox.com/legal/tos/
---
+### libjpeg-turbo,3.1.4.1,IJG AND BSD-3-Clause AND Zlib,The libjpeg-turbo Project,https://libjpeg-turbo.org/
+
+```
+libjpeg-turbo Licenses
+======================
+
+libjpeg-turbo is covered by two compatible BSD-style open source licenses:
+
+- The IJG (Independent JPEG Group) License, which is listed in
+ [README.ijg](README.ijg)
+
+ This license applies to the libjpeg API library and associated programs,
+ including any code inherited from libjpeg and any modifications to that
+ code. Note that the libjpeg-turbo SIMD source code bears the
+ [zlib License](https://opensource.org/licenses/Zlib), but in the context of
+ the overall libjpeg API library, the terms of the zlib License are subsumed
+ by the terms of the IJG License.
+
+- The Modified (3-clause) BSD License, which is listed below
+
+ This license applies to the TurboJPEG API library and associated programs, as
+ well as the build system. Note that the TurboJPEG API library wraps the
+ libjpeg API library, so in the context of the overall TurboJPEG API library,
+ both the terms of the IJG License and the terms of the Modified (3-clause)
+ BSD License apply.
+
+
+Complying with the libjpeg-turbo Licenses
+=========================================
+
+This section provides a roll-up of the libjpeg-turbo licensing terms, to the
+best of our understanding. This is not a license in and of itself. It is
+intended solely for clarification.
+
+1. If you are distributing a modified version of the libjpeg-turbo source,
+ then:
+
+ 1. You cannot alter or remove any existing copyright or license notices
+ from the source.
+
+ **Origin**
+ - Clause 1 of the IJG License
+ - Clause 1 of the Modified BSD License
+ - Clauses 1 and 3 of the zlib License
+
+ 2. You must add your own copyright notice to the header of each source
+ file you modified, so others can tell that you modified that file. (If
+ there is not an existing copyright header in that file, then you can
+ simply add a notice stating that you modified the file.)
+
+ **Origin**
+ - Clause 1 of the IJG License
+ - Clause 2 of the zlib License
+
+ 3. You must include the IJG README file, and you must not alter any of the
+ copyright or license text in that file.
+
+ **Origin**
+ - Clause 1 of the IJG License
+
+2. If you are distributing only libjpeg-turbo binaries without the source, or
+ if you are distributing an application that statically links with
+ libjpeg-turbo, then:
+
+ 1. Your product documentation must include a message stating:
+
+ This software is based in part on the work of the Independent JPEG
+ Group.
+
+ **Origin**
+ - Clause 2 of the IJG license
+
+ 2. If your binary distribution includes or uses the TurboJPEG API, then
+ your product documentation must include the text of the Modified BSD
+ License (see below.)
+
+ **Origin**
+ - Clause 2 of the Modified BSD License
+
+3. You cannot use the name of the IJG or The libjpeg-turbo Project or the
+ contributors thereof in advertising, publicity, etc.
+
+ **Origin**
+ - IJG License
+ - Clause 3 of the Modified BSD License
+
+4. The IJG and The libjpeg-turbo Project do not warrant libjpeg-turbo to be
+ free of defects, nor do we accept any liability for undesirable
+ consequences resulting from your use of the software.
+
+ **Origin**
+ - IJG License
+ - Modified BSD License
+ - zlib License
+
+
+The Modified (3-clause) BSD License
+===================================
+
+Copyright (C) 2009-2026 D. R. Commander. All Rights Reserved.
+Copyright (C) 2015 Viktor Szathmáry. All Rights Reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+- Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+- Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+- Neither the name of the libjpeg-turbo Project nor the names of its
+ contributors may be used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS",
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES LOSS OF USE, DATA, OR PROFITS OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+
+Why Two Licenses?
+=================
+
+The zlib License could have been used instead of the Modified (3-clause) BSD
+License, and since the IJG License effectively subsumes the distribution
+conditions of the zlib License, this would have effectively placed
+libjpeg-turbo binary distributions under the IJG License. However, the IJG
+License specifically refers to the Independent JPEG Group and does not extend
+attribution and endorsement protections to other entities. Thus, it was
+desirable to choose a license that granted us the same protections for new code
+that were granted to the IJG for code derived from their software.
+
+```
+
+---
+
### icu,75.1,Unicode-3.0,Unicode Inc,https://github.com/unicode-org/icu
```
diff --git a/android/build.gradle b/android/build.gradle
deleted file mode 100644
index 431329f6d..000000000
--- a/android/build.gradle
+++ /dev/null
@@ -1,73 +0,0 @@
-group 'com.mapbox.maps.mapbox_maps'
-version '1.0.0'
-
-buildscript {
- ext.kotlin_version = '1.8.22'
- repositories {
- google()
- mavenCentral()
- }
-
- dependencies {
- classpath 'com.android.tools.build:gradle:8.10.1'
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
- }
-}
-
-rootProject.allprojects {
- repositories {
- google()
- mavenCentral()
- maven {
- url 'https://api.mapbox.com/downloads/v2/releases/maven'
- }
- maven {
- url 'https://api.mapbox.com/downloads/v2/snapshots/maven'
- authentication {
- basic(BasicAuthentication)
- }
- credentials {
- username = "mapbox"
- password = System.getenv("SDK_REGISTRY_TOKEN") ?: project.findProperty("SDK_REGISTRY_TOKEN") as String
- }
- }
- }
-}
-
-apply plugin: 'com.android.library'
-apply plugin: 'kotlin-android'
-
-android {
- compileSdk 35
-
- namespace 'com.mapbox.maps.mapbox_maps'
- sourceSets {
- main.java.srcDirs += 'src/main/kotlin'
- }
- defaultConfig {
- minSdkVersion 21
- }
-
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
-
- kotlinOptions {
- jvmTarget = "1.8"
- }
-}
-
-if (file("$rootDir/gradle/ktlint.gradle").exists() && file("$rootDir/gradle/lint.gradle").exists()) {
- project.apply {
- from("$rootDir/gradle/ktlint.gradle")
- from("$rootDir/gradle/lint.gradle")
- }
-}
-
-dependencies {
- implementation "com.mapbox.maps:android-ndk27:11.25.0"
-
- implementation "androidx.annotation:annotation:1.5.0"
- implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.2"
-}
diff --git a/android/build.gradle.kts b/android/build.gradle.kts
new file mode 100644
index 000000000..4549625c8
--- /dev/null
+++ b/android/build.gradle.kts
@@ -0,0 +1,68 @@
+group = "com.mapbox.maps.mapbox_maps"
+version = "1.0.0"
+
+buildscript {
+ repositories {
+ google()
+ mavenCentral()
+ }
+ dependencies {
+ classpath("com.android.tools.build:gradle:9.0.1")
+ }
+}
+
+rootProject.allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ maven {
+ url = uri("https://api.mapbox.com/downloads/v2/releases/maven")
+ }
+ maven {
+ url = uri("https://api.mapbox.com/downloads/v2/snapshots/maven")
+ authentication {
+ register("basic", BasicAuthentication::class.java)
+ }
+ credentials {
+ username = "mapbox"
+ password = (System.getenv("SDK_REGISTRY_TOKEN") ?: (project.findProperty("SDK_REGISTRY_TOKEN") as? String)) ?: ""
+ }
+ }
+ }
+}
+
+plugins {
+ id("com.android.library")
+}
+
+android {
+ compileSdk = 35
+
+ namespace = "com.mapbox.maps.mapbox_maps"
+ sourceSets {
+ getByName("main") {
+ java.srcDirs("src/main/kotlin")
+ }
+ }
+ defaultConfig {
+ minSdk = 21
+ }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+}
+
+val ktlintFile = file("$rootDir/gradle/ktlint.gradle")
+val lintFile = file("$rootDir/gradle/lint.gradle")
+if (ktlintFile.exists() && lintFile.exists()) {
+ apply(from = ktlintFile)
+ apply(from = lintFile)
+}
+
+dependencies {
+ implementation("com.mapbox.maps:android-ndk27:11.25.0")
+ implementation("androidx.annotation:annotation:1.5.0")
+ implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
+}
diff --git a/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/MapboxMapController.kt b/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/MapboxMapController.kt
index d6add334a..e7bf20a38 100644
--- a/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/MapboxMapController.kt
+++ b/android/src/main/kotlin/com/mapbox/maps/mapbox_maps/MapboxMapController.kt
@@ -369,6 +369,19 @@ class MapboxMapController(
result.error("HEADER_ERROR", e.message, null)
}
}
+ "map#setMaxRequestsPerHost" -> {
+ try {
+ val max = call.argument("max")
+ if (max == null) {
+ result.error("INVALID_ARGUMENTS", "max cannot be null", null)
+ } else {
+ com.mapbox.common.HttpServiceFactory.setMaxRequestsPerHost(max.toByte())
+ result.success(null)
+ }
+ } catch (e: Exception) {
+ result.error("MAX_REQUESTS_PER_HOST_ERROR", e.message, null)
+ }
+ }
else -> {
result.notImplemented()
}
diff --git a/example/integration_test/all_test.dart b/example/integration_test/all_test.dart
index a0758de34..86bda0ffa 100644
--- a/example/integration_test/all_test.dart
+++ b/example/integration_test/all_test.dart
@@ -45,6 +45,7 @@ import 'logo_test.dart' as logo_test;
import 'attribution_test.dart' as attribution_test;
import 'compass_test.dart' as compass_test;
import 'scale_bar_test.dart' as scale_bar_test;
+import 'http_service_test.dart' as http_service_test;
import 'offline_test.dart' as offline_test;
import 'snapshotter/snapshotter_test.dart' as snapshotter_test;
import 'viewport_test.dart' as viewport_test;
@@ -74,6 +75,7 @@ void main() {
// offline_test
offline_test.main();
+ http_service_test.main();
// style tests
style_test.main();
diff --git a/example/integration_test/http_service_test.dart b/example/integration_test/http_service_test.dart
new file mode 100644
index 000000000..f4041b0ed
--- /dev/null
+++ b/example/integration_test/http_service_test.dart
@@ -0,0 +1,27 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:integration_test/integration_test.dart';
+import 'empty_map_widget.dart' as app;
+
+void main() {
+ IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+ group('MapboxHttpService.setMaxRequestsPerHost', () {
+ testWidgets('accepts valid values across the supported range',
+ (widgetTester) async {
+ final mapFuture = app.main();
+ await widgetTester.pumpAndSettle();
+ final mapboxMap = await mapFuture;
+
+ // Reasonable mid-range value.
+ await mapboxMap.httpService.setMaxRequestsPerHost(2);
+
+ // Boundary values (1..255 maps to uint8 on the native side).
+ await mapboxMap.httpService.setMaxRequestsPerHost(1);
+ await mapboxMap.httpService.setMaxRequestsPerHost(255);
+
+ // Repeated calls should not throw — setting is idempotent / overwriting.
+ await mapboxMap.httpService.setMaxRequestsPerHost(8);
+ await mapboxMap.httpService.setMaxRequestsPerHost(8);
+ });
+ });
+}
diff --git a/ios/mapbox_maps_flutter.podspec b/ios/mapbox_maps_flutter.podspec
index 53644f6f9..6b33d8fc1 100644
--- a/ios/mapbox_maps_flutter.podspec
+++ b/ios/mapbox_maps_flutter.podspec
@@ -4,7 +4,7 @@
#
Pod::Spec.new do |s|
s.name = 'mapbox_maps_flutter'
- s.version = '2.24.0'
+ s.version = '2.25.0'
s.summary = 'Mapbox Maps SDK Flutter Plugin.'
s.description = 'An officially developed solution from Mapbox that enables use of our latest Maps SDK product.'
diff --git a/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/MapboxMapController.swift b/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/MapboxMapController.swift
index b17312d00..63b911ae3 100644
--- a/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/MapboxMapController.swift
+++ b/ios/mapbox_maps_flutter/Sources/mapbox_maps_flutter/Classes/MapboxMapController.swift
@@ -1,4 +1,5 @@
import Flutter
+import MapboxCommon
@_spi(Experimental) import MapboxMaps
import UIKit
@@ -173,6 +174,20 @@ public final class MapboxMapController: NSObject, FlutterPlatformView {
customInterceptor.customHeaders = headers
result(nil)
+ case "map#setMaxRequestsPerHost":
+ guard let arguments = methodCall.arguments as? [String: Any],
+ let max = arguments["max"] as? Int
+ else {
+ result(FlutterError(
+ code: "setMaxRequestsPerHost",
+ message: "could not decode arguments",
+ details: nil
+ ))
+ return
+ }
+ HttpServiceFactory.setMaxRequestsPerHostForMax(UInt8(clamping: max))
+ result(nil)
+
default:
result(FlutterMethodNotImplemented)
}
diff --git a/lib/src/http/http_service.dart b/lib/src/http/http_service.dart
index cc2d081b6..aafb3386d 100644
--- a/lib/src/http/http_service.dart
+++ b/lib/src/http/http_service.dart
@@ -11,10 +11,15 @@ class MapboxHttpService {
/// [binaryMessenger] is used for platform channel communication
/// [channelSuffix] is used to create a unique channel identifier when multiple instances
/// of the service are needed. This should match the suffix used on the platform side.
- MapboxHttpService(
- {required this.binaryMessenger, required this.channelSuffix}) {
- _channel = MethodChannel('plugins.flutter.io.${channelSuffix.toString()}',
- const StandardMethodCodec(), binaryMessenger);
+ MapboxHttpService({
+ required this.binaryMessenger,
+ required this.channelSuffix,
+ }) {
+ _channel = MethodChannel(
+ 'plugins.flutter.io.${channelSuffix.toString()}',
+ const StandardMethodCodec(),
+ binaryMessenger,
+ );
}
/// Sets custom headers for all Mapbox HTTP requests
@@ -40,4 +45,38 @@ class MapboxHttpService {
);
}
}
+
+ /// Sets the maximum number of concurrent HTTP requests per host that the
+ /// underlying HTTP service may issue.
+ ///
+ /// Lowering this value reduces the chance of hitting per-token rate limits
+ /// when downloading large amounts of data (for example, offline tile
+ /// regions), at the cost of slower throughput.
+ ///
+ /// This is a global setting that applies to all Mapbox HTTP requests across
+ /// the process; calling it on any map instance updates it for all of them.
+ ///
+ /// [max] must be in the range 1..255.
+ ///
+ /// Throws a [PlatformException] if the native implementation is not available
+ /// or if the operation fails.
+ Future setMaxRequestsPerHost(int max) async {
+ try {
+ await _channel
+ .invokeMethod('map#setMaxRequestsPerHost', {'max': max});
+ } on MissingPluginException catch (e) {
+ throw PlatformException(
+ code: 'MISSING_IMPLEMENTATION',
+ message:
+ 'Native implementation for setMaxRequestsPerHost is not available',
+ details: e.toString(),
+ );
+ } catch (e) {
+ throw PlatformException(
+ code: 'SET_MAX_REQUESTS_PER_HOST_FAILED',
+ message: 'Failed to set max requests per host',
+ details: e.toString(),
+ );
+ }
+ }
}
diff --git a/lib/src/mapbox_map.dart b/lib/src/mapbox_map.dart
index 355eb242b..02edbc4ef 100644
--- a/lib/src/mapbox_map.dart
+++ b/lib/src/mapbox_map.dart
@@ -270,7 +270,9 @@ class MapboxMap extends ChangeNotifier {
late final MapboxHttpService httpService = MapboxHttpService(
binaryMessenger: _mapboxMapsPlatform.binaryMessenger,
channelSuffix: _mapboxMapsPlatform.channelSuffix);
+ @Deprecated('Use [MapboxMap.addInteraction] instead')
OnMapTapListener? onMapTapListener;
+ @Deprecated('Use [MapboxMap.addInteraction] instead')
OnMapLongTapListener? onMapLongTapListener;
OnMapScrollListener? onMapScrollListener;
OnMapZoomListener? onMapZoomListener;
@@ -840,11 +842,13 @@ class MapboxMap extends ChangeNotifier {
messageChannelSuffix: _mapboxMapsPlatform.channelSuffix.toString());
}
+ @Deprecated('Use [MapboxMap.addInteraction] instead')
void setOnMapTapListener(OnMapTapListener? onMapTapListener) {
this.onMapTapListener = onMapTapListener;
_setupGestures();
}
+ @Deprecated('Use [MapboxMap.addInteraction] instead')
void setOnMapLongTapListener(OnMapLongTapListener? onMapLongTapListener) {
this.onMapLongTapListener = onMapLongTapListener;
_setupGestures();
diff --git a/lib/src/package_info.dart b/lib/src/package_info.dart
index 8f5cf187d..781420b09 100644
--- a/lib/src/package_info.dart
+++ b/lib/src/package_info.dart
@@ -1,4 +1,4 @@
part of mapbox_maps_flutter;
-const String mapboxPluginVersion = '2.24.0';
+const String mapboxPluginVersion = '2.25.0';
diff --git a/pubspec.yaml b/pubspec.yaml
index d4e91f725..1d235e42b 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
name: mapbox_maps_flutter
description: Interactive, thoroughly customizable maps powered by Mapbox Maps mobile SDKs.
-version: 2.24.0
+version: 2.25.0
homepage: https://github.com/mapbox/mapbox-maps-flutter
environment: