Beta
Maps SDK for Android v10

Migrate to v10

The Mapbox Maps SDK v10 introduces improvements to how Mapbox works on the Android platform, as well as changes to how developers use the SDK. This document summarizes the most important changes and walks you through how to upgrade an application using a previous version of the Mapbox Maps SDK to v10.

Requirements

  • Minimum Android SDK version is now 21 (formerly 16).
  • Kotlin version 1.4.x.

Upgrading to use OkHttp / Okio 4.+

The Mapbox Maps SDK v10 is using OkHttp v4.9.0 by default under the hood. It is important to use compatible versions of all related Square libraries like Retrofit, Okio etc otherwise runtime crashes may happen. Moreover make sure to validate dependencies by running ./gradlew :app:dependencies because some other project libraries may silently bring in dependencies that you are not aware of, e.g. using com.squareup.okhttp3:logging-interceptor:3.12.7 as part of some other 3d-party library can break requests done with com.squareup.retrofit2:retrofit:2.9.0 and com.squareup.okhttp3:okhttp:4.9.0 and result in unexpected errors.

Package name changes

Packagepre-v10v10
Maven group ID for mapcom.mapbox.mapboxsdkcom.mapbox.maps
Maven artifact ID for mapmapbox-android-sdkandroid
Maven group ID for pluginscom.mapbox.mapboxsdkcom.mapbox.plugin
Maven group ID for pluginscom.mapbox.mapboxsdkcom.mapbox.extension
Maven artifact ID for pluginsmapbox-android-plugin-PLUGINNAMEmaps-PLUGINNAME
Package name for mapscom.mapbox.mapboxsdk.mapscom.mapbox.maps
MapView class in the layoutcom.mapbox.mapboxsdk.maps.MapViewcom.mapbox.maps.MapView

Simplified lifecycle management

With the Maps SDK v10, managing activity lifecycle methods has been reduced by a factor of four. It is no longer required to hook into Activity#onCreate, Activity#onPause, Activity#onResume and Activity#onSaveInstanceState. Below is an example implementation of lifecycle management:

class SimpleMapActivity : AppCompatActivity() {

 override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   setContentView(R.layout.activity_simple_map)
   mapView.getMapboxMap().loadStyleUri(Style.MAPBOX_STREETS)
 }

 override fun onStart() {
   super.onStart()
   mapView.onStart()
 }

 override fun onStop() {
   super.onStop()
   mapView.onStop()
 }

 override fun onLowMemory() {
   super.onLowMemory()
   mapView.onLowMemory()
 }

 override fun onDestroy() {
   super.onDestroy()
   mapView.onDestroy()
 }
}

Modular architecture

The Maps SDK v10 has a modular architecture to set the foundation for a plugin-based architecture that can be minimized or extended. Because of this architectural change, the SDK employs extension functions widely used to augment the map’s control class, MapboxMap. These extension functions must be imported. They will typically appear in the Android Studio autocomplete suggestions, but with lower priorities than the native functions inside the class.

Import the Maps SDK, including all packaged plugins as:

implementation "com.mapbox.maps:android:10.0.0-beta.19"

Example: Replace modular HTTP stack

The SDK allows for swapping certain components for a customized implementation. For example, the HTTP stack can be swapped out by:

  1. Removing the default HTTP stack bundled with the SDK and include Mapbox Annotation processor dependency:

    plugins {
        ...
        kotlin("kapt")
    }
    
    dependencies {
        ...
        implementation('com.mapbox.maps:android:10.0.0-beta.19') {
            exclude group: 'com.mapbox.common', module: 'okhttp'
        }
        implementation('androidx.annotation:annotation:${version.androidX}')
        compileOnly('com.mapbox.base:annotations:${version.mapboxBaseAndroid}')
        kapt('com.mapbox.base:annotations-processor:${versions.mapboxBaseAndroid}')
    }
    
  2. Provide a custom HTTP Client class and let it implement com.mapbox.common.HttpServiceInterface and annotate the class with @MapboxModule(type = MapboxModuleType.CommonHttpClient):

    @MapboxModule(type = MapboxModuleType.CommonHttpClient)
    class CustomizedHttpService : HttpServiceInterface() {
        ...
    
        override fun setMaxRequestsPerHost(max: Byte) {
            TODO("Not yet implemented")
        }
    
        override fun request(request: HttpRequest, callback: HttpResponseCallback): Long {
            TODO("Not yet implemented")
        }
    
        override fun cancelRequest(id: Long, callback: ResultCallback) {
            TODO("Not yet implemented")
        }
    
        override fun supportsKeepCompression(): Boolean {
            TODO("Not yet implemented")
        }
    
        override fun download(options: DownloadOptions, callback: DownloadStatusCallback): Long {
            TODO("Not yet implemented")
        }
    
    }
    
  3. Implement the abstract functions.

Access token configuration

The pre-v10 required configuration of the access token through the Mapbox.getInstance API. With v10, the singleton Mapbox object has been removed and CredentialsManager is introduced to manage Mapbox access token. The priority of access tokens is:

  1. Token that is set in a MapView XML attribute (mapbox_resourcesAccessToken)
  2. Token that is programmatically set to CredentialsManager.default
  3. Token that with predefined string identifier resource named mapbox_access_token

A unified configuration can be implemented by updating token in CredentialsManager.default:

// Set a token to CredentialsManager.shared, so that all MapViews created with default config will apply this token.
CredentialsManager.default.setAccessToken(YOUR_TOKEN)

A custom configuration for creating a MapView programmatically can be implemented like this:

// set map options
val mapOptions = MapOptions.Builder()
  .constrainMode(ConstrainMode.HEIGHT_ONLY)
  .glyphsRasterizationOptions(
    GlyphsRasterizationOptions.Builder()
      .rasterizationMode(GlyphsRasterizationMode.IDEOGRAPHS_RASTERIZED_LOCALLY)
      // Font family is required when the GlyphsRasterizationMode is set to IDEOGRAPHS_RASTERIZED_LOCALLY or ALL_GLYPHS_RASTERIZED_LOCALLY
      .fontFamily("sans-serif")
      .build()
  )
  .build()

// set token and cache size for this particular map view
val resourceOptions = ResourceOptions.Builder()
  .accessToken(YOUR_TOKEN)
  .cacheSize(75_000L)
  .build()

// set initial camera position
val initialCameraOptions = CameraOptions.Builder()
  .center(Point.fromLngLat(-122.4194, 37.7749))
  .zoom(9.0)
  .build()

val mapboxMapOptions =
  MapInitOptions(this, resourceOptions, mapOptions, initialCameraOptions, true)
// create view programmatically 
customMapView = MapView(this, mapboxMapOptions)

Map styles

Synchronized getMapboxMap()

In the Maps SDK v10, you can safely access MapboxMap from the MapView in a synchronized manner. You are no longer required to wait for the map to become ready before calling functions on the MapboxMap:

pre-v10:

mapView.getMapAsync { mapboxMap ->
  mapboxMap.setStyle(Style.MAPBOX_STREETS) { style ->
    // Map is set up and the style has loaded. Now you can add data or make other map adjustments.
  }
}

v10:

mapView.getMapboxMap().loadStyleUri(Style.MAPBOX_STREETS) { style ->
  // Map is set up and the style has loaded. Now you can add data or make other map adjustments.
}

Style loading functions

The setStyle API has been split into multiple functions with more descriptive names:

FunctionDescription
loadStyleUri(styleUri: String)Load a new map style asynchronous from the specified URI.
loadStyleJson(styleJson: String)Load style from a JSON string.
loadStyle(style: StyleExtension)Load the style from the style extension provided DSL.

Style DSL

The Style domain-specific language (DSL) is a collection of functions that allows composition of a style in a block that is applied to a receiver class. In the Maps SDK v10, the style API more closely matches the Mapbox Style Specification.

In v10, DSL functions for layers, sources, light, and expressions are provided to construct the instances. The DSL function names match the class name being created, but the first character in the name is lowercase. In cases where there are mandatory constructor parameters of the class, assign the mandatory constructor parameters before the code block. Inside the DSL block, use code completion in Android Studio to find all available receiver functions.

With the Style DSL, authoring or updating map styles is more like writing style JSON directly. The higher level style API is exposed as DSL, allowing construction of a StyleExtension object using the same paradigm and using the overloaded operator + inside the Style DSL closure to add layers, sources, or light to the StyleExtension.

Below is an example using the Style DSL to add a GeoJsonSource and a circle layer:

mapView.getMapboxMap().loadStyle(
  style(styleUri = Style.TRAFFIC_DAY) {
    +geoJsonSource(id = "earthquakes") {
      url(GEOJSON_URL)
      cluster(false)
    }
    +circleLayer(layerId = "earthquakeCircle", sourceId = "earthquakes") {
      circleRadius(get { literal("mag") })
      circleColor(Color.RED)
      circleOpacity(0.3)
      circleStrokeColor(Color.WHITE)
    }
  },
  object : Style.OnStyleLoaded {
    override fun onStyleLoaded(style: Style) {
      // Map is set up and the style has loaded. Now you can add data or make other map adjustments.
    }
  },
  object : OnMapLoadErrorListener {
    override fun onMapLoadError(mapViewLoadError: MapLoadError, msg: String) {
      // Error occurred when loading the map, try to handle it gracefully here
    }
  }
)

Styles parity with pre-v10 Maps SDK

The APIs for layer, source, and light are flattened, meaning you can find all the operations and properties directly under the Layer, Source and Light classes themselves. There are no more nested options like Layer.setProperties() and GeoJsonOptions. This makes the APIs safe because you cannot assign a property that isn’t supported by the layer with the new style API.

Below is a comparison of v10 and pre-v10 style APIs in specific use cases.

PropertyFactory deprecation

Instead of PropertyFactory, compatible properties are exposed directly inside the layer as functions with strongly typed value parameters.

pre-v10:

SymbolLayer stretchLayer = new SymbolLayer(STRETCH_LAYER, STRETCH_SOURCE)
    .withProperties(
        textField(get("name")),
        iconImage(get("image-name")),
        iconAllowOverlap(true),
        textAllowOverlap(true),
        iconTextFit(ICON_TEXT_FIT_BOTH));

v10:

val stretchLayer = symbolLayer(STRETCH_LAYER, STRETCH_SOURCE) {
    textField(get { literal("name") })
    iconImage(get { literal("image-name") })
    iconAllowOverlap(true)
    textAllowOverlap(true)
    iconTextFit(IconTextFit.BOTH)
}

StringDef deprecation

The StringDef static property values have been replaced by enum class.

Use Double instead of Float

The Float type properties in the style APIs are replaced with the more precise type, Double.

Use expressions

To better utilize the Style DSL and to be able to use the nested expression DSL, all constant values inside the expressions are treated as literal expressions and have to be wrapped inside the literal expression.

Expressions can be constructed using DSL as well:

pre-v10:

heatmapWeight(
        interpolate(
          linear(), get("mag"),
          stop(0, 0),
          stop(6, 1)
        )
      )

v10:

heatmapWeight(
        interpolate {
          linear()
          get { literal("mag") }
          stop {
            literal(0)
            literal(0)
          }
          stop {
            literal(6)
            literal(1)
          }
        }
      )

Update a single style property

After a layer, source, and light is added to the style, any change to the layer’s property will be passed through style and be shown on the Map.

pre-v10:

circleLayer.setProperties(
    circleColor(Color.RED)
);

v10:

circleLayer.circleColor(Color.RED)

Retrieve style properties from the map

After the layer, source, and light is added to the style, get the current style properties using the getter function directly exposed to the Layer, Source, and Light classes.

In most cases, there will be two getter functions of the property: get{PropertyName} : {PropertyType}? and get{PropertyName}AsExpression : Expression?. The first will return the property in its own type, and the second will return a property as an Expression (constant values will be wrapped into a literal expression).

Float types are replaced with Double in the v10 style APIs.

pre-v10:

val radiusPropertyValue: PropertyValue<Float> = circleLayer.getCircleRadius()
val radius: Float = radius.getValue()

v10:

val radius: Double = circleLayer.circleRadius!!

Changes to sources

Sources share the same API structure: all source properties mutators are available on the Source class. After a source is added to the style, changes to the source properties are passed to the style and reflected on the map directly. Setting the source properties can be done through methods such as url(), geometry(), feature(), or featureCollection().

val geoJsonSource = geoJsonSource(id = "earthquakes") {
      featureCollection(FEATURE_COLLECTION)
      cluster(false)
      clusterRadius(50L)
}

Annotations

With the Maps SDK v10, the legacy annotations have been replaced with the plugin annotation of v9.x.x. Most of the plugin APIs in v10 are the same as those in pre-v10, the main difference is the way of getting AnnotationManagers. Instead of creating an AnnotationManager directly like you did in pre-v10, in v10 an Annotation plugin instance must be created first.

pre-v10:

val circleManager = new CircleManager(mapView, mapboxMap, style);

v10:

val annotationPlugin = mapView.annotations
val circleManager = annotationPlugin.getCircleManager()

In pre-v10, AnnotationManager needed to be recycled in the onDestroy method of the host activity. In v10, there is no need to do it manually because AnnotationManager will be cleaned automatically along with the host, AnnotationPlugin.

In v10, all the LatLng related methods and parameters are replaced with Point. Remember that the latitude and longitude parameters are reversed between LatLng and Point.

pre-v10:

CircleOptions circleOptions = new CircleOptions()
 .withLatLng(new LatLng(6.687337, 0.381457))

v10:

val circleOptions: CircleOptions = CircleOptions()
 .withPoint(Point.fromLngLat(0.381457, 6.687337))

Map events

In v10, there are two ways to monitor the map's event: the traditional listener-based event API and the observable event API.

Traditional listener-based event API

In v10, two listener-based event APIs are provided. Use MapboxMap#addEVENTListener() and MapboxMap#removeEVENTListener() to add or remove the event listener respectively. Find a list of supported listeners the documentation.

Observable event API

In additional to the traditional listener-based event APIs, the SDK provides two observable event APIs. Use the MapboxMap#subscribe API and the MapboxMap#unsubscribe API to subscribe to and unsubscribe from one or multiple events from the observable respectively.

The available events and their documentation can be found in the MapEvents class as follows:

public final class MapEvents {
    /**
     * The Map's style has been fully loaded, and the Map has rendered all visible tiles.
     *
     * Event data format (Object).
     */
    public static final String MAP_LOADED = "map-loaded";
    /**
     * Describes an error that has occured while loading the Map. The 'type' property defines what resource could
     * not be loaded and the 'message' property will contain a descriptive error message.
     *
     * Event data format (Object):
     * .
     * ├── type - String ("style" | "sprite" | "source" | "tile" | "glyphs")
     * └── message - String
     */
    public static final String MAP_LOADING_ERROR = "map-loading-error";
    /**
     * The Map has entered the idle state. The Map is in the idle state when there are no ongoing transitions
     * and the Map has rendered all available tiles.
     *
     * Event data format (Object).
     */
    public static final String MAP_IDLE = "map-idle";
    /**
     * The requested style data has been loaded. The 'type' property defines what kind of style data has been loaded.
     *
     * Event data format (Object):
     * .
     * └── type - String ("style" | "sprite" | "sources")
     */
    public static final String STYLE_DATA_LOADED = "style-data-loaded";
    /**
     * The requested style has been fully loaded, including the style, specified sprite and sources' metadata.
     *
     * Event data format (Object).
     */
    public static final String STYLE_LOADED = "style-loaded";
    /**
     * A style has a missing image. This event is emitted when the Map renders visible tiles and
     * one of the required images is missing in the sprite sheet. Subscriber has to provide the missing image
     * by calling StyleManager#addStyleImage method.
     *
     * Event data format (Object):
     * .
     * └── id - String
     */
    public static final String STYLE_IMAGE_MISSING = "style-image-missing";
    /**
     * An image added to the Style is no longer needed and can be removed using StyleManager#removeStyleImage method.
     *
     * Event data format (Object):
     * .
     * └── id - String
     */
    public static final String STYLE_IMAGE_REMOVE_UNUSED = "style-image-remove-unused";
    /**
     * Source data has been loaded.
     * The 'id' property defines the source id.
     * The 'type' property defines if source's metadata (e.g., TileJSON) or tile has been loaded.
     * The 'loaded' property will be set to 'true' if all source's data required for Map's visible viewport, are loaded.
     * The 'tile-id' property defines the tile id if the 'type' field equals 'tile'.
     *
     * Event data format (Object):
     * .
     * ├── id - String
     * ├── type - String ("metadata" | "tile")
     * ├── loaded - optional Boolean
     * └── tile-id optional Object
     *     ├── z Number (zoom level)
     *     ├── x Number (x coorinate)
     *     └── y Number (y coorinate)
     */
    public static final String SOURCE_DATA_LOADED = "source-data-loaded";
    /**
     * Source has been added with StyleManager#addStyleSource runtime API.
     *
     * Event data format (Object):
     * .
     * └── id - String
     */
    public static final String SOURCE_ADDED = "source-added";
    /**
     * Source has been removed with StyleManager#removeStyleSource runtime API.
     *
     * Event data format (Object):
     * .
     * └── id - String
     */
    public static final String SOURCE_REMOVED = "source-removed";
    /**
     * The Map started rendering a frame.
     *
     * Event data format (Object).
     */
    public static final String RENDER_FRAME_STARTED = "render-frame-started";
    /**
     * The Map finished rendering a frame.
     * The 'render-mode' property tells whether the Map has all data ("full") required to render the visible viewport.
     * The 'needs-repaint' property provides information about ongoing transitions that trigger Map repaint.
     * The 'placement-changed' property tells if the symbol placement has been changed in the visible viewport.
     *
     * Event data format (Object):
     * .
     * ├── render-mode - String ("partial" | "full")
     * ├── needs-repaint - Boolean
     * └── placement-changed - Boolean
     */
    public static final String RENDER_FRAME_FINISHED = "render-frame-finished";
    /**
     * Camera has changed. This event is emitted whenever the visible viewport
     * changes due to the invocation of Map#setSize, Map#setBounds methods or when the camera
     * is modified by calling Map camera methods.
     *
     * Event data format (Object).
     */
    public static final String CAMERA_CHANGED = "camera-changed";
    /**
     * ResourceRequest event allows client to observe resource requests made by a
     * Map or MapSnapshotter objects.
     *
     * Event data format (Object):
     * .
     * ├── data-source - String ("resource-loader" | "network" | "database" | "asset" | "file-system")
     * ├── request - Object
     * │   ├── url - String
     * │   ├── kind - String ("unknown" | "style" | "source" | "tile" | "glyphs" | "sprite-image" | "sprite-json" | "image")
     * │   ├── priority - String ("regular" | "low")
     * │   └── loading-method - Array ["cache" | "network"]
     * ├── response - optional Object
     * │   ├── no-content - Boolean
     * │   ├── not-modified - Boolean
     * │   ├── must-revalidate - Boolean
     * │   ├── offline-data - Boolean
     * │   ├── size - Number (size in bytes)
     * │   ├── modified - optional String, rfc1123 timestamp
     * │   ├── expires - optional String, rfc1123 timestamp
     * │   ├── etag - optional String
     * │   └── error - optional Object
     * │       ├── reason - String ("success" | "not-found" | "server" | "connection" | "rate-limit" | "other")
     * │       └── message - String
     * └── cancelled - Boolean
     */
    public static final String RESOURCE_REQUEST = "resource-request";
}

The convenient extension functions are also provided to help subscribe to or unsubscribe from a single event type, and parse the event data from the observed events to strong-typed Kotlin classes.

Map events lifecycle

The closest equivalent of pre-v10's OnStyleLoaded listener is the OnStyleDataLoaded listener when its associated type is style, but we recommended using the new OnStyleLoaded listener instead.

The following simplified diagram helps explain the event lifecycle:

 ┌─────────────┐               ┌─────────┐                   ┌──────────────┐
 │ Application │                  Map                      │ResourceLoader│
 └──────┬──────┘               └────┬────┘                   └───────┬──────┘
                                                                   
        ├───── Set style URL ──────▶│                                
                                   ├───────────get style───────────▶│
                                                                   
                                   │◀─────────style data────────────┤
                                                                   
                                   ├─parse style─┐                  
                                                                  
              styleDataLoaded      ◀─────────────┘                  
        │◀────{"type": "style"}─────┤                                
                                   ├─────────get sprite────────────▶│
                                                                   
                                   │◀────────sprite data────────────┤
                                                                   
                                   ├──────parse sprite───────┐      
                                                                  
              styleDataLoaded      ◀─────────────────────────┘      
        │◀───{"type": "sprite"}─────┤                                
                                   ├─────get source TileJSON(s)────▶│
                                                                   
             sourceDataLoaded      │◀─────parse TileJSON data───────┤
        │◀──{"type": "metadata"}────┤                                
                                                                   
                                                                   
              styleDataLoaded                                      
        │◀───{"type": "sources"}────┤                                
                                   ├──────────get tiles────────────▶│
                                                                   
        │◀───────styleLoaded────────┤                                
                                                                   
             sourceDataLoaded      │◀─────────tile data─────────────┤
        │◀────{"type": "tile"}──────┤                                
                                                                   
                                                                   
        │◀────renderFrameStarted────┤                                
                                   ├─────render─────┐               
                                                                  
                                   ◀────────────────┘               
        │◀───renderFrameFinished────┤                                
                                   ├──render, all tiles loaded──┐   
                                                                  
                                   ◀────────────────────────────┘   
        │◀────────mapLoaded─────────┤                                
                                                                   
                                                                   
        │◀─────────mapIdle──────────┤                                
                            ┌ ─── ─┴─ ─── ┐                         
                               offline                            
                            └ ─── ─┬─ ─── ┘                         
                                                                   
        ├──────── Set camera ──────▶│                                
                                   ├───────────get tiles───────────▶│
                                                                   
                                   │┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─   
        │◀─────────mapIdle──────────┤   waiting for connectivity    
                                   ││  Map renders cached data      
                                   │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘  
                                                                   

New features and improvements

Platform-driven animation system

A new camera animation system leverages the Android animator framework and provides two sets of camera APIs: high-level animataion APIs and low-level animation APIs.

High-level animation APIs

High-level APIs provide parity with pre-v10 animations and cater to most typical use cases. In v10, customization options have been expanded. The Maps SDK v10 also leverages Android SDK listeners and time interpolators, delivering results that are more performant and implementations that are more convenient to use and enhance.

Important API changes:

  • flyTo replaces animateCamera.
  • easeTo replaces easeCamera.

pre-v10:

public final void easeCamera(
 @NonNull CameraUpdate update,
 int durationMs,
 @Nullable final MapboxMap.CancelableCallback callback
)

public final void animateCamera(
 @NonNull CameraUpdate update,
 final int durationMs
 @Nullable MapboxMap.CancelableCallback callback
)

v10:

MapboxMap.flyTo(
 cameraOptions: CameraOptions,
 animationOptions: MapAnimationOptions? = null
)

MapboxMap.easeTo(
 cameraOptions: CameraOptions,
 animationOptions: MapAnimationOptions? = null
)

// additional camera functions

MapboxMap.rotateBy(
 first: ScreenCoordinate,
 second: ScreenCoordinate,
 animationOptions: MapAnimationOptions? = null
)

MapboxMap.pitchBy(
 pitch: Double,
 animationOptions: MapAnimationOptions? = null
)

MapboxMap.scaleBy(
 amount: Double,
 screenCoordinate: ScreenCoordinate?,
 animationOptions: MapAnimationOptions? = null
)

MapboxMap.moveBy(
 screenCoordinate: ScreenCoordinate,
 animationOptions: MapAnimationOptions? = null
)

// MapAnimationOptions class definition

class MapAnimationOptions(
 val owner: String?,
 val duration: Long?,
 val interpolator: TimeInterpolator?,
 val animatorListener: Animator.AnimatorListener?
)

Low-level animation APIs

The Maps SDK v10 also introduces a new set of lower-level animation APIs. Each camera property can be animated independently through the Android SDK animator framework.

The following example constructs three independent animations (bearing, zoom, and pitch) and creates an animation set so they execute simultaneously. The animation can be customized by utilizing the flexible low-level APIs.

import com.mapbox.maps.plugin.animation.CameraAnimatorOptions.Companion.cameraAnimatorOptions
import com.mapbox.maps.plugin.animation.camera

val plugin = mapView.camera
val bearing = plugin.createBearingAnimator(cameraAnimatorOptions(0.0, 160.0)) {
 duration = 8500
 interpolator = AnticipateOvershootInterpolator()
}
val zoom = plugin.createZoomAnimator(
 cameraAnimatorOptions(18.0) {
   startValue = 15.0
 }
) {
 duration = 5000
 interpolator = AccelerateDecelerateInterpolator()
}
val pitch = plugin.createPitchAnimator(
 cameraAnimatorOptions(55.0) {
   startValue = 0.0
 }
) {
 duration = 7000
 interpolator = BounceInterpolator()
}

plugin.registerAnimators(bearing, zoom, pitch)

val animatorSet = AnimatorSet()
animatorSet.startDelay = 5000
animatorSet.playTogether(bearing, zoom, pitch)
animatorSet.addListener(object : AnimatorListenerAdapter() {
   override fun onAnimationEnd(animation: Animator?) {
     super.onAnimationEnd(animation)
     plugin.unregisterAnimators(bearing, pitch, zoom)
   }
})
animatorSet.start()

Location component plugin with puck and camera decoupled

With v10, we have introduced the location component plugin for managing the location indicator decoupled from camera updates. The location component plugin will only handle the location puck updates; tracking modes are removed. Camera updates can be manually handled and synced. We will be surfacing a plugin for this convenience soon.

3D model capabilities

The new location component plugin supports a new API to use a 3D model to visualize the end user’s location on the map. Under the hood, it uses ModelLayer and ModelSource.

mapView.location.enabled = true
mapView.location.locationPuck = LocationPuck3D(
  modelUri = "asset://race_car_model.gltf",
  modelScale = listOf(0.1f, 0.1f, 0.1f)
)

The LocationModelLayer options can be configured by:

data class LocationPuck3D(
  /**
   * An URL for the model file in gltf format.
   */
  var modelUri: String,
  /**
   * The scale of the model.
   */
  var position: List<Float> = listOf(0f, 0f),
  /**
   * The opacity of the model.
   */
  var modelOpacity: Float = 1f,
  /**
   * The scale of the model.
   */
  var modelScale: List<Float> = listOf(1f, 1f, 1f),
  /**
   * The scale expression of the model, which will overwrite the default scale expression that keeps the model size constant during zoom.
   */
  var modelScaleExpression: String? = null,
  /**
   * The rotation of the model.
   */
  var modelRotation: List<Float> = listOf(0f, 0f, 90f),
) : LocationPuck()

CacheManager

A new interface for managing the device ambient cache exposes APIs to reset, clear, reconfigure, and pre-load data. Below is a snippet of the class definition:

/**
* Forces a revalidation of the tiles in the ambient cache and downloads
* a fresh version of the tiles from the tile server.
*
* This is more efficient than clearing the cache using \c clearAmbientCache()
* because tiles in the ambient cache are re-downloaded to remove outdated data
* from a device. It does not erase resources from the ambient cache or delete
* the database, which can be computationally expensive operations that may carry
* unintended side effects.
*
* @param callback Called once the request is complete or an error occurred.
*/
@Override
public native void invalidateAmbientCache(@NonNull CacheStatusCallback callback);

/**
* Sets the maximum size of the ambient cache in bytes.
*
* This call is potentially expensive because it will try to trim the data
* in case the database is larger than the size defined.
*
* Setting the size to 0 will effectively disable the cache.
*
* Preferably, this method should be called before using the database,
* otherwise the default maximum size will be used.
*
* @param size The maximum size of the ambient cache in bytes.
* @param callback Called once the request is complete or an error occurred.
*/
@Override
public native void setMaximumAmbientCacheSize(long size, @NonNull CacheStatusCallback callback);

/**
* Erase resources from the ambient cache, freeing storage space.
*
* This operation can be potentially slow Compared to \c invalidateAmbientCache()
* because it will trigger a VACUUM on SQLite, forcing the database to move pages
* on the filesystem.
*
* @param callback Called once the request is complete or an error occurred.
*/
@Override
public native void clearAmbientCache(@NonNull CacheStatusCallback callback);

/**
* Inserts the provided resource into the ambient cache.
*
* This method mimics the caching that would take place if the equivalent
* resources were requested in the process of map rendering. Use this method
* to preload the cache with resources you know will be requested.
*
* This method is asynchronous; the data may not be immediately available for
* in-progress requests, though subsequent requests should have access to the
* cached data.
*
* @param url The URL at which the data can normally be found.
* @param data Response data to store for this resource. The data is expected to be uncompressed;
* internally, the cache will compress data as necessary.
* @param expires The date after which the resource is no longer valid.
* @param httpEntityTag An HTTP entity tag.
* @param mustRevalidate A Boolean value indicating whether the data is still usable past the expiration date.
*/
@Override
public native void preloadData(@NonNull String url, @NonNull String data, @Nullable Date modified, @Nullable Date expires, @Nullable String httpEntityTag, boolean mustRevalidate);

/**
* Sets path of a database to be used by the ambient cache and invokes provided
* callback when a database path is set.
*
* @param dbPath The new database path
* @param callback Callback to call once the request is completed or an error occurred.
*/
@Override
public native void setDatabasePath(@NonNull String dbPath, @NonNull CacheStatusCallback callback);

/**
* Prefetches the resources for the from network and populates the ambient cache.
*
* @param area Map area to pre-fetch and put into cache
* @param callback Callback to call once the operation is complete or encounters an error.
* @return Returns the request id
*/
@Override
public native long prefetchAmbientCache(@NonNull CacheAreaDefinition cacheArea, @Nullable CachePrefetchCallback callback);

/**
* Cancels the prefetch request.
*
* If there is no request corresponding to the given id, or the request has been already
* finished, this call is ignored.
*
* @param id Request id
*/
@Override
public native void cancelPrefetchRequest(long id);

3D terrain and sky layers

In the Maps SDK v10, you can show dramatic elevation changes against an atmospheric backdrop by enabling 3D terrain and using the new sky layer.

3D Terrain can be applied to a DEM data source. Below is a code snippet of enabling terrain using Style DSL.

mapboxMap.loadStyle(
 styleExtension = style(Style.SATELLITE_STREETS) {
   +rasterDemSource("TERRAIN_SOURCE") {
     url("mapbox://mapbox.terrain-rgb")
     tileSize(512)
   }
   +terrain("TERRAIN_SOURCE") {
     exaggeration(1.1) 
   }
)
Note

3D terrain is still in an experimental state. It might not work as expected with the map camera animation system.

Asynchronous query rendered features

Getting a list of map features is now performed asynchronously and does not block the executing thread.

pre-v10:

public List<Feature> queryRenderedFeatures(@NonNull PointF coordinates, @Nullable String... layerIds)

v10:

void queryRenderedFeatures(@NonNull List<ScreenCoordinate> shape, @NonNull RenderedQueryOptions options, @NonNull QueryFeaturesCallback callback);

// where callback is executed on background thread
public interface QueryFeaturesCallback {
   void run(@NonNull Expected<List<Feature>, String> features);
}

New OfflineManager

v10 introduces a new OfflineManager API that manages style packs and produces tileset descriptors for the tile store.

  • By default, users may download up to 250 MB of data for offline use without incurring additional charges. This limit is subject to change during the beta.
  • The new API replaces the deprecated OfflineRegionManager API. The OfflineManager API can be used to create offline style packs that contain style data, such as: style definition, sprites, fonts and other resources. Tileset descriptors created by the OfflineManager API are used to create tile packs via TileStore API. Mobile maps SDKs use tile packs for rendering map content. Example of how to use API is presented in a following snippet (pseudocode):
// 1. Get an instance of OfflineManager.
val offlineManager = OfflineManager(map.getResourceOptions())

// 2. Create a tile region with tiles for Streets
TilesetDescriptorOptions tilesetOptions(
 Style.MAPBOX_STREETS,
 0, // min zoom
 5, // max zoom
);

// Creation of a tileset descriptor WILL implicitly create a style package for provided StylePackLoadOptions.
tilesetOptions.stylePackOptions = StylePackLoadOptions(GlyphsRasterizationMode.AllGlyphsRasterizedLocally);
val descriptor = offlineManager.createTilesetDescriptor(tilesetOptions);

val tileStore = TileStore.getInstance();
TileRegionLoadOptions tileRegionLoadOptions(geometry, [descriptor]);
tileStore.loadTileRegion("my_region", tileRegionLoadOptions,
onProgressCallback(result) {
 // Handle TileRegionLoadProgress result
},
onFinishedCallback(result) {
 if (result) {
   // Handle TileRegion result
 } else {
   // Handle TileRegionError
 }
}
);

Deprecations and removals

PropertyFactory

PropertyFactory has been deprecated in favor of direct property setter and getter APIs inside layers.

StringDef annotations

StringDef annotations have been replaced by enum classes.

Point and LatLng

LatLngBounds and LatLng has been removed in favor of coordinate bounds and point geometry.

Renaming tilt to pitch

tilt has been renamed to pitch throughout our codebase. For example, Camera#tilt is replaced by CameraOptions#Pitch.

CameraUpdateFactory

CameraUpdateFactory has been removed in favor of the new platform-driven camera animation system.

CameraPosition

CameraPosition has been replaced with CameraOptions if setting camera to MapboxMap. When obtaining current MapboxMap camera, immutable CameraState with non-null properties will be returned.

getVisibleRegion

The function getVisibleRegion() is not available, but we have an equivalent implementation that can get the CoordinateBounds for the visible region.

val cameraState = mapView.getMapboxMap().cameraState
val bounds = mapView.getMapboxMap().coordinateBoundsForCamera(cameraState.toCameraOptions())

LegacyOfflineManager

The legacy pre-v10 OfflineManager has been deprecated and renamed to OfflineRegionManager.

LocationPlugin

The legacy LocationPlugin has been removed.