メインコンテンツまでスキップ

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, for example 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:11.8.0"

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:11.8.0') {
    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. The 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

We have split the setStyle API 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 use 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 displayed 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. The source properties can be declared through methods such as url(), geometry(), feature(), or featureCollection() and data is parsed on the worker thread not blocking UI.

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.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))
View annotations available in v10.2.0 and higher
In v9, view annotations were included in the Mapbox Annotation Plugin. In pre-v9 versions, this functionality was included in the Mapbox MarkerView Plugin.The Maps SDK v10 and higher comes with view annotation functionality built in, and view annotations are available in v10.2.0 and higher. For more details see the View annotations guide.

Map events

In v10, there are two ways to track 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.

You can find the available events and their documentation in the MapEvents class as follows:

public final class MapEvents {
/**
* The style has been fully loaded, and the `map` has rendered all visible tiles.
*
* ``` text
* 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.
* In case of `source` or `tile` loading errors, `source-id` will contain the id of the source failing.
* In case of `tile` loading errors, `tile-id` will contain the id of the tile
*
* ``` text
* Event data format (Object):
* .
* ├── type - String ("style" | "sprite" | "source" | "tile" | "glyphs")
* ├── message - String
* ├── source-id - optional String
* └── tile-id - optional Object
* ├── z Number (zoom level)
* ├── x Number (x coorinate)
* └── y Number (y coorinate)
* ```
*/
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 requested non-volatile tiles. The event will not be emitted if `setUserAnimationInProgress`
* and / or `setGestureInProgress` is set to `true`.
*
* ``` text
* 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 may be emitted synchronously, for example, when `setStyleJSON` is used to load style.
*
* Based on an event data `type` property value, following use-cases may be implemented:
* - `style`: Style is parsed, style layer properties could be read and modified, style layers and sources could be
* added or removed before rendering is started.
* - `sprite`: Style's sprite sheet is parsed and it is possible to add or update images.
* - `sources`: All sources defined by the style are loaded and their properties could be read and updated if needed.
*
* ``` text
* 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.
*
* ``` text
* Event data format (Object).
* ```
*
* Note: The style specified sprite would be marked as loaded even with sprite loading error (An error will be emitted via `MapLoadingError`).
* Sprite loading error is not fatal and we don't want it to block the map rendering, thus this event will still be emitted if style and sources are fully loaded.
*
*/
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 `addStyleImage` method.
*
* ``` text
* 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 `removeStyleImage` method.
*
* ``` text
* Event data format (Object):
* .
* └── id - String
* ```
*/
public static final String STYLE_IMAGE_REMOVE_UNUSED = "style-image-remove-unused";
/**
* A source data has been loaded.
* Event may be emitted synchronously in cases when source's metadata is available when source is added to the style.
*
* The `id` property defines the source id.
*
* The `type` property defines if source's metadata (e.g., TileJSON) or tile has been loaded. The property of `metadata`
* value might be useful to identify when particular source's metadata is loaded, thus all source's properties are
* readable and can be updated before `map` will start requesting data to be rendered.
*
* The `loaded` property will be set to `true` if all source's data required for visible viewport of the `map`, are loaded.
* The `tile-id` property defines the tile id if the `type` field equals `tile`.
*
* ``` text
* 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";
/**
* The source has been added with `addStyleSource` method.
* The event is emitted synchronously, therefore, it is possible to immediately
* read added source's properties.
*
* ``` text
* Event data format (Object):
* .
* └── id - String
* ```
*/
public static final String SOURCE_ADDED = "source-added";
/**
* The source has been removed with `removeStyleSource` method.
* The event is emitted synchronously, thus, `getStyleSources` will be
* in sync when the `observer` receives the notification.
*
* ``` text
* 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.
*
* ``` text
* 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";
/**
* The camera has changed. This event is emitted whenever the visible viewport
* changes due to the invocation of `setSize`, `setBounds` methods or when the camera
* is modified by calling camera methods. The event is emitted synchronously,
* so that an updated camera state can be fetched immediately.
*
* ``` text
* Event data format (Object).
* ```
*/
public static final String CAMERA_CHANGED = "camera-changed";
/**
* The `ResourceRequest` event allows client to observe resource requests made by a
* `map` or `map snapshotter` objects.
*
* ``` text
* 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
* │ ├── source - String ("network" | "cache" | "tile-store" | "local-file")
* │ ├── 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 the pre-v10 function 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│
* └──────┬──────┘ └────┬────┘ └───────┬──────┘
* │ │ │
* ├───────setStyleURI────────▶│ │
* │ ├───────────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 │ │
* │ └ ─── ─┬─ ─── ┘ │
* │ │ │
* ├─────────setCamera────────▶│ │
* │ ├───────────get tiles───────────▶│
* │ │ │
* │ │┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │
* │◀─────────MapIdle──────────┤ waiting for connectivity │ │
* │ ││ Map renders cached data │
* │ │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │

Offline

Retaining your offline cache from previous Maps SDK versions

Migrating from previous versions of the Maps SDK requires the following steps to make sure that resources created in v9 are available to users in v10 as the offline database has moved.

The offline database file, which used to be located at ./files/mbgl-offline.db in v9, is now located at ./files/.mapbox/map_data/map_data.db. The SDK will not automatically detect the legacy database and an application level migration is necessary to move the existing database.

Below is a sample method to do this migration:

fun migrateOfflineCache() {

// Old and new cache file paths
val targetPathName = applicationContext.filesDir.absolutePath + "/.mapbox/map_data"
val sourcePath = Paths.get(applicationContext.filesDir.absolutePath + "/mbgl-offline.db")
val targetPath = Paths.get(targetPathName + "/map_data.db")

try {
val directory = File(targetPathName)
directory.mkdirs()
Files.move(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING)
Log.d("TAG","v10 cache directory created successfully")
} catch (e: Exception) {
Log.d("TAG", "${e}... file move unsuccessful")
}
}

Note, this method will need to be called before the new cache is created.

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
): Cancelable

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

// additional camera functions

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

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

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

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

// 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 Mapbox Location Component Plugin for managing the location indicator decoupled from camera updates. v9 tracking modes have been replaced by the Mapbox Viewport Plugin (available starting in v10.3). Besides following objects on a map, it can be extended with custom states and transitions.

For more details, see the User location guide.

Scale bar plugin

You can access plugins from pre-v10 directly in the Maps SDK v10.

pre-v10:

dependencies {
implementation 'com.mapbox.mapboxsdk:mapbox-android-plugin-scalebar-v9:0.5.0'
}

v10:

If you install the Maps SDK in full, plugins will be included and you do not need to specify plugins separately in your build.gradle file. You can also use plugins independent of other Maps SDK features by installing the plugin only (as seen below).

dependencies {
implementation 'com.mapbox.plugin:maps-scalebar:10.0.0'
}

For more details, see the Mapbox Maps ScaleBar Plugin's README.

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. 3D puck supports wide variety of properties for customization.

mapView.location.enabled = true
mapView.location.locationPuck = LocationPuck3D(
modelUri = "asset://sportcar.glb",
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")
}
+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.

3D globe

In the Maps SDK v10, you could switch to globe projection to show map as the 3D globe. Switching projections is available in runtime.

mapboxMap.setMapProjection(MapProjection.Globe)
Note
3D globe is still in an experimental state. Not all layer types are supported for globe projection.

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.

Set the maximum size allocated for the render cache in megabytes 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.

この{Type}は役に立ちましたか?