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
Package | pre-v10 | v10 |
---|---|---|
Maven group ID for map | com.mapbox.mapboxsdk | com.mapbox.maps |
Maven artifact ID for map | mapbox-android-sdk | android |
Maven group ID for plugins | com.mapbox.mapboxsdk | com.mapbox.plugin |
Maven group ID for plugins | com.mapbox.mapboxsdk | com.mapbox.extension |
Maven artifact ID for plugins | mapbox-android-plugin-PLUGINNAME | maps-PLUGINNAME |
Package name for maps | com.mapbox.mapboxsdk.maps | com.mapbox.maps |
MapView class in the layout | com.mapbox.mapboxsdk.maps.MapView | com.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:
-
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}')
} -
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")
}
} -
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:
- The
MapView
XML attribute (mapbox_resourcesAccessToken
) - Token that is programmatically set to
ResourceOptionsManager.getDefault(context: Context, defaultToken: String? = null)
- 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.:
Function | Description |
---|---|
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 AnnotationManager
s. 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 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
replacesanimateCamera
.easeTo
replaceseaseCamera
.
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)
}
)
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)
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);
}