Take a map offline using a preplanned map area.
Use case
Generating offline maps on demand for a specific area can be time consuming for users and a processing load on the server. If areas of interest are known ahead of time, a web map author can pre-create packages for these areas. This way, the generation only needs to happen once, making the workflow more efficient for users and servers.
An archeology team could define preplanned map areas for dig sites which can be taken offline for field use. To see the difference, compare this sample to the "Generate offline map" sample.
How to use the sample
Select a map area from the Preplanned map areas list. The download progress will be shown and when a download is complete it will be displayed in the map view.
How it works
- Open the online
ArcGISMap
from aPortalItem
and display it. - Create an
OfflineMapTask
using the portal item. - Get the
PreplannedMapArea
s from the task. - To download a selected map area, create default
DownloadPreplannedOfflineMapParameters
from theOfflineMapTask
using the selected preplanned map area. - Set the update mode of the preplanned map area.
- Use the parameters and a local path to create a
DownloadPreplannedOfflineMapJob
from the task. - Start the
DownloadPreplannedOfflineMapJob
. Once it has completed, get theDownloadPreplannedOfflineMapResult
. - Get the
ArcGISMap
from the result and display it in theMapView
.
Relevant API
- DownloadPreplannedOfflineMapJob
- DownloadPreplannedOfflineMapParameters
- DownloadPreplannedOfflineMapResult
- OfflineMapTask
- PreplannedMapArea
About the data
The Naperville stormwater network map is based on ArcGIS Solutions for Stormwater and provides a realistic depiction of a theoretical stormwater network.
Additional information
PreplannedUpdateMode
can be used to set the way the preplanned map area receives updates in several ways:
NoUpdates
- No feature updates will be performed.DownloadScheduledUpdates
- Scheduled, read-only updates will be downloaded from the online map area and applied to the local mobile geodatabases.DownloadScheduledUpdatesAndUploadNewFeatures
- Scheduled, read-only updates are downloaded from the online map area and applied to the local mobile geodatabases. Newly added features can also be uploaded to the feature service.SyncWithFeatureServices
- Changes, including local edits, will be synced directly with the underlying feature services.
For more information about offline workflows, see Offline maps, scenes, and data in the ArcGIS Developers guide.
Tags
map area, offline, pre-planned, preplanned
Sample Code
/* Copyright 2025 Esri
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.esri.arcgismaps.sample.downloadpreplannedmaparea.components
import android.app.Application
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.arcgismaps.mapping.ArcGISMap
import com.arcgismaps.mapping.PortalItem
import com.arcgismaps.portal.Portal
import com.arcgismaps.tasks.offlinemaptask.DownloadPreplannedOfflineMapJob
import com.arcgismaps.tasks.offlinemaptask.GenerateOfflineMapJob
import com.arcgismaps.tasks.offlinemaptask.OfflineMapTask
import com.arcgismaps.tasks.offlinemaptask.PreplannedMapArea
import com.arcgismaps.tasks.offlinemaptask.PreplannedUpdateMode
import com.esri.arcgismaps.sample.downloadpreplannedmaparea.R
import com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
class DownloadPreplannedMapAreaViewModel(application: Application) : AndroidViewModel(application) {
// The directory where the offline map will be saved
private val offlineMapPath by lazy {
application.getExternalFilesDir(null)?.path.toString() + File.separator + application.getString(
R.string.download_preplanned_map_area_app_name
)
}
// Create a portal to ArcGIS Online
private val portal = Portal("https://www.arcgis.com")
// create a portal item using the portal and the item id of a map service
private val portalItem = PortalItem(portal, "acc027394bc84c2fb04d1ed317aac674")
private val offlineMapTask = OfflineMapTask(portalItem)
// A list of preplanned map areas populated by the offline map task
private var preplannedMapAreas = mutableListOf<PreplannedMapArea>()
// Keep a hash map of downloaded maps
private var downloadedMapAreas: HashMap<String, ArcGISMap> = hashMapOf()
// An online map created from the portal item
private val onlineMap = ArcGISMap(portalItem)
// The current map shown in the map view
var currentMap by mutableStateOf(onlineMap)
val preplannedMapAreaInfoList = mutableStateListOf<PreplannedMapAreaInfo>()
// Defined to send messages related to offlineMapJob
val snackbarHostState = SnackbarHostState()
// Create a message dialog view model for handling error messages
val messageDialogVM = MessageDialogViewModel()
init {
with(viewModelScope) {
launch(Dispatchers.IO) {
offlineMapTask.getPreplannedMapAreas().onSuccess {
// Keep a list of all preplanned map areas
preplannedMapAreas.addAll(it)
// Add all of the preplanned map areas name and download status to a list
it.forEachIndexed { index, preplannedMapArea ->
preplannedMapAreaInfoList.add(
PreplannedMapAreaInfo(
index = index,
name = preplannedMapArea.portalItem.title,
progress = 0f,
isDownloaded = false
)
)
}
}
}
launch(Dispatchers.Main) {
onlineMap.load().onFailure { messageDialogVM.showMessageDialog(it) }
}
}
}
/**
* Show the original map from the portal item.
*/
fun showOnlineMap() {
currentMap = onlineMap
}
/**
* Download or show the already downloaded preplanned map area.
*/
fun downloadOrShowOfflineMap(preplannedMapAreaInfo: PreplannedMapAreaInfo) {
if (preplannedMapAreaInfo.isDownloaded) {
showOfflineMap(preplannedMapAreaInfo)
} else {
downloadOfflineMap(preplannedMapAreaInfo)
}
}
/**
* Show the offline map of the given preplanned map area name.
*/
private fun showOfflineMap(preplannedMapAreaInfo: PreplannedMapAreaInfo) {
downloadedMapAreas[preplannedMapAreaInfo.name]?.let { selectedArcGISMap ->
currentMap = selectedArcGISMap
}
}
/**
* Use the [OfflineMapTask] to create [DownloadPreplannedOfflineMapParameters] for the given [PreplannedMapArea].
* Then use the task to create a [DownloadPreplannedOfflineMapJob] to download the preplanned offline map.
*/
private fun downloadOfflineMap(preplannedMapAreaInfo: PreplannedMapAreaInfo) {
viewModelScope.launch(Dispatchers.IO) {
// Get the area of interest for the preplanned map area
preplannedMapAreas.find { it.portalItem.title == preplannedMapAreaInfo.name }?.let { preplannedMapArea ->
// Create default download parameters from the offline map task
offlineMapTask.createDefaultDownloadPreplannedOfflineMapParameters(preplannedMapArea).onSuccess {
// Set the update mode to receive no updates
it.updateMode = PreplannedUpdateMode.NoUpdates
// Define the path where the map will be saved
val downloadDirectoryPath = offlineMapPath + File.separator + preplannedMapAreaInfo.name
File(downloadDirectoryPath).mkdirs()
// Create a job to download the preplanned offline map
val downloadPreplannedOfflineMapJob = offlineMapTask.createDownloadPreplannedOfflineMapJob(
parameters = it, downloadDirectoryPath = downloadDirectoryPath
)
runOfflineMapJob(downloadPreplannedOfflineMapJob, preplannedMapAreaInfo)
}
}
}
}
/**
* Starts the [GenerateOfflineMapJob], shows the progress dialog and displays the result offline map to the MapView.
*/
private fun runOfflineMapJob(
downloadPreplannedOfflineMapJob: DownloadPreplannedOfflineMapJob,
preplannedMapAreaInfo: PreplannedMapAreaInfo
) {
with(viewModelScope) {
// Collect the job's progress flow
val progressCoroutine = launch(Dispatchers.IO) {
downloadPreplannedOfflineMapJob.progress.collect { progress ->
// Update the UI to this preplanned map area's downloads progress
preplannedMapAreaInfoList[preplannedMapAreaInfo.index] =
preplannedMapAreaInfo.copy(progress = progress.toFloat() / 100)
}
}
// Start the job and handle the result
launch(Dispatchers.IO) {
// Start the job and wait for Job result
downloadPreplannedOfflineMapJob.start()
downloadPreplannedOfflineMapJob.result().onSuccess { downloadedMap ->
// Set the offline map result as the displayed
currentMap = downloadedMap.offlineMap
// Update the UI to show the map as downloaded
val index = preplannedMapAreaInfoList.indexOf(preplannedMapAreaInfo)
preplannedMapAreaInfoList[preplannedMapAreaInfo.index] =
preplannedMapAreaInfo.copy(isDownloaded = true)
// Add the downloaded map to the list of downloaded maps
downloadedMapAreas[preplannedMapAreaInfo.name] = downloadedMap.offlineMap
// Show user where map was locally saved
snackbarHostState.showSnackbar(message = "Map saved at: " + downloadPreplannedOfflineMapJob.downloadDirectoryPath)
}.onFailure { messageDialogVM.showMessageDialog(it) }
// Cancel the coroutine handling progress reporting
progressCoroutine.cancel()
}
}
}
}
data class PreplannedMapAreaInfo(val index: Int, val name: String, val progress: Float, val isDownloaded: Boolean)