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.
  • Java 8 language features support should be declared in app-level build.gradle file.
android {
    ...
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }
}

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

For common examples, refer to the Example App.

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)
   // Will load Style.MAPBOX_STREETS by default if no style is provided. Users can load other styles if need.
   //mapView.getMapboxMap().loadStyleUri(Style.SATELLITE)
 }

 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()
 }
}

Lifecycle Plugin

Beginning with v10.0.0-rc.5, the lifecycle plugin is included as a standard part of the Maps SDK.

The plugin simplifies handling your activity or fragment's lifecycle events (onStart/onStop/onDestroy/onLowMemory). By default, the lifecycle plugin will automatically hook into the lifecycle events of the host Activity or Fragment, so that developers don't need to manually invoke the corresponding lifecycle methods of the MapView.

Note: To be able to function properly, the lifecycle plugin requires the application to use appcompat v1.3.0+ and above. Otherwise, the lifecycle events (onStart/onStop/onDestroy/onLowMemory) must be manually handled.

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-rc.5"

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-rc.5') {
            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.

Default ResourceOptions configuration(including access token)

The pre-v10 required configuration of the access token through the Mapbox.getInstance API. With v10, the singleton Mapbox object has been removed and ResourceOptionsManager is introduced to manage the application-scoped ResourceOptions, including the default 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 ResourceOptionsManager.getDefault(context: Context, defaultToken: String? = null)
  3. Token that with predefined string identifier resource named mapbox_access_token

A unified configuration can be implemented by updating the default settings using ResourceOptionsManager.getDefault(context: Context, defaultToken: String? = null).update { } API:

// Set the application-scoped ResourceOptionsManager with customised token and tile store usage mode
// so that all MapViews created with default config will apply these settings.
ResourceOptionsManager.getDefault(this, getString(R.string.mapbox_access_token)).update {
  tileStoreUsageMode(TileStoreUsageMode.READ_ONLY)
}

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

// set map options
val mapOptions = MapOptions.Builder().applyDefaultParams(this)
  .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()

// plugins that will be loaded as part of MapView initialisation
val plugins = listOf(
  PLUGIN_LOGO_CLASS_NAME,
  PLUGIN_ATTRIBUTION_CLASS_NAME
)

// set token and tile store usage mode for this particular map view, these settings will overwrite the default value.
val resourceOptions = ResourceOptions.Builder().applyDefaultParams(this)
  .accessToken(getString(R.string.mapbox_access_token))
  .tileStoreUsageMode(TileStoreUsageMode.DISABLED)
  .build()

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

val mapInitOptions =
  MapInitOptions(this, resourceOptions, mapOptions, plugins, 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)
}

Parsing geojson on a worker thread.

A worker thread is introduced for GeoJsonSource to parse geojson data asynchronously, which will unblocking the UI thread while the data is big.

GeoJsonSource can be created with the help of Style DSL functions:

/**
 * DSL function for [GeoJsonSource] performing parsing using background thread.
 * Immediately returns [GeoJsonSource] with no data set and starts preparing actual data
 * using a worker thread.
 *
 * If using runtime styling:
 *
 * loadStyle(style(Style.DARK) {
 *   +geoJsonSource(id) {
 *    featureCollection(collection)
 *   }
 *   ...
 * }
 *
 * compositing style will be performed correctly under the hood and
 * [Style.OnStyleLoaded] will be emitted in correct moment of time when all sources are parsed.
 *
 * If creating geojson sources for already loaded Style please consider using overloaded
 * geoJsonSource(String, GeoJsonSource.Builder.() -> Unit, onGeoJsonParsed: (GeoJsonSource) -> Unit) function
 * and use fully prepared [GeoJsonSource] in onGeoJsonParsed callback.
 */
fun geoJsonSource(
  id: String,
  block: GeoJsonSource.Builder.() -> Unit
): GeoJsonSource

/**
 * DSL function for [GeoJsonSource] performing parsing using a worker thread.
 * Immediately returns [GeoJsonSource] with no data set,
 * fully parsed [GeoJsonSource] is returned in [onGeoJsonParsed] callback.
 *
 * Using this method means that it is user's responsibility to proceed with adding this source,
 * layers or other style objects in [onGeoJsonParsed] callback.
 */
fun geoJsonSource(
  id: String,
  block: GeoJsonSource.Builder.() -> Unit,
  onGeoJsonParsed: (GeoJsonSource) -> Unit
): GeoJsonSource

If updating existing GeoJsonSource from some Style overloaded functions are introduced:

/**
   * Add a FeatureCollection to the GeojsonSource.
   * If [onDataParsed] is provided and not null - data will be loaded in async mode.
   * Otherwise method will be synchronous.
   *
   * @param value the feature collection
   * @param onDataParsed optional callback notifying when data is parsed on a worker thread
   */
  fun featureCollection(
    value: FeatureCollection,
    onDataParsed: ((GeoJsonSource) -> Unit)? = null
  )

Similar functions are introduced for feature and geometry. All other properties can be executed on main thread only.

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.createCircleAnnotationManager(mapView)

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()

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.mapbox-terrain-dem-v1")
     // 514 specifies padded DEM tile and provides better performance than 512 tiles.
     tileSize(514)
   }
   +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<String, List<QueriedFeature>> features);
}

New OfflineManager

v10 introduces a new OfflineManager API that manages style packs and produces tileset descriptors for the tile store. Read more about OfflineManager in the Offline guide.

Localization extension

v10 provides an ability to change style language based on locale given. It could be achieved with following code:

// Get current device locale
val locale = resources.configuration.locale
// Apply this locale to given style
mapView.getMapboxMap().loadStyleUri(nextStyle) {
   it.localizeLabels(locale)
}

Render cache

Render cache is an experimental feature aiming to reduce map rendering resource usage by caching intermediate rendering results of tiles into specific cache textures for reuse between frames.

The performance benefit of the cache depends on the map style you are using because not all layers are cacheable (for example, when using viewport aligned features). Render cache always prefers quality over performance.

The maximum size allocated for the render cache in megabytes can be set using the MapboxMap#setRenderCacheOptions(RenderCacheOptions) API. The recommended starting values for the cache sizes are 64 and 128 for devices with pixel ratio 1.0 and > 1.0 respectively. Setting the render cache size to 0 will effectively disable the render cache. An example using this API is presented in the following snippet (pseudocode):

val mapboxMap = mapView.getMapboxMap()
// Set the large(128MB) render cache size.
mapboxMap.setRenderCacheOptions(RenderCacheOptions.Builder().setSmallSize().build())

// Or, disable the render cache.
mapboxMap.setRenderCacheOptions(RenderCacheOptions.Builder().setDisabled().build())

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.