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 introduces a new Kotlin-based API for Android called "ArcGIS Maps SDK for Kotlin".
This release is a full reimagining of the ArcGIS Runtime SDK for Android 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 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, which replaces the Java-based ArcGIS Runtime SDK for Android. For more information, check out the blog ArcGIS Runtime in 2022 and beyond.
LifeCycleObserver
The Geo
class is a base class for Map
and Scene
, which implements the DefaultLifecycleObserver. Hence, forwarding lifecycle events like o
, o
, o
to a Geo
is not required anymore.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// set the API key
ArcGISEnvironment.apiKey = ApiKey.create(BuildConfig.API_KEY)
// 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.
lifecyleScope.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 SharedFlow, so callbacks are no longer necessary.
val simulatedLocationDataSource = SimulatedLocationDataSource(polyline)
simulatedLocationDataSource.start()
simulatedLocationDataSource.locationChanged.collect { location ->
val locationPoint = location.position
}
Loadable
Loadable state (Load
) is represented using a StateFlow, 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 Load
updates, as it is now a StateFlow. This way, you can get the intermediate Load
values.
lifecycleScope.launch {
portalItem.load()
}
lifecycleScope.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 & Jobs
ArcGIS Maps SDK for Kotlin substantially changes the Jobs or Tasks workflow. 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 which help to avoid working with nested callbacks.
// create a new offline map task
val offlineMapTask = OfflineMapTask(mapView.map)
// set the parameters of the task
val generateOfflineMapParameters = GenerateOfflineMapParameters(
geometry, minScale, maxScale
)
// create a job with the offline map parameters
val offlineMapJob = offlineMapTask.createGenerateOfflineMapJob(
generateOfflineMapParameters,
offlineMapPath
)
// start the job
offlineMapJob.start()
with (lifecycleScope) {
// 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 ->
mapView.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
Polyline
andBuilder Polygon
now takes a function parameter with the builder as the receiver type, allowing you to add geometries to the builder in a Kotlin idiomatic wayBuilder -
A Part can be created with segments using
Mutable
Part.create With Segments -
The names of mutable and immutable geometry collection types are aligned with Kotlin idioms. Below is a list of geometry types defined in ArcGIS Maps SDK for Kotlin that is analogous to ArcGIS Runtime API for Android.
Mutable
<-Part Part
Part
<-Immutable
Part Mutable
<-Part Collection Part
Collection Part
<-Collection Immutable
Part Collection
-
Part
andMutable
may be viewed as a collection of points by accessing thePart .points
property -
Geometry
methods are generic to provide greater type safety hence an identical geometry is returned as shown below.Engine 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(-10e5, 40e5)
addPoint(20e5, 50e5)
}.toGeometry()
// Make a part geometry using segments
val partGeometry = MutablePart.createWithSegments(
listOf(leftCurve, leftArc, rightArc, rightCurve),
spatialReference
)
val polygon = Polygon(listOf(partGeometry))
Gestures
MapView and SceneView have gesture events instead of overriding Default
. The events are represented as Shared
s and can be collected in a coroutine.
lifecycleScope.launch {
mapView.onSingleTapConfirmed.collect { tapConfirmedEvent ->
val mapPoint = tapConfirmedEvent.mapPoint
val screenCoordinate = tapConfirmedEvent.screenCoordinate
}
}
Authentication
ArcGIS Maps SDK for Kotlin has a different approach for working with secured ArcGIS services. The SDK provides a set of asynchronous APIs to create ArcGIS credentials and generate tokens independent of loading any specific secured resource. The credentials created while handling authentication challenges are stored and re-used while sending subsequent requests.
ArcGISEnvironment.authenticationManager.arcGISAuthenticationChallengeHandler =
ArcGISAuthenticationChallengeHandler { challenge ->
val result: Result<TokenCredential> = TokenCredential.create(
challenge.requestUrl, "username", "password", tokenExpirationInterval
)
val credential = result.getOrElse { throwable ->
Log.e("TokenCredential Creation", "Failed")
return@ArcGISAuthenticationChallengeHandler ArcGISAuthenticationChallengeResponse
.ContinueAndFailWithError(throwable)
}
Log.d("TokenCredential Creation", "Succeeded")
return@ArcGISAuthenticationChallengeHandler ArcGISAuthenticationChallengeResponse
.ContinueWithCredential(credential)
}
lifecycleScope.launch {
// the authenticationManager will handle authentication to get the PortalItem
val portal = Portal(portalURL, Portal.Connection.Authenticated)
val portalItem = PortalItem(portal, itemID)
val map = ArcGISMap(portalItem)
mapView.map = map
map.loadStatus.collect { loadStatus ->
when (loadStatus) {
LoadStatus.NotLoaded -> { ... }
LoadStatus.Loading ->
Log.e("LoadStatus", "About to load map")
LoadStatus.Loaded ->
Log.e("LoadStatus", "Map loaded successfully")
is LoadStatus.FailedToLoad ->
Log.e("LoadStatus", loadStatus.error.message.toString())
}
}
}
Output
LoadStatus: About to load map
TokenCredential Creation: Succeeded
LoadStatus: Map loaded successfully
See the Migrate authentication from 100.x to 200.x topic for more instructions for migrating authentication code in your app.
Custom Location DataSource
ArcGIS Maps SDK for Kotlin introduces a Custom
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 Location
so that it can interface with other parts of the API.
{
// ... sets up the MapView
// create a custom location emitter
val customLocationProvider = { SingleLocationEmitter() }
// add the location provider to the datasource
val customLocationDataSource = CustomLocationDataSource(customLocationProvider)
lifecycleScope.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 datasource to the MapView
mapView.locationDisplay.dataSource = customLocationDataSource
}
// ... in an external class, create a custom location provider
class SingleLocationEmitter() : CustomLocationDataSource.LocationProvider {
override val locations: Flow<Location> = flow {
emit(
Location(
Instant.now(),
point,
horizontalAccurary,
verticalAccuracy,
speed,
course,
lastKnown
)
)
}
override val headings: Flow<Double> = flow {
emit(0.0)
}
}
ApplicationContext Requirement
ArcGIS Maps SDK for Kotlin update from the ArcGIS Runtime SDK for Android requires that you set the ArcGISEnvironment.application
property in a few parts of the API. Context is required for Location
, Custom
, System
, Indoors
, Route
, Service
, Closest
or Authentication
. This property can be set at the beginning of the activity as follows:
ArcGISEnvironment.applicationContext = this.applicationContext
Library-specific DataTypes
Color: The update replaces android.graphics.color
with com.arcgismaps.Color
. This color library comes with default colors out of the box, can create custom RGB colors, and can use colors from the app's resources.
// multiple ways to create arcgismaps.Color
var accentColor = Color(getColor(R.color.colorAccent))
accentColor = Color.fromRgba(10,255,0,255)
accentColor = Color(0x0AFF00)
// set selection color
mapView.selectionProperties.color = accentColor
ArcGIS Maps GUIDs: ArcGIS Maps SDK for Kotlin introduces its own datatype GUID, which represents a 128-bit globally unique identifier to represent GlobalID and GUID fields from ArcGIS services and geodatabases
val utilityElement = utilityNetwork.createElementOrNull(
utilityAssetType,
Guid("<Unique-Identifier-Here>"),
utilityTerminal
)