ArcGIS Runtime SDK version 100.15 is a long-term support release focused exclusively on bug fixes and minor updates. ArcGIS Maps SDKs for Native Apps version 200.x builds on the proven architecture of 100.15, and is designed to leverage the latest developer framework innovations. This topic outlines areas of the API that have undergone changes and provides guidance for re-factoring 100.x code for a 200.x app.
Release 200.0 introduced a new Kotlin-based API for Android called ArcGIS Maps SDK for Kotlin.
This release is a full re-imagining of the ArcGIS Runtime SDK for Android 100.x as a Kotlin-first SDK, with out-of-the-box support for features like coroutines, flows, and null safety. The Java-based ArcGIS Runtime SDK for Android 100.x will receive long-term support (LTS), with ongoing bug fixes but no further feature updates.
To utilize the new features, migrate to ArcGIS Maps SDK for Kotlin 200.x, which replaces the Java-based ArcGIS Runtime SDK for Android 100.x. For more information, check out the blog ArcGIS Runtime in 2022 and beyond.
The code snippets on this page have two tabs: one demonstrating the ArcGIS Maps SDK for Kotlin 200.x patterns, and the other showing the ArcGIS Runtime SDK for Android 100.x patterns that have been replaced.
Support for Jetpack Compose
ArcGIS Maps SDK for Kotlin 200.2 and later provides full support for Jetpack Compose. Code snippets on this page demonstrate the use of the MapView
composable function, which is defined, along with other composables, in the ArcGIS Maps SDK for Kotlin Tookit. For more information, see Toolkit.
If you do not wish to use Jetpack Compose, you can continue using XML layouts and the MapView
and SceneView
classes instead.
LifeCycleObserver
The GeoView
class is a base class for MapView
and SceneView
, which implements the DefaultLifecycleObserver. Hence, forwarding lifecycle events like on
, on
, on
to a GeoView
is no longer required.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// set the API key
ArcGISEnvironment.apiKey = ApiKey.create(BuildConfig.YOUR_ACCESS_TOKEN)
// set up data binding for the activity
val activityMainBinding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
mapView = activityMainBinding.mapView
// add the MapView to the activity's lifecycle
lifecycle.addObserver(mapView)
}
Coroutines and Coroutines Scope
Kotlin's suspending function is safer and less error-prone for asynchronous operations. All suspend functions return a Result
, so there is no longer a need for try/catch block. Additionally, you can launch a coroutine using a lifecycle-aware coroutine scope, which will be canceled automatically if the lifecycle is destroyed.
coroutineScope.launch {
// run the suspend function to get a geodatabase
val geodatabaseResult = Geodatabase.create(filePath)
// get the result once suspend is completed
geodatabaseResult.onSuccess { geodatabase ->
// run the suspend function to create a geodatabase feature table
val featureTableResult = geodatabase.createTable(tableDescription)
// get the result once suspend is completed
featureTableResult.onSuccess { geodatabaseFeatureTable ->
setupMapFromGeodatabase(geodatabaseFeatureTable)
}.onFailure { throwable ->
showMessage(throwable.message)
}
}.onFailure { throwable ->
showMessage(throwable.message)
}
}
Events handling
All events are now represented using Shared
, so callbacks are no longer necessary.
val simulatedLocationDataSource = SimulatedLocationDataSource(polyline)
simulatedLocationDataSource.start()
simulatedLocationDataSource.locationChanged.collect { location ->
val locationPoint = location.position
}
Loadable
Loadable state (LoadStatus
) is represented using a State
, while load()
is a suspending function which returns a load process complete Result
. The load error can be obtained from the Result
if it fails.
private suspend fun loadPortalItem() {
val portalItem = PortalItem(Portal("https://www.arcgis.com"), itemID)
portalItem.load().onSuccess {
val featureLayer = FeatureLayer.createWithItem(portalItem)
setFeatureLayer(featureLayer, viewpoint)
}.onFailure { throwable ->
showError(throwable.message)
}
}
There's an asynchronous way to listen to LoadStatus
updates, as the Loadable.loadStatus
property is now a State
. This way, you can get the intermediate LoadStatus
values.
coroutineScope.launch {
portalItem.load()
}
coroutineScope.launch {
portalItem.loadStatus.collect { loadStatus ->
when (loadStatus) {
LoadStatus.Loaded -> {
val featureLayer = FeatureLayer.createWithItem(portalItem)
setFeatureLayer(featureLayer)
}
is LoadStatus.FailedToLoad -> {
showError("Error loading portal item: ${loadStatus.error.message}")
}
LoadStatus.Loading -> { ... }
LoadStatus.NotLoaded -> { ... }
}
}
}
Tasks and jobs
ArcGIS Maps SDK for Kotlin 200.x substantially changes the Job
and task
workflows. A job needs to be run in a Coroutine
, which provides control over the flow of the progress or completion. Multiple jobs and tasks can be performed using coroutine flows; thus, you can in general avoid working with nested callbacks.
// create a new offline map task
val offlineMapTask = OfflineMapTask(map)
// set the parameters of the task
val generateOfflineMapParameters = GenerateOfflineMapParameters(
areaOfInterest = geometry,
minScale = minScale,
maxScale = maxScale
)
// create a job with the offline map parameters
val offlineMapJob = offlineMapTask.createGenerateOfflineMapJob(
parameters = generateOfflineMapParameters,
downloadDirectoryPath = offlineMapPath
)
// start the job
offlineMapJob.start()
with (coroutineScope) {
// collect the progress of the job
launch {
offlineMapJob.progress.collect {
val progressPercentage = offlineMapJob.progress.value
}
}
// display map if job succeeds
launch {
offlineMapJob.result().onSuccess { generateOfflineMapResult ->
map = generateOfflineMapResult.offlineMap
}.onFailure { throwable ->
showMessage(throwable.message.toString())
}
}
}
Geometry and geometry builders
There are several changes to the usage of geometries and geometry builders to improve readability and ease of use.
-
Geometry builders like
PolylineBuilder
andPolygonBuilder
now takes a function parameter with the builder as the receiver type, allowing you to add geometries to the builder in a Kotlin idiomatic way. -
A part can be created with segments using
MutablePart.createWithSegments()
-
The names of mutable and immutable geometry collection types are aligned with Kotlin idioms. The list below shows the geometry types defined in ArcGIS Maps SDK for Kotlin 200.x and their analogues in ArcGIS Runtime API for Android 100.x.
ArcGIS Maps SDK for Kotlin 200.x ArcGIS Runtime API for Android v100.x MutablePart
Part
Part
Immutable
Part MutablePartCollection
Part
Collection PartCollection
Immutable
Part Collection -
A
Part
andMutablePart
may be viewed as a collection of points by accessing itspoints
property. -
GeometryEngine
methods are generic to provide greater type safety hence an identical geometry is returned as shown below.ArcGIS Maps SDK for Kotlin v200.xUse dark colors for code blocks Copy fun <T : Geometry> projectOrNull(geometry: T, outputSpatialReference: SpatialReference, datumTransformation: DatumTransformation?): T? fun <T : Geometry> projectOrNull(geometry: T, spatialReference: SpatialReference): T? fun <T : Geometry> simplifyOrNull(geometry: T): T? fun <T : Geometry> createWithM(geometry: T, m: Double?): T fun <T : Geometry> createWithZ(geometry: T, z: Double?): T fun <T : Geometry> createWithZAndM(geometry: T, z: Double?, m: Double?): T
// Make a polyline geometry
val polylineGeometry = PolylineBuilder(spatialReference) {
addPoint(x = -10e5, y = 40e5)
addPoint(x = 20e5, y = 50e5)
}.toGeometry()
// Make a part geometry using segments
val partGeometry = MutablePart.createWithSegments(
segments = listOf(leftCurve, leftArc, rightArc, rightCurve),
spatialReference = spatialReference
)
val polygon = Polygon(listOf(partGeometry))
Gestures
The MapView
MapView and SceneView
composables have gesture events instead of overriding Default
. The events are represented as Shared
s and can be collected in a coroutine.
MapView(
modifier = Modifier.fillMaxSize(),
arcGISMap = map,
onSingleTapConfirmed = { singleTapConfirmedEvent ->
val mapPoint = singleTapConfirmedEvent.mapPoint
val screenCoordinate = singleTapConfirmedEvent.screenCoordinate
// . . .
}
)
Authentication
See the Migrate authentication from 100.x to 200.x topic for instructions on migrating authentication code in your app.
Custom location dataSource
ArcGIS Maps SDK for Kotlin 200.x introduces a CustomLocationDataSource
that can be driven by a user-defined location data provider. This can be useful if you have location data from a custom source and would like that data in the form of a LocationDataSource
so that it can interface with other parts of the API.
fun createCustomLocationDataSource() {
// create a custom location emitter
val customLocationProvider = { SingleLocationEmitter() }
// add the location provider to the datasource
val customLocationDataSource = CustomLocationDataSource(customLocationProvider)
coroutineScope.launch {
customLocationDataSource.start().onSuccess {
// custom location datasource successfully started
}.onFailure { throwable ->
// error starting location datasource
}
customLocationDataSource.locationChanged.collect {
// get changes in the location
}
}
// add the custom location datasource to the location display
return customLocationDataSource
}
// ... in an external class, create a custom location provider
class SingleLocationEmitter() : CustomLocationDataSource.LocationProvider {
override val locations: Flow<Location> = flow {
emit(
Location.create(
position = point,
horizontalAccuracy = horizontalAccuracy,
verticalAccuracy = verticalAccuracy,
speed = speed,
course = course,
lastKnown = lastKnown,
timestamp = Instant.now()
)
)
}
override val headings: Flow<Double> = flow {
emit(0.0)
}
}
The custom location data source is assigned to the dataSource
property of the location display. One way to create the location display and pass it to the MapView
composable is the following.
import com.arcgismaps.toolkit.geoviewcompose.rememberLocationDisplay
val locationDisplay = rememberLocationDisplay {
dataSource = createCustomLocationDataSource()
}
MapView(
modifier = Modifier.fillMaxSize(),
arcGISMap = map,
locationDisplay = locationDisplay
)
ApplicationContext requirement
ArcGIS Maps SDK for Kotlin 200.x differs from the ArcGIS Runtime SDK for Android 100.x in that you now set the ArcGISEnvironment.applicationContext
property in a few parts of the API. Context is required for LocationDataSource
, CustomLocationDataSource
, SystemLocationDataSource
, IndoorsLocationDataSource
, RouteTask
, ServiceAreaTask
, ClosestFacilityTask
, or AuthenticationManager
. This property can be set at the beginning of the activity as follows:
ArcGISEnvironment.applicationContext = this.applicationContext
Library-specific data types
Color
: ArcGIS Maps SDK for Kotlin 200.x replaces android.graphics.color
with com.arcgismaps.
. This color library comes with default colors out of the box. You can also create custom RGB colors and use colors from the app's resources.
import com.arcgismaps.Color
var accentColor = Color.green
// var accentColor = Color.fromRgba(r = 0, g = 255, b = 0, a = 255)
// var accentColor = 0xFF00FF00.toInt()
// val accentColor = Color(colorResource(id = R.color.colorAccent).toArgb())
val selectionProperties = remember {
SelectionProperties().apply {
color = accentColor
}
}
MapView(
modifier = Modifier.fillMaxSize(),
arcGISMap = map,
selectionProperties = selectionProperties
)
ArcGIS Maps GUIDs: ArcGIS Maps SDK for Kotlin 200.x introduces its own data type, Guid
, which represents a 128-bit globally unique identifier to represent GlobalID and GUID fields from ArcGIS services and geodatabases.
val utilityElement = utilityNetwork.createElementOrNull(
assetType = utilityAssetType,
globalId = Guid("<Unique-Identifier-Here>"),
terminal = utilityTerminal
)