Add feature layers from various data sources.

Use case
Feature layers, like other types of layers, visually represent data on a map or scene. For feature layers specifically, the data comes from a feature table or a feature service.
Feature services allow vector GIS data to be shared with different clients, enabling them to view, query, and edit individual features. These services can be accessed using both online and offline methods.
How to use the sample
Tap the floating action button on the bottom right and select from the various sources to add feature layers to the map. Pan and zoom to explore the feature layers
How it works
- Create a new
ArcGISMapobject and pass it to theMapViewcomposable’sarcGISMapparameter. - Load a feature layer with a service feature table:
- Create a
ServiceFeatureTableusing the service URL. - Create a
FeatureLayerfrom the feature table.
- Create a
- Load a feature layer with a portal item:
- Create a
PortalItemwith the portal and item ID. - Create a
FeatureLayerfrom the portal item and ID.
- Create a
- Load a feature layer with a geodatabase:
- Initialize and load a
Geodatabseusing a file name. - Retrieve the feature table from the geodatabase with the feature table’s name.
- Create a
FeatureLayerfrom the feature table.
- Initialize and load a
- Load a feature layer with a geopackage:
- Initialize and load the geopackage using a file name.
- Get the first
GeoPackageFeatureTablefrom thegeoPackageFeatureTableslist. - Create a
FeatureLayerfrom the feature table.
- Load a feature layer with a shapefile:
- Create a
ShapefileFeatureTableusing the file path. - Create and load a
FeatureLayerfrom the table.
- Create a
- In all cases, the selected feature layer is added to the map’s operational layers.
Relevant API
- FeatureLayer
- Geodatabase
- GeoPackageFeatureTable
- PortalItem
- ServiceFeatureTable
- ShapefileFeatureTable
About the data
This sample uses the Naperville damage assessment service, Trees of Portland portal item, Los Angeles Trailheads geodatabase, Aurora, Colorado GeoPackage, and Scottish Wildlife Trust Reserves Shapefile.
The Scottish Wildlife Trust shapefile data is provided from Scottish Wildlife Trust under CC-BY license. Data Copyright Scottish Wildlife Trust (2022).
Tags
feature, geodatabase, geopackage, layers, portal, service, shapefile, table
Sample code
/* Copyright 2026 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.addfeaturelayers.components
import android.app.Applicationimport androidx.compose.runtime.getValueimport androidx.compose.runtime.mutableStateOfimport androidx.compose.runtime.setValueimport androidx.lifecycle.AndroidViewModelimport androidx.lifecycle.viewModelScopeimport com.arcgismaps.data.GeoPackageimport com.arcgismaps.data.Geodatabaseimport com.arcgismaps.data.ServiceFeatureTableimport com.arcgismaps.data.ShapefileFeatureTableimport com.arcgismaps.mapping.ArcGISMapimport com.arcgismaps.mapping.BasemapStyleimport com.arcgismaps.mapping.PortalItemimport com.arcgismaps.mapping.Viewpointimport com.arcgismaps.mapping.layers.FeatureLayerimport com.arcgismaps.portal.Portalimport com.arcgismaps.toolkit.geoviewcompose.MapViewProxyimport com.esri.arcgismaps.sample.addfeaturelayers.Rimport com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModelimport kotlinx.coroutines.launchimport java.io.File/*** ViewModel that loads a [FeatureLayer] from various sources and adds it to the ArcGISMap. */class AddFeatureLayersViewModel(val app: Application) : AndroidViewModel(app) { val arcGISMap = ArcGISMap(BasemapStyle.ArcGISTopographic)
val mapViewProxy = MapViewProxy()
private val provisionPath: String by lazy { app.getExternalFilesDir(null)?.path.toString() + File.separator + app.getString(R.string.add_feature_layers_app_name) }
// Keep track of the current selected feature layer source var selectedFeatureLayerSource by mutableStateOf<FeatureLayerSource>(FeatureLayerSource.SERVICE_FEATURE_TABLE) private set
// Create a message dialog view model for handling error messages val messageDialogVM = MessageDialogViewModel()
init { viewModelScope.launch { arcGISMap.load().onFailure { messageDialogVM.showMessageDialog(it) } onFeatureLayerSourceSelected(source = selectedFeatureLayerSource) } }
/** * Update the current [selectedFeatureLayerSource] and to load the corresponding * [FeatureLayerSource]. */ fun onFeatureLayerSourceSelected(source: FeatureLayerSource) { if (source == selectedFeatureLayerSource && arcGISMap.operationalLayers.isNotEmpty()) return selectedFeatureLayerSource = source
viewModelScope.launch { when(selectedFeatureLayerSource) { FeatureLayerSource.SERVICE_FEATURE_TABLE -> loadFeatureServiceURL() FeatureLayerSource.PORTAL_ITEM -> loadPortalItem() FeatureLayerSource.GEODATABASE -> loadGeodatabase() FeatureLayerSource.GEOPACKAGE -> loadGeopackage() FeatureLayerSource.SHAPEFILE -> loadShapefile() } } }
/** * Replace the map's operational layers with the loaded [layer] and animate to the given * [viewpoint]. */ private suspend fun setFeatureLayer(layer: FeatureLayer, viewpoint: Viewpoint) { arcGISMap.operationalLayers.apply { clear() add(layer) } mapViewProxy.setViewpointAnimated(viewpoint) }
/** * Load a feature layer using a [ServiceFeatureTable] URL. */ private suspend fun loadFeatureServiceURL() { // Create a service feature table from a given URI val serviceFeatureTable = ServiceFeatureTable( uri = app.getString(R.string.add_feature_layers_sample_service_url) )
// Load to create a feature layer and a viewpoint to set on map serviceFeatureTable.load().onSuccess { // Create a feature layer with the feature table val featureLayer = FeatureLayer.createWithFeatureTable(serviceFeatureTable) //Viewpoint centered on Naperville, IL val viewpoint = Viewpoint(latitude = 41.70, longitude = -88.20, scale = 120000.0) setFeatureLayer(featureLayer, viewpoint) } }
/** * Load a feature layer from an online [PortalItem]. */ private suspend fun loadPortalItem() { //Connect to the public ArcGIS online portal val portalItem = PortalItem( portal = Portal.arcGISOnline(connection = Portal.Connection.Anonymous), itemId = "1759fd3e8a324358a0c58d9a687a8578" )
// Load to create a feature layer and a viewpoint to set on map portalItem.load().onSuccess { val featureLayer = FeatureLayer.createWithItem(portalItem) // Viewpoint centered on Portland, OR val viewpoint = Viewpoint(latitude = 45.5266, longitude = -122.6219, scale = 2500.0) setFeatureLayer(featureLayer, viewpoint) }.onFailure { messageDialogVM.showMessageDialog(it) } }
/** * Load a feature layer from a local mobile [Geodatabase] file. */ private suspend fun loadGeodatabase() { // Locate the .geodatabase file val geodatabaseFile = File( provisionPath, "LA_Trails.geodatabase" )
// Create the geodatabase from the local path val geodatabase = Geodatabase(geodatabaseFile.path)
// Load to create a feature layer and a viewpoint to set on map geodatabase.load().onSuccess { // Get the "Trailheads" feature table from the geodatabase val geodatabaseFeatureTable = geodatabase.getFeatureTable(tableName = "Trailheads") ?: return@onSuccess messageDialogVM.showMessageDialog("Trailheads feature table name not found in geodatabase") val featureLayer = FeatureLayer.createWithFeatureTable(geodatabaseFeatureTable) // Viewpoint centered on Malibu, CA val viewpoint = Viewpoint(latitude = 34.0772, longitude = -118.7989, scale = 600000.0) setFeatureLayer(featureLayer, viewpoint) }.onFailure { messageDialogVM.showMessageDialog(it) } }
/** * Load a feature layer from a local [GeoPackage] file. */ private suspend fun loadGeopackage() { // Locate the .gpkg file val geopackageFile = File(provisionPath, "AuroraCO.gpkg") val geoPackage = GeoPackage(geopackageFile.path)
// Load to create a feature layer and a viewpoint to set on map geoPackage.load().onSuccess { val geoPackageFeatureTable = geoPackage.geoPackageFeatureTables.first() val featureLayer = FeatureLayer.createWithFeatureTable(geoPackageFeatureTable) // Viewpoint centered on Denver, CO val viewpoint = Viewpoint(latitude = 39.7294, longitude = -104.8319, scale = 500000.0) setFeatureLayer(featureLayer, viewpoint) }.onFailure { messageDialogVM.showMessageDialog(it) } }
/** * Load a feature layer from a local shapefile. */ private suspend fun loadShapefile() { val shapefileFile = File( provisionPath, "ScottishWildlifeTrust_ReserveBoundaries_20201102.shp" ) val shapefileFeatureTable = ShapefileFeatureTable(shapefileFile.path)
// Load to create a feature layer and a viewpoint to set on map shapefileFeatureTable.load().onSuccess { val featureLayer = FeatureLayer.createWithFeatureTable(shapefileFeatureTable) val viewpoint = Viewpoint( latitude = 56.641344, longitude = -3.889066, scale = 6000000.0 ) setFeatureLayer(featureLayer, viewpoint) }.onFailure { messageDialogVM.showMessageDialog(it) } }
/** * UI selector options to drive updates [onFeatureLayerSourceSelected] in the view model. */ enum class FeatureLayerSource(val label: String) { SERVICE_FEATURE_TABLE(label = "Service feature table"), PORTAL_ITEM(label = "Portal item"), GEODATABASE(label = "Geodatabase"), GEOPACKAGE(label = "GeoPackage"), SHAPEFILE(label = "Shapefile") }}/* Copyright 2026 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.addfeaturelayers
import android.os.Bundleimport androidx.activity.ComponentActivityimport androidx.activity.compose.setContentimport androidx.activity.enableEdgeToEdgeimport androidx.compose.material3.MaterialThemeimport androidx.compose.material3.Surfaceimport com.esri.arcgismaps.sample.sampleslib.theme.SampleAppThemeimport com.esri.arcgismaps.sample.addfeaturelayers.screens.AddFeatureLayersScreen
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { SampleAppTheme { Surface(color = MaterialTheme.colorScheme.background) { AddFeatureLayersScreen() } } } }}/* Copyright 2022 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.addfeaturelayers
import android.content.Intentimport android.os.Bundleimport com.esri.arcgismaps.sample.sampleslib.DownloaderActivity
class DownloadActivity : DownloaderActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Download the mobile map package, shapefile and geopackage needed for this sample // and then launch MainActivity once all downloads complete downloadAndStartSample( Intent(this, MainActivity::class.java), // get the app name of the sample getString(R.string.add_feature_layers_app_name), listOf( // ArcGIS Portal item containing the .mmpk mobile map package "https://www.arcgis.com/home/item.html?id=cb1b20748a9f4d128dad8a87244e3e37", // ArcGIS Portal item containing shapefiles of Scottish Wildlife Trust reserve boundaries "https://www.arcgis.com/home/item.html?id=15a7cbd3af1e47cfa5d2c6b93dc44fc2", // GeoPackage AuroraCO.gpkg with datasets that cover Aurora Colorado "https://www.arcgis.com/home/item.html?id=68ec42517cdd439e81b036210483e8e7" ) ) }}/* Copyright 2026 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.addfeaturelayers.screens
import android.content.res.Configurationimport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.paddingimport androidx.compose.material.icons.Iconsimport androidx.compose.material.icons.filled.Addimport androidx.compose.material3.FloatingActionButtonimport androidx.compose.material3.Iconimport androidx.compose.material3.Scaffoldimport androidx.compose.runtime.Composableimport androidx.compose.runtime.getValueimport androidx.compose.runtime.mutableStateOfimport androidx.compose.runtime.rememberimport androidx.compose.runtime.setValueimport androidx.compose.ui.Modifierimport androidx.compose.ui.res.stringResourceimport androidx.compose.ui.tooling.preview.Previewimport androidx.compose.ui.unit.dpimport androidx.lifecycle.viewmodel.compose.viewModelimport com.arcgismaps.toolkit.geoviewcompose.MapViewimport com.esri.arcgismaps.sample.addfeaturelayers.Rimport com.esri.arcgismaps.sample.addfeaturelayers.components.AddFeatureLayersViewModelimport com.esri.arcgismaps.sample.sampleslib.components.BottomSheetimport com.esri.arcgismaps.sample.sampleslib.components.DropDownMenuBoximport com.esri.arcgismaps.sample.sampleslib.components.MessageDialogimport com.esri.arcgismaps.sample.sampleslib.components.SamplePreviewSurfaceimport com.esri.arcgismaps.sample.sampleslib.components.SampleTopAppBar
/** * Main screen layout for the sample app */@Composablefun AddFeatureLayersScreen( mapViewModel: AddFeatureLayersViewModel = viewModel()) { // Track the visibility of the bottom sheet var isBottomSheetVisible by remember { mutableStateOf(true) }
Scaffold( topBar = { SampleTopAppBar(title = stringResource(R.string.add_feature_layers_app_name)) }, floatingActionButton = { //Only show FAB when the bottom sheet is closed if (!isBottomSheetVisible) { FloatingActionButton( modifier = Modifier.padding(bottom = 36.dp, end = 12.dp), onClick = { isBottomSheetVisible = true } ) { Icon(Icons.Filled.Add, contentDescription = "Show options") } } }, content = { innerPadding -> MapView( modifier = Modifier .fillMaxSize() .padding(innerPadding), arcGISMap = mapViewModel.arcGISMap, mapViewProxy = mapViewModel.mapViewProxy, onDown = { isBottomSheetVisible = false } )
BottomSheet( isVisible = isBottomSheetVisible, sheetTitle = "Feature layer source", onDismissRequest = { isBottomSheetVisible = false } ) { FeatureLayerSourceMenuSelector( selectedFeatureLayerSource = mapViewModel.selectedFeatureLayerSource, // Forward the tapped dropdown index to the ViewModel onFeatureLayerSourceSelected = mapViewModel::onFeatureLayerSourceSelected ) }
mapViewModel.messageDialogVM.apply { if (dialogStatus) { MessageDialog( title = messageTitle, description = messageDescription, onDismissRequest = ::dismissDialog ) } } } )}
@Composablefun FeatureLayerSourceMenuSelector( selectedFeatureLayerSource: AddFeatureLayersViewModel.FeatureLayerSource, onFeatureLayerSourceSelected: (AddFeatureLayersViewModel.FeatureLayerSource) -> Unit = {}) { DropDownMenuBox( textFieldValue = selectedFeatureLayerSource.label, textFieldLabel = "Select a feature layer source", // Populate the selector to map a list of each entry from the view model enum. dropDownItemList = AddFeatureLayersViewModel.FeatureLayerSource.entries.map { it.label }, onIndexSelected = { index -> onFeatureLayerSourceSelected(AddFeatureLayersViewModel.FeatureLayerSource.entries[index]) } )
}
@Preview(showBackground = true)@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true)@Composablefun BottomSheetPreview() { SamplePreviewSurface { BottomSheet( isVisible = true, sheetTitle = "Feature layer source", ) { FeatureLayerSourceMenuSelector(AddFeatureLayersViewModel.FeatureLayerSource.GEOPACKAGE) } }}