Repository: Esri/arcgis-maps-sdk-kotlin-samples
Pull request #331: New sample: Manage features
Author: login: TADraeseke
Labels: name: New sample
description: New Kotlin sample using ArcGIS Maps SDK
Created: 2025-03-14T17:18:14Z | Merged: 2025-03-19T21:42:35Z

## Description

PR to add a new Kotlin sample Manage features in Edit and manage data category.


### Testing:

- [x] vTest: https://runtime-kotlin.esri.com/view/all/job/vtest/job/sampleviewer/88/


Diff:
diff --git a/.gitignore b/.gitignore
index 7528ac37f..1a8e1e708 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,6 +15,7 @@ build/
 *.classpath
 .settings/
 .vscode
+*.salive
 
 # OS generated files #
 .DS_Store
diff --git a/.kotlin/sessions/kotlin-compiler-17391608372419177291.salive b/.kotlin/sessions/kotlin-compiler-17391608372419177291.salive
deleted file mode 100644
index e69de29bb..000000000
diff --git a/samples/manage-features/README.md b/samples/manage-features/README.md
new file mode 100644
index 000000000..d820d1e61
--- /dev/null
+++ b/samples/manage-features/README.md
@@ -0,0 +1,42 @@
+# Manage features
+
+Create, update, and delete features to manage a feature layer.
+
+![Screenshot of manage features](manage-features.png)
+
+## Use case
+
+An end-user performing a survey may want to manage features on the map in various ways during the course of their work.
+
+## How to use the sample
+
+Pick an operation, then tap on the map to perform the operation at that location. Available feature management operations include: "Create feature", "Delete feature", "Update attribute", and "Update geometry".
+
+## How it works
+
+1. Create a `ServiceGeodatabase` from a URL.
+2. Get a `ServiceFeatureTable` from the `ServiceGeodatabase`.
+3. Create a `FeatureLayer` derived from the `ServiceFeatureTable` instance.
+4. Apply the feature management operation upon tapping the map.
+    - Create features: create a `Feature` with attributes and a location using the `ServiceFeatureTable`.
+    - Delete features: delete the selected `Feature` from the `FeatureTable`.
+    - Update attribute: update the attribute of the selected `Feature`.
+    - Update geometry: update the geometry of the selected `Feature`.
+5. Update the `FeatureTable` locally.
+6. Update the `ServiceGeodatabase` of the `ServiceFeatureTable` by calling `applyEdits()`. This pushes the changes to the server.
+
+## Relevant API
+
+* Feature
+* FeatureEditResult
+* FeatureLayer
+* ServiceFeatureTable
+* ServiceGeodatabase
+
+## Additional information
+
+When editing feature tables that are subject to database behavior (operations on one table affecting another table), it's recommended to call these methods (apply or undo edits) on the `ServiceGeodatabase` object rather than on the `ServiceFeatureTable` object. Using the `ServiceGeodatabase` object to call these operations will prevent possible data inconsistencies and ensure transactional integrity so that all changes can be committed or rolled back.
+
+## Tags
+
+amend, attribute, create, delete, deletion, details, edit, editing, feature, feature layer, feature table, geodatabase, information, moving, online service, service, update, updating, value
diff --git a/samples/manage-features/README.metadata.json b/samples/manage-features/README.metadata.json
new file mode 100644
index 000000000..c54b956aa
--- /dev/null
+++ b/samples/manage-features/README.metadata.json
@@ -0,0 +1,50 @@
+{
+    "category": "Edit and Manage Data",
+    "description": "Create, update, and delete features to manage a feature layer.",
+    "formal_name": "ManageFeatures",
+    "ignore": false,
+    "images": [
+        "manage-features.png"
+    ],
+    "keywords": [
+        "amend",
+        "attribute",
+        "create",
+        "delete",
+        "deletion",
+        "details",
+        "edit",
+        "editing",
+        "feature",
+        "feature layer",
+        "feature table",
+        "geodatabase",
+        "information",
+        "moving",
+        "online service",
+        "service",
+        "update",
+        "updating",
+        "value",
+        "Feature",
+        "FeatureEditResult",
+        "FeatureLayer",
+        "ServiceFeatureTable",
+        "ServiceGeodatabase"
+    ],
+    "language": "kotlin",
+    "redirect_from": "",
+    "relevant_apis": [
+        "Feature",
+        "FeatureEditResult",
+        "FeatureLayer",
+        "ServiceFeatureTable",
+        "ServiceGeodatabase"
+    ],
+    "snippets": [
+        "src/main/java/com/esri/arcgismaps/sample/managefeatures/components/ManageFeaturesViewModel.kt",
+        "src/main/java/com/esri/arcgismaps/sample/managefeatures/MainActivity.kt",
+        "src/main/java/com/esri/arcgismaps/sample/managefeatures/screens/ManageFeaturesScreen.kt"
+    ],
+    "title": "Manage features"
+}
diff --git a/samples/manage-features/build.gradle.kts b/samples/manage-features/build.gradle.kts
new file mode 100644
index 000000000..7c48d294f
--- /dev/null
+++ b/samples/manage-features/build.gradle.kts
@@ -0,0 +1,22 @@
+plugins {
+    alias(libs.plugins.arcgismaps.android.library)
+    alias(libs.plugins.arcgismaps.android.library.compose)
+    alias(libs.plugins.arcgismaps.kotlin.sample)
+    alias(libs.plugins.gradle.secrets)
+}
+
+secrets {
+    // this file doesn't contain secrets, it just provides defaults which can be committed into git.
+    defaultPropertiesFileName = "secrets.defaults.properties"
+}
+
+android {
+    namespace = "com.esri.arcgismaps.sample.managefeatures"
+    buildFeatures {
+        buildConfig = true
+    }
+}
+
+dependencies {
+    // Only module specific dependencies needed here
+}
diff --git a/samples/manage-features/manage-features.png b/samples/manage-features/manage-features.png
new file mode 100644
index 000000000..07e595c46
Binary files /dev/null and b/samples/manage-features/manage-features.png differ
diff --git a/samples/manage-features/src/main/AndroidManifest.xml b/samples/manage-features/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..4035bbdae
--- /dev/null
+++ b/samples/manage-features/src/main/AndroidManifest.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <uses-permission android:name="android.permission.INTERNET" />
+
+    <application><activity
+            android:exported="true"
+            android:name=".MainActivity"
+            android:label="@string/manage_features_app_name">
+            
+        </activity>
+    </application>
+
+</manifest>
diff --git a/samples/manage-features/src/main/java/com/esri/arcgismaps/sample/managefeatures/MainActivity.kt b/samples/manage-features/src/main/java/com/esri/arcgismaps/sample/managefeatures/MainActivity.kt
new file mode 100644
index 000000000..e63896c5c
--- /dev/null
+++ b/samples/manage-features/src/main/java/com/esri/arcgismaps/sample/managefeatures/MainActivity.kt
@@ -0,0 +1,53 @@
+/* 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.managefeatures
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import com.arcgismaps.ApiKey
+import com.arcgismaps.ArcGISEnvironment
+import com.esri.arcgismaps.sample.sampleslib.theme.SampleAppTheme
+import com.esri.arcgismaps.sample.managefeatures.screens.ManageFeaturesScreen
+
+class MainActivity : ComponentActivity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        // authentication with an API key or named user is
+        // required to access basemaps and other location services
+        ArcGISEnvironment.apiKey = ApiKey.create(BuildConfig.ACCESS_TOKEN)
+
+        setContent {
+            SampleAppTheme {
+                ManageFeaturesApp()
+            }
+        }
+    }
+
+    @Composable
+    private fun ManageFeaturesApp() {
+        Surface(color = MaterialTheme.colorScheme.background) {
+            ManageFeaturesScreen(
+                sampleName = getString(R.string.manage_features_app_name)
+            )
+        }
+    }
+}
diff --git a/samples/manage-features/src/main/java/com/esri/arcgismaps/sample/managefeatures/components/ManageFeaturesViewModel.kt b/samples/manage-features/src/main/java/com/esri/arcgismaps/sample/managefeatures/components/ManageFeaturesViewModel.kt
new file mode 100644
index 000000000..c8e44e6e5
--- /dev/null
+++ b/samples/manage-features/src/main/java/com/esri/arcgismaps/sample/managefeatures/components/ManageFeaturesViewModel.kt
@@ -0,0 +1,332 @@
+/* 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.managefeatures.components
+
+import android.app.Application
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.viewModelScope
+import com.arcgismaps.LoadStatus
+import com.arcgismaps.data.ArcGISFeature
+import com.arcgismaps.data.CodedValueDomain
+import com.arcgismaps.data.ServiceFeatureTable
+import com.arcgismaps.data.ServiceGeodatabase
+import com.arcgismaps.geometry.GeometryEngine
+import com.arcgismaps.geometry.Point
+import com.arcgismaps.geometry.SpatialReference
+import com.arcgismaps.mapping.ArcGISMap
+import com.arcgismaps.mapping.BasemapStyle
+import com.arcgismaps.mapping.Viewpoint
+import com.arcgismaps.mapping.layers.FeatureLayer
+import com.arcgismaps.mapping.view.ScreenCoordinate
+import com.arcgismaps.mapping.view.SingleTapConfirmedEvent
+import com.arcgismaps.toolkit.geoviewcompose.MapViewProxy
+import com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModel
+import kotlinx.coroutines.launch
+
+
+class ManageFeaturesViewModel(application: Application) : AndroidViewModel(application) {
+
+    val mapViewProxy = MapViewProxy()
+
+    // Hold a reference to the feature table.
+    private var damageFeatureTable: ServiceFeatureTable? = null
+
+    // Hold a reference to the feature layer.
+    private var damageLayer: FeatureLayer? = null
+
+    // Hold a reference to the selected feature.
+    var selectedFeature: ArcGISFeature? by mutableStateOf(null)
+
+    // The current feature operation to perform.
+    var currentFeatureOperation by mutableStateOf(FeatureOperationType.CREATE)
+
+    // The list of damage types to update the feature attribute.
+    var damageTypeList: List<String> = mutableListOf()
+
+    var currentDamageType by mutableStateOf("")
+
+    // Create the map with streets basemap.
+    val arcGISMap = ArcGISMap(BasemapStyle.ArcGISStreets).apply {
+        // Zoom to the United States.
+        initialViewpoint = Viewpoint(
+            Point(x = -10800000.0, y = 4500000.0, spatialReference = SpatialReference.webMercator()), scale = 3e7
+        )
+    }
+
+    // Create a snackbar message to display the result of feature operations.
+    var snackBarMessage: String by mutableStateOf("")
+
+    // Create a message dialog view model for handling error messages
+    val messageDialogVM = MessageDialogViewModel()
+
+    init {
+        viewModelScope.launch {
+            // Create a service geodatabase from the feature service.
+            val serviceGeodatabase =
+                ServiceGeodatabase("https://sampleserver6.arcgisonline.com/arcgis/rest/services/DamageAssessment/FeatureServer/0")
+            serviceGeodatabase.load().onSuccess {
+                // Get the feature table from the service geodatabase referencing the Damage Assessment feature service.
+                serviceGeodatabase.getTable(0)?.let { serviceFeatureTable ->
+                    // Load the feature table to get the coded value domain for the attribute field.
+                    serviceFeatureTable.load().onSuccess {
+                        // Hold a reference to the feature table.
+                        damageFeatureTable = serviceFeatureTable
+                        // Get the field from the feature table that will be updated.
+                        val typeDamageField = serviceFeatureTable.fields.first { it.name == "typdamage" }
+                        // Get the coded value domain for the field.
+                        val attributeDomain = typeDamageField.domain as CodedValueDomain
+                        // Add the damage types to the list.
+                        attributeDomain.codedValues.forEach {
+                            damageTypeList += it.name
+                        }
+                        // Create a feature layer to visualize the features in the table.
+                        FeatureLayer.createWithFeatureTable(serviceFeatureTable).let { featureLayer ->
+                            // Hold a reference to the feature layer.
+                            damageLayer = featureLayer
+                            // Add it to the map.
+                            arcGISMap.operationalLayers.add(featureLayer)
+                            // Load the map.
+                            arcGISMap.load().onFailure { error ->
+                                messageDialogVM.showMessageDialog(
+                                    "Failed to load map", error.message.toString()
+                                )
+                            }
+                        }
+                    }.onFailure { error ->
+                        messageDialogVM.showMessageDialog(
+                            "Failed to load feature table", error.message.toString()
+                        )
+                    }
+                }
+            }.onFailure { error ->
+                // Show the message dialog and pass the error message to be displayed in the dialog.
+                messageDialogVM.showMessageDialog(
+                    "Failed to load service geodatabase", error.message.toString()
+                )
+            }
+        }
+    }
+
+    /**
+     * Directs the behaviour of tap's on the map view.
+     */
+    fun onTap(singleTapConfirmedEvent: SingleTapConfirmedEvent) {
+        if (damageLayer?.loadStatus?.value != LoadStatus.Loaded) {
+            snackBarMessage = "Layer not loaded!"
+            return
+        }
+        when (currentFeatureOperation) {
+            FeatureOperationType.CREATE -> createFeatureAt(singleTapConfirmedEvent.screenCoordinate)
+            FeatureOperationType.DELETE -> deleteFeatureAt(singleTapConfirmedEvent.screenCoordinate)
+            FeatureOperationType.UPDATE_ATTRIBUTE -> selectFeatureForAttributeEditAt(singleTapConfirmedEvent.screenCoordinate)
+            FeatureOperationType.UPDATE_GEOMETRY -> updateFeatureGeometryAt(singleTapConfirmedEvent.screenCoordinate)
+        }
+    }
+
+    /**
+     * Set the current feature operation to perform based on the selected index from the dropdown. Also, reset feature
+     * selection.
+     */
+    fun onFeatureOperationSelected(index: Int) {
+        currentFeatureOperation = FeatureOperationType.entries[index]
+        // Reset the selected feature when the operation changes.
+        damageLayer?.clearSelection()
+        selectedFeature = null
+    }
+
+    /**
+     * Create a new feature at the tapped location with some default attributes
+     */
+    private fun createFeatureAt(screenCoordinate: ScreenCoordinate) {
+        // Create the feature.
+        val feature = damageFeatureTable?.createFeature()?.apply {
+            // Get the normalized geometry for the tapped location and use it as the feature's geometry.
+            mapViewProxy.screenToLocationOrNull(screenCoordinate)?.let { mapPoint ->
+                geometry = GeometryEngine.normalizeCentralMeridian(mapPoint)
+                // Set feature attributes.
+                attributes["typdamage"] = "Minor"
+                attributes["primcause"] = "Earthquake"
+            }
+        }
+        feature?.let {
+            viewModelScope.launch {
+                // Add the feature to the table.
+                damageFeatureTable?.addFeature(it)
+                // Apply the edits to the service on the service geodatabase.
+                damageFeatureTable?.serviceGeodatabase?.applyEdits()
+                // Update the feature to get the updated objectid - a temporary ID is used before the feature is added.
+                it.refresh()
+                // Confirm feature addition.
+                snackBarMessage = "Created feature ${it.attributes["objectid"]}"
+            }
+        }
+    }
+
+    /**
+     * Selects a feature at the tapped location in preparation for deletion.
+     */
+    private fun deleteFeatureAt(screenCoordinate: ScreenCoordinate) {
+        damageLayer?.let { damageLayer ->
+            // Clear any existing selection.
+            damageLayer.clearSelection()
+            selectedFeature = null
+            viewModelScope.launch {
+                // Determine if a user tapped on a feature.
+                mapViewProxy.identify(damageLayer, screenCoordinate, 10.dp).onSuccess { identifyResult ->
+                    selectedFeature = (identifyResult.geoElements.firstOrNull() as? ArcGISFeature)?.also {
+                        damageLayer.selectFeature(it)
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Delete the selected feature from the feature table and service geodatabase.
+     */
+    fun deleteSelectedFeature() {
+        selectedFeature?.let {
+            // Get the feature's object id.
+            val featureId = it.attributes["objectid"] as Long
+            viewModelScope.launch {
+                // Delete the feature from the feature table.
+                damageFeatureTable?.deleteFeature(it)?.onSuccess {
+                    snackBarMessage = "Deleted feature $featureId"
+                    // Apply the edits to the service geodatabase.
+                    damageFeatureTable?.serviceGeodatabase?.applyEdits()
+                    selectedFeature = null
+                }?.onFailure {
+                    snackBarMessage = "Failed to delete feature $featureId"
+                }
+            }
+        }
+    }
+
+    /**
+     * Selects a feature at the tapped location in preparation for attribute editing.
+     */
+    private fun selectFeatureForAttributeEditAt(screenCoordinate: ScreenCoordinate) {
+        damageLayer?.let { damageLayer ->
+            // Clear any existing selection.
+            damageLayer.clearSelection()
+            selectedFeature = null
+            viewModelScope.launch {
+                // Determine if a user tapped on a feature.
+                mapViewProxy.identify(damageLayer, screenCoordinate, 10.dp).onSuccess { identifyResult ->
+                    // Get the identified feature.
+                    val identifiedFeature = identifyResult.geoElements.firstOrNull() as? ArcGISFeature
+                    identifiedFeature?.let {
+                        val currentAttributeValue = it.attributes["typdamage"] as String
+                        currentDamageType = currentAttributeValue
+                        selectedFeature = it.also {
+                            damageLayer.selectFeature(it)
+                        }
+                    } ?: run {
+                        // Reset damage type if no feature identified.
+                        currentDamageType = ""
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Update the attribute value of the selected feature to the new value from the new damage type.
+     */
+    fun onDamageTypeSelected(index: Int) {
+        // Get the new value.
+        currentDamageType = damageTypeList[index]
+        selectedFeature?.let { selectedFeature ->
+            viewModelScope.launch {
+                // Load the feature.
+                selectedFeature.load().onSuccess {
+                    // Update the attribute value.
+                    selectedFeature.attributes["typdamage"] = currentDamageType
+                    // Update the table.
+                    damageFeatureTable?.updateFeature(selectedFeature)
+                    // Update the service on the service geodatabase.
+                    damageFeatureTable?.serviceGeodatabase?.applyEdits()?.onSuccess {
+                        snackBarMessage =
+                            "Updated feature ${selectedFeature.attributes["objectid"]} to $currentDamageType"
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Select a feature, if none is selected. If a feature is selected, update its geometry to the tapped location.
+     */
+    private fun updateFeatureGeometryAt(screenCoordinate: ScreenCoordinate) {
+
+        damageLayer?.let { damageLayer ->
+            when (selectedFeature) {
+                // When no feature is selected.
+                null -> {
+                    viewModelScope.launch {
+                        // Determine if a user tapped on a feature.
+                        mapViewProxy.identify(damageLayer, screenCoordinate, 10.dp).onSuccess { identifyResult ->
+                            // Get the identified feature.
+                            val identifiedFeature = identifyResult.geoElements.firstOrNull() as? ArcGISFeature
+                            identifiedFeature?.let {
+                                selectedFeature = it.also {
+                                    damageLayer.selectFeature(it)
+                                }
+                            }
+                        }
+                    }
+                }
+                // When a feature is selected, update its geometry to the tapped location.
+                else -> {
+                    mapViewProxy.screenToLocationOrNull(screenCoordinate)?.let { mapPoint ->
+                        // Normalize the point - needed when the tapped location is over the international date line.
+                        val destinationPoint = GeometryEngine.normalizeCentralMeridian(mapPoint)
+                        viewModelScope.launch {
+                            selectedFeature?.let { selectedFeature ->
+                                // Load the feature.
+                                selectedFeature.load().onSuccess {
+                                    // Update the geometry of the selected feature.
+                                    selectedFeature.geometry = destinationPoint
+                                    // Apply the edit to the feature table.
+                                    damageFeatureTable?.updateFeature(selectedFeature)
+                                    // Push the update to the service with the service geodatabase.
+                                    damageFeatureTable?.serviceGeodatabase?.applyEdits()?.onSuccess {
+                                        snackBarMessage = "Moved feature ${selectedFeature.attributes["objectid"]}"
+                                    }?.onFailure {
+                                        snackBarMessage =
+                                            "Failed to move feature ${selectedFeature.attributes["objectid"]}"
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+enum class FeatureOperationType(val operationName: String, val instruction: String) {
+    CREATE("Create feature", "Tap on the map to create a new feature."),
+    DELETE("Delete feature", "Select an existing feature to delete it."),
+    UPDATE_ATTRIBUTE("Update attribute", "Select an existing feature to edit its attribute."),
+    UPDATE_GEOMETRY("Update geometry", "Select an existing feature and tap the map to move it to a new position.")
+}
diff --git a/samples/manage-features/src/main/java/com/esri/arcgismaps/sample/managefeatures/screens/ManageFeaturesScreen.kt b/samples/manage-features/src/main/java/com/esri/arcgismaps/sample/managefeatures/screens/ManageFeaturesScreen.kt
new file mode 100644
index 000000000..bfb20cba8
--- /dev/null
+++ b/samples/manage-features/src/main/java/com/esri/arcgismaps/sample/managefeatures/screens/ManageFeaturesScreen.kt
@@ -0,0 +1,162 @@
+/* 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.managefeatures.screens
+
+import androidx.compose.animation.animateContentSize
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Button
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.SnackbarHost
+import androidx.compose.material3.SnackbarHostState
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.arcgismaps.toolkit.geoviewcompose.MapView
+import com.esri.arcgismaps.sample.managefeatures.components.FeatureOperationType
+import com.esri.arcgismaps.sample.managefeatures.components.ManageFeaturesViewModel
+import com.esri.arcgismaps.sample.sampleslib.components.DropDownMenuBox
+import com.esri.arcgismaps.sample.sampleslib.components.MessageDialog
+import com.esri.arcgismaps.sample.sampleslib.components.SampleTopAppBar
+import kotlinx.coroutines.launch
+
+/**
+ * Main screen layout for the sample app
+ */
+@Composable
+fun ManageFeaturesScreen(sampleName: String) {
+    val mapViewModel: ManageFeaturesViewModel = viewModel()
+
+    var featureManagementDropdownIndex by remember { mutableIntStateOf(0) }
+
+    val snackbarHostState = remember { SnackbarHostState() }
+    val coroutineScope = rememberCoroutineScope()
+
+    Scaffold(
+        topBar = { SampleTopAppBar(title = sampleName) },
+        snackbarHost = {
+            SnackbarHost(
+                modifier = Modifier.padding(bottom = 128.dp),
+                hostState = snackbarHostState
+            )
+        },
+        content = {
+            Column(
+                modifier = Modifier
+                    .fillMaxSize()
+                    .padding(it),
+                horizontalAlignment = Alignment.CenterHorizontally,
+            ) {
+                MapView(
+                    modifier = Modifier
+                        .weight(1f),
+                    mapViewProxy = mapViewModel.mapViewProxy,
+                    arcGISMap = mapViewModel.arcGISMap,
+                    onSingleTapConfirmed = mapViewModel::onTap,
+                ) {
+                    mapViewModel.selectedFeature?.let { selectedFeature ->
+                        // Only show the delete button when on the delete feature operation and a feature is selected.
+                        if (mapViewModel.currentFeatureOperation == FeatureOperationType.DELETE) {
+                            Callout(geoElement = selectedFeature) {
+                                Button(onClick = mapViewModel::deleteSelectedFeature) {
+                                    Text(text = "Delete")
+                                }
+                            }
+                        }
+                        // Only show the dropdown for damage type when on the update feature operation.
+                        if (mapViewModel.currentFeatureOperation == FeatureOperationType.UPDATE_ATTRIBUTE) {
+                            Callout(geoElement = selectedFeature) {
+                                DropDownMenuBox(
+                                    modifier = Modifier
+                                        .padding(8.dp),
+                                    textFieldLabel = "Select damage type",
+                                    textFieldValue = mapViewModel.currentDamageType,
+                                    dropDownItemList = mapViewModel.damageTypeList,
+                                    onIndexSelected = { index ->
+                                        if (mapViewModel.selectedFeature != null) {
+                                            mapViewModel.onDamageTypeSelected(index)
+                                        } else {
+                                            coroutineScope.launch {
+                                                snackbarHostState.showSnackbar("Please select a feature to update")
+                                            }
+                                        }
+                                    }
+                                )
+                            }
+                        }
+                    }
+                }
+                // Start of drop down and instruction UI.
+                Row(
+                    modifier = Modifier
+                        .padding(8.dp),
+                    verticalAlignment = Alignment.CenterVertically,
+                ) {
+                    // Show the dropdown for feature management operations.
+                    DropDownMenuBox(
+                        modifier = Modifier.padding(end = 8.dp),
+                        textFieldLabel = "Feature management operation",
+                        textFieldValue = mapViewModel.currentFeatureOperation.operationName,
+                        dropDownItemList = FeatureOperationType.entries.map { entry -> entry.operationName },
+                        onIndexSelected = { index ->
+                            mapViewModel.onFeatureOperationSelected(index)
+                            featureManagementDropdownIndex = index
+                        })
+                }
+                // Show instructions for the current feature operation.
+                Text(
+                    text = FeatureOperationType.entries[featureManagementDropdownIndex].instruction,
+                    textAlign = TextAlign.Center,
+                    style = MaterialTheme.typography.bodyMedium,
+                    modifier = Modifier
+                        .align(Alignment.CenterHorizontally)
+                        .padding(8.dp)
+                        .animateContentSize()
+                )
+            }
+            // Show snack bar messages with information about feature operations.
+            if (mapViewModel.snackBarMessage != "") {
+                LaunchedEffect(mapViewModel.snackBarMessage) {
+                    snackbarHostState.showSnackbar(mapViewModel.snackBarMessage)
+                }
+            }
+            // Show any errors in a message dialog.
+            mapViewModel.messageDialogVM.apply {
+                if (dialogStatus) {
+                    MessageDialog(
+                        title = messageTitle,
+                        description = messageDescription,
+                        onDismissRequest = ::dismissDialog
+                    )
+                }
+            }
+        }
+    )
+}
diff --git a/samples/manage-features/src/main/res/values/strings.xml b/samples/manage-features/src/main/res/values/strings.xml
new file mode 100644
index 000000000..b284e379a
--- /dev/null
+++ b/samples/manage-features/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+    <string name="manage_features_app_name">Manage features</string>
+</resources>


Reviews:
user: login: shubham7109
body: Looks good, just one bug fix and couple code suggestions:  
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-03-17T17:48:22Z
user: login: TADraeseke
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-18T18:35:55Z
user: login: TADraeseke
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-18T19:16:07Z
user: login: shubham7109
body: Thanks for adding in the changes. looks good 👍 
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-18T19:32:21Z
user: login: eri9000
body: Good work,
LGTM 👍🏼 
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-19T20:12:32Z
user: login: TADraeseke
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-19T21:35:43Z
user: login: TADraeseke
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-19T21:38:20Z
user: login: TADraeseke
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-19T21:39:38Z
user: login: TADraeseke
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-19T21:39:53Z

Review comments:
path: .kotlin/sessions/kotlin-compiler-17391608372419177291.salive
user: login: shubham7109
body: This should be deleted. Maybe adding `*.salive` to our gitignore would be helpful. 
created_at: 2025-03-14T17:54:36Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,338 @@
+/* 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.managefeatures.components
+
+import android.app.Application
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.viewModelScope
+import com.arcgismaps.LoadStatus
+import com.arcgismaps.data.ArcGISFeature
+import com.arcgismaps.data.CodedValueDomain
+import com.arcgismaps.data.ServiceFeatureTable
+import com.arcgismaps.data.ServiceGeodatabase
+import com.arcgismaps.geometry.GeometryEngine
+import com.arcgismaps.geometry.Point
+import com.arcgismaps.geometry.SpatialReference
+import com.arcgismaps.mapping.ArcGISMap
+import com.arcgismaps.mapping.BasemapStyle
+import com.arcgismaps.mapping.Viewpoint
+import com.arcgismaps.mapping.layers.FeatureLayer
+import com.arcgismaps.mapping.view.ScreenCoordinate
+import com.arcgismaps.mapping.view.SingleTapConfirmedEvent
+import com.arcgismaps.toolkit.geoviewcompose.MapViewProxy
+import com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModel
+import kotlinx.coroutines.launch
+
+
+class ManageFeaturesViewModel(application: Application) : AndroidViewModel(application) {
+
+    val mapViewProxy = MapViewProxy()
+
+    // Hold a reference to the feature table.
+    private var damageFeatureTable: ServiceFeatureTable? = null
+
+    // Hold a reference to the feature layer.
+    private var damageLayer: FeatureLayer? = null
+
+    // Hold a reference to the selected feature.
+    var selectedFeature: ArcGISFeature? by mutableStateOf(null)
+
+    // The list of feature operations shown in this sample.
+    val manageFeaturesList = listOf("Create feature", "Delete feature", "Update attribute", "Update geometry")
+
+    // The current feature operation to perform.
+    var currentFeatureOperation by mutableStateOf(manageFeaturesList[0])
+
+    // Instructions associated with each feature operation.
+    val instructionsList = listOf(
+        "Tap on the map to create a new feature.",
+        "Select an existing feature to delete it.",
+        "Select an existing feature to edit its attribute.",
+        "Select an existing feature and tap the map to move it to a new position."
+    )
path: samples/manage-features/src/main/java/com/esri/arcgismaps/sample/managefeatures/components/ManageFeaturesViewModel.kt
user: login: shubham7109
body: With changing Compose UI states, I find using a trackable custom enum helpful to define UI screen messages/instructions. The enum type could be a `FeatureOperationType`  e.g: 

```kotlin
enum class FeatureOperationType(name: String, instruction: String) {
    CREATE("Create Feature", "Tap on the map to create a new feature."),
    DELETE("Delete Feature", "Select an existing feature to delete it."),
    UPDATE_ATTRIBUTE("Update Attribute", "Select an existing feature to edit its attribute."),
    UPDATE_GEOMETRY("Update Geometry", "Select an existing feature and tap the map to move it to a new position.")
}
```

And the ViewModel would keep track of the current `FeatureOperationType` which triggers UI updates for the MainScreen: 

```suggestion
    // The current feature operation to perform.
    var currentFeatureOperation by mutableStateOf(FeatureOperationType.CREATE)
```


- This may/may-not be a significant change for the sample, so feel free to take it as only a suggestion. 

created_at: 2025-03-14T20:02:43Z
author_association: COLLABORATOR
path: samples/manage-features/manage-features.png
user: login: shubham7109
body: Should the screenshot be updated with the callout changes?
created_at: 2025-03-17T17:34:36Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,338 @@
+/* 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.managefeatures.components
+
+import android.app.Application
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.viewModelScope
+import com.arcgismaps.LoadStatus
+import com.arcgismaps.data.ArcGISFeature
+import com.arcgismaps.data.CodedValueDomain
+import com.arcgismaps.data.ServiceFeatureTable
+import com.arcgismaps.data.ServiceGeodatabase
+import com.arcgismaps.geometry.GeometryEngine
+import com.arcgismaps.geometry.Point
+import com.arcgismaps.geometry.SpatialReference
+import com.arcgismaps.mapping.ArcGISMap
+import com.arcgismaps.mapping.BasemapStyle
+import com.arcgismaps.mapping.Viewpoint
+import com.arcgismaps.mapping.layers.FeatureLayer
+import com.arcgismaps.mapping.view.ScreenCoordinate
+import com.arcgismaps.mapping.view.SingleTapConfirmedEvent
+import com.arcgismaps.toolkit.geoviewcompose.MapViewProxy
+import com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModel
+import kotlinx.coroutines.launch
+
+
+class ManageFeaturesViewModel(application: Application) : AndroidViewModel(application) {
+
+    val mapViewProxy = MapViewProxy()
+
+    // Hold a reference to the feature table.
+    private var damageFeatureTable: ServiceFeatureTable? = null
+
+    // Hold a reference to the feature layer.
+    private var damageLayer: FeatureLayer? = null
+
+    // Hold a reference to the selected feature.
+    var selectedFeature: ArcGISFeature? by mutableStateOf(null)
+
+    // The list of feature operations shown in this sample.
+    val manageFeaturesList = listOf("Create feature", "Delete feature", "Update attribute", "Update geometry")
+
+    // The current feature operation to perform.
+    var currentFeatureOperation by mutableStateOf(manageFeaturesList[0])
+
+    // Instructions associated with each feature operation.
+    val instructionsList = listOf(
+        "Tap on the map to create a new feature.",
+        "Select an existing feature to delete it.",
+        "Select an existing feature to edit its attribute.",
+        "Select an existing feature and tap the map to move it to a new position."
+    )
+
+    // The list of damage types to update the feature attribute.
+    var damageTypeList: List<String> = mutableListOf()
+
+    var currentDamageType by mutableStateOf("")
+
+    // Create the map with streets basemap.
+    val arcGISMap = ArcGISMap(BasemapStyle.ArcGISStreets).apply {
+        // Zoom to the United States.
+        initialViewpoint = Viewpoint(
+            Point(x = -10800000.0, y = 4500000.0, spatialReference = SpatialReference.webMercator()), scale = 3e7
+        )
+    }
+
+    // Create a snackbar message to display the result of feature operations.
+    var snackBarMessage: String by mutableStateOf("")
+
+    // Create a message dialog view model for handling error messages
+    val messageDialogVM = MessageDialogViewModel()
+
+    init {
+        viewModelScope.launch {
+            // Create a service geodatabase from the feature service.
+            val serviceGeodatabase =
+                ServiceGeodatabase("https://sampleserver6.arcgisonline.com/arcgis/rest/services/DamageAssessment/FeatureServer/0")
+            serviceGeodatabase.load().onSuccess {
+                // Get the feature table from the service geodatabase referencing the Damage Assessment feature service.
+                serviceGeodatabase.getTable(0)?.let { serviceFeatureTable ->
+                    // Load the feature table to get the coded value domain for the attribute field.
+                    serviceFeatureTable.load().onSuccess {
+                        // Hold a reference to the feature table.
+                        damageFeatureTable = serviceFeatureTable
+                        // Get the field from the feature table that will be updated.
+                        val typeDamageField = serviceFeatureTable.fields.first { it.name == "typdamage" }
+                        // Get the coded value domain for the field.
+                        val attributeDomain = typeDamageField.domain as CodedValueDomain
+                        // Add the damage types to the list.
+                        attributeDomain.codedValues.forEach {
+                            damageTypeList += it.name
+                        }
+                        // Create a feature layer to visualize the features in the table.
+                        FeatureLayer.createWithFeatureTable(serviceFeatureTable).let { featureLayer ->
+                            // Hold a reference to the feature layer.
+                            damageLayer = featureLayer
+                            // Add it to the map.
+                            arcGISMap.operationalLayers.add(featureLayer)
+                            // Load the map.
+                            arcGISMap.load().onFailure { error ->
+                                messageDialogVM.showMessageDialog(
+                                    "Failed to load map", error.message.toString()
+                                )
+                            }
+                        }
+                    }.onFailure { error ->
+                        messageDialogVM.showMessageDialog(
+                            "Failed to load feature table", error.message.toString()
+                        )
+                    }
+                }
+            }.onFailure { error ->
+                // Show the message dialog and pass the error message to be displayed in the dialog.
+                messageDialogVM.showMessageDialog(
+                    "Failed to load service geodatabase", error.message.toString()
+                )
+            }
+        }
+    }
+
+    /**
+     * Directs the behaviour of tap's on the map view.
+     */
+    fun onTap(singleTapConfirmedEvent: SingleTapConfirmedEvent) {
+        if (damageLayer?.loadStatus?.value != LoadStatus.Loaded) {
+            snackBarMessage = "Layer not loaded!"
+            return
+        }
+        when (currentFeatureOperation) {
+            manageFeaturesList[0] -> createFeatureAt(singleTapConfirmedEvent.screenCoordinate)
+            manageFeaturesList[1] -> deleteFeatureAt(singleTapConfirmedEvent.screenCoordinate)
+            manageFeaturesList[2] -> selectFeatureForAttributeEditAt(singleTapConfirmedEvent.screenCoordinate)
+            manageFeaturesList[3] -> updateFeatureGeometryAt(singleTapConfirmedEvent.screenCoordinate)
+        }
+    }
+
+    /**
+     * Set the current feature operation to perform based on the selected index from the dropdown. Also, reset feature
+     * selection.
+     */
+    fun onFeatureOperationSelected(index: Int) {
+        currentFeatureOperation = manageFeaturesList[index]
+        // Reset the selected feature when the operation changes.
+        damageLayer?.clearSelection()
+        selectedFeature = null
+    }
+
+    /**
+     * Create a new feature at the tapped location with some default attributes
+     */
+    private fun createFeatureAt(screenCoordinate: ScreenCoordinate) {
+        // Create the feature.
+        val feature = damageFeatureTable?.createFeature()?.apply {
+            // Get the normalized geometry for the tapped location and use it as the feature's geometry.
+            mapViewProxy.screenToLocationOrNull(screenCoordinate)?.let { mapPoint ->
+                geometry = GeometryEngine.normalizeCentralMeridian(mapPoint)
+                // Set feature attributes.
+                attributes["typdamage"] = "Minor"
+                attributes["primcause"] = "Earthquake"
+            }
+        }
+        feature?.let {
+            viewModelScope.launch {
+                // Add the feature to the table.
+                damageFeatureTable?.addFeature(it)
+                // Apply the edits to the service on the service geodatabase.
+                damageFeatureTable?.serviceGeodatabase?.applyEdits()
+                // Update the feature to get the updated objectid - a temporary ID is used before the feature is added.
+                it.refresh()
+                // Confirm feature addition.
+                snackBarMessage = "Created feature ${it.attributes["objectid"]}"
+            }
+        }
+    }
+
+    /**
+     * Selects a feature at the tapped location in preparation for deletion.
+     */
+    private fun deleteFeatureAt(screenCoordinate: ScreenCoordinate) {
+        damageLayer?.let { damageLayer ->
+            // Clear any existing selection.
+            damageLayer.clearSelection()
+            selectedFeature = null
+            viewModelScope.launch {
+                // Determine if a user tapped on a feature.
+                mapViewProxy.identify(damageLayer, screenCoordinate, 10.dp).onSuccess { identifyResult ->
+                    selectedFeature = (identifyResult.geoElements.firstOrNull() as ArcGISFeature).also {
path: samples/manage-features/src/main/java/com/esri/arcgismaps/sample/managefeatures/components/ManageFeaturesViewModel.kt
user: login: shubham7109
body: Sample crashes if I select a blank area of the map to identify and `deleteFeatureAt`: 
```suggestion
                    selectedFeature = (identifyResult.geoElements.firstOrNull() as? ArcGISFeature).also {
```
created_at: 2025-03-17T17:45:06Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,338 @@
+/* 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.managefeatures.components
+
+import android.app.Application
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.viewModelScope
+import com.arcgismaps.LoadStatus
+import com.arcgismaps.data.ArcGISFeature
+import com.arcgismaps.data.CodedValueDomain
+import com.arcgismaps.data.ServiceFeatureTable
+import com.arcgismaps.data.ServiceGeodatabase
+import com.arcgismaps.geometry.GeometryEngine
+import com.arcgismaps.geometry.Point
+import com.arcgismaps.geometry.SpatialReference
+import com.arcgismaps.mapping.ArcGISMap
+import com.arcgismaps.mapping.BasemapStyle
+import com.arcgismaps.mapping.Viewpoint
+import com.arcgismaps.mapping.layers.FeatureLayer
+import com.arcgismaps.mapping.view.ScreenCoordinate
+import com.arcgismaps.mapping.view.SingleTapConfirmedEvent
+import com.arcgismaps.toolkit.geoviewcompose.MapViewProxy
+import com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModel
+import kotlinx.coroutines.launch
+
+
+class ManageFeaturesViewModel(application: Application) : AndroidViewModel(application) {
+
+    val mapViewProxy = MapViewProxy()
+
+    // Hold a reference to the feature table.
+    private var damageFeatureTable: ServiceFeatureTable? = null
+
+    // Hold a reference to the feature layer.
+    private var damageLayer: FeatureLayer? = null
+
+    // Hold a reference to the selected feature.
+    var selectedFeature: ArcGISFeature? by mutableStateOf(null)
+
+    // The list of feature operations shown in this sample.
+    val manageFeaturesList = listOf("Create feature", "Delete feature", "Update attribute", "Update geometry")
+
+    // The current feature operation to perform.
+    var currentFeatureOperation by mutableStateOf(manageFeaturesList[0])
+
+    // Instructions associated with each feature operation.
+    val instructionsList = listOf(
+        "Tap on the map to create a new feature.",
+        "Select an existing feature to delete it.",
+        "Select an existing feature to edit its attribute.",
+        "Select an existing feature and tap the map to move it to a new position."
+    )
path: samples/manage-features/src/main/java/com/esri/arcgismaps/sample/managefeatures/components/ManageFeaturesViewModel.kt
user: login: TADraeseke
body: Great improvement, thanks!
created_at: 2025-03-18T18:35:55Z
author_association: COLLABORATOR
path: .kotlin/sessions/kotlin-compiler-17391608372419177291.salive
user: login: TADraeseke
body: I think this is actually a deletion of the file. Don't know how it got in there
created_at: 2025-03-18T19:16:07Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,334 @@
+/* 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.managefeatures.components
+
+import android.app.Application
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.viewModelScope
+import com.arcgismaps.LoadStatus
+import com.arcgismaps.data.ArcGISFeature
+import com.arcgismaps.data.CodedValueDomain
+import com.arcgismaps.data.ServiceFeatureTable
+import com.arcgismaps.data.ServiceGeodatabase
+import com.arcgismaps.geometry.GeometryEngine
+import com.arcgismaps.geometry.Point
+import com.arcgismaps.geometry.SpatialReference
+import com.arcgismaps.mapping.ArcGISMap
+import com.arcgismaps.mapping.BasemapStyle
+import com.arcgismaps.mapping.Viewpoint
+import com.arcgismaps.mapping.layers.FeatureLayer
+import com.arcgismaps.mapping.view.ScreenCoordinate
+import com.arcgismaps.mapping.view.SingleTapConfirmedEvent
+import com.arcgismaps.toolkit.geoviewcompose.MapViewProxy
+import com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModel
+import kotlinx.coroutines.launch
+
+
+class ManageFeaturesViewModel(application: Application) : AndroidViewModel(application) {
+
+    val mapViewProxy = MapViewProxy()
+
+    // Hold a reference to the feature table.
+    private var damageFeatureTable: ServiceFeatureTable? = null
+
+    // Hold a reference to the feature layer.
+    private var damageLayer: FeatureLayer? = null
+
+    // Hold a reference to the selected feature.
+    var selectedFeature: ArcGISFeature? by mutableStateOf(null)
+
+    // The current feature operation to perform.
+    var currentFeatureOperation by mutableStateOf(FeatureOperationType.CREATE)
+
+    // The list of damage types to update the feature attribute.
+    var damageTypeList: List<String> = mutableListOf()
+
+    var currentDamageType by mutableStateOf("")
+
+    // Create the map with streets basemap.
+    val arcGISMap = ArcGISMap(BasemapStyle.ArcGISStreets).apply {
+        // Zoom to the United States.
+        initialViewpoint = Viewpoint(
+            Point(x = -10800000.0, y = 4500000.0, spatialReference = SpatialReference.webMercator()), scale = 3e7
+        )
+    }
+
+    // Create a snackbar message to display the result of feature operations.
+    var snackBarMessage: String by mutableStateOf("")
+
+    // Create a message dialog view model for handling error messages
+    val messageDialogVM = MessageDialogViewModel()
+
+    init {
+        viewModelScope.launch {
+            // Create a service geodatabase from the feature service.
+            val serviceGeodatabase =
+                ServiceGeodatabase("https://sampleserver6.arcgisonline.com/arcgis/rest/services/DamageAssessment/FeatureServer/0")
+            serviceGeodatabase.load().onSuccess {
+                // Get the feature table from the service geodatabase referencing the Damage Assessment feature service.
+                serviceGeodatabase.getTable(0)?.let { serviceFeatureTable ->
+                    // Load the feature table to get the coded value domain for the attribute field.
+                    serviceFeatureTable.load().onSuccess {
+                        // Hold a reference to the feature table.
+                        damageFeatureTable = serviceFeatureTable
+                        // Get the field from the feature table that will be updated.
+                        val typeDamageField = serviceFeatureTable.fields.first { it.name == "typdamage" }
+                        // Get the coded value domain for the field.
+                        val attributeDomain = typeDamageField.domain as CodedValueDomain
+                        // Add the damage types to the list.
+                        attributeDomain.codedValues.forEach {
+                            damageTypeList += it.name
+                        }
+                        // Create a feature layer to visualize the features in the table.
+                        FeatureLayer.createWithFeatureTable(serviceFeatureTable).let { featureLayer ->
+                            // Hold a reference to the feature layer.
+                            damageLayer = featureLayer
+                            // Add it to the map.
+                            arcGISMap.operationalLayers.add(featureLayer)
+                            // Load the map.
+                            arcGISMap.load().onFailure { error ->
+                                messageDialogVM.showMessageDialog(
+                                    "Failed to load map", error.message.toString()
+                                )
+                            }
+                        }
+                    }.onFailure { error ->
+                        messageDialogVM.showMessageDialog(
+                            "Failed to load feature table", error.message.toString()
+                        )
+                    }
+                }
+            }.onFailure { error ->
+                // Show the message dialog and pass the error message to be displayed in the dialog.
+                messageDialogVM.showMessageDialog(
+                    "Failed to load service geodatabase", error.message.toString()
+                )
+            }
+        }
+    }
+
+    /**
+     * Directs the behaviour of tap's on the map view.
+     */
+    fun onTap(singleTapConfirmedEvent: SingleTapConfirmedEvent) {
+        if (damageLayer?.loadStatus?.value != LoadStatus.Loaded) {
+            snackBarMessage = "Layer not loaded!"
+            return
+        }
+        when (currentFeatureOperation) {
+            FeatureOperationType.CREATE -> createFeatureAt(singleTapConfirmedEvent.screenCoordinate)
+            FeatureOperationType.DELETE -> deleteFeatureAt(singleTapConfirmedEvent.screenCoordinate)
+            FeatureOperationType.UPDATE_ATTRIBUTE -> selectFeatureForAttributeEditAt(singleTapConfirmedEvent.screenCoordinate)
+            FeatureOperationType.UPDATE_GEOMETRY -> updateFeatureGeometryAt(singleTapConfirmedEvent.screenCoordinate)
+        }
+    }
+
+    /**
+     * Set the current feature operation to perform based on the selected index from the dropdown. Also, reset feature
+     * selection.
+     */
+    fun onFeatureOperationSelected(index: Int) {
+        currentFeatureOperation = FeatureOperationType.entries[index]
+        // Reset the selected feature when the operation changes.
+        damageLayer?.clearSelection()
+        selectedFeature = null
+    }
+
+    /**
+     * Create a new feature at the tapped location with some default attributes
+     */
+    private fun createFeatureAt(screenCoordinate: ScreenCoordinate) {
+        // Create the feature.
+        val feature = damageFeatureTable?.createFeature()?.apply {
+            // Get the normalized geometry for the tapped location and use it as the feature's geometry.
+            mapViewProxy.screenToLocationOrNull(screenCoordinate)?.let { mapPoint ->
+                geometry = GeometryEngine.normalizeCentralMeridian(mapPoint)
+                // Set feature attributes.
+                attributes["typdamage"] = "Minor"
+                attributes["primcause"] = "Earthquake"
+            }
+        }
+        feature?.let {
+            viewModelScope.launch {
+                // Add the feature to the table.
+                damageFeatureTable?.addFeature(it)
+                // Apply the edits to the service on the service geodatabase.
+                damageFeatureTable?.serviceGeodatabase?.applyEdits()
+                // Update the feature to get the updated objectid - a temporary ID is used before the feature is added.
+                it.refresh()
+                // Confirm feature addition.
+                snackBarMessage = "Created feature ${it.attributes["objectid"]}"
+            }
+        }
+    }
+
+    /**
+     * Selects a feature at the tapped location in preparation for deletion.
+     */
+    private fun deleteFeatureAt(screenCoordinate: ScreenCoordinate) {
+        damageLayer?.let { damageLayer ->
+            // Clear any existing selection.
+            damageLayer.clearSelection()
+            selectedFeature = null
+            viewModelScope.launch {
+                // Determine if a user tapped on a feature.
+                mapViewProxy.identify(damageLayer, screenCoordinate, 10.dp).onSuccess { identifyResult ->
+                    selectedFeature = (identifyResult.geoElements.firstOrNull() as? ArcGISFeature)?.also {
+                        damageLayer.selectFeature(it)
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Delete the selected feature from the feature table and service geodatabase.
+     */
+    fun deleteSelectedFeature() {
+        selectedFeature?.let {
+            // Get the feature's object id.
+            val featureId = it.attributes["objectid"] as Long
+            viewModelScope.launch {
+                // Delete the feature from the feature table.
+                damageFeatureTable?.deleteFeature(it)?.onSuccess {
+                    snackBarMessage = "Deleted feature $featureId"
+                }?.onFailure {
+                    snackBarMessage = "Failed to delete feature $featureId"
+                }
+                // Apply the edits to the service geodatabase.
+                damageFeatureTable?.serviceGeodatabase?.applyEdits()
+                selectedFeature = null
path: samples/manage-features/src/main/java/com/esri/arcgismaps/sample/managefeatures/components/ManageFeaturesViewModel.kt
user: login: eri9000
body: does need to be done if deleting a feature is unsuccessful? 
created_at: 2025-03-19T17:01:00Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,334 @@
+/* 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.managefeatures.components
+
+import android.app.Application
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.viewModelScope
+import com.arcgismaps.LoadStatus
+import com.arcgismaps.data.ArcGISFeature
+import com.arcgismaps.data.CodedValueDomain
+import com.arcgismaps.data.ServiceFeatureTable
+import com.arcgismaps.data.ServiceGeodatabase
+import com.arcgismaps.geometry.GeometryEngine
+import com.arcgismaps.geometry.Point
+import com.arcgismaps.geometry.SpatialReference
+import com.arcgismaps.mapping.ArcGISMap
+import com.arcgismaps.mapping.BasemapStyle
+import com.arcgismaps.mapping.Viewpoint
+import com.arcgismaps.mapping.layers.FeatureLayer
+import com.arcgismaps.mapping.view.ScreenCoordinate
+import com.arcgismaps.mapping.view.SingleTapConfirmedEvent
+import com.arcgismaps.toolkit.geoviewcompose.MapViewProxy
+import com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModel
+import kotlinx.coroutines.launch
+
+
+class ManageFeaturesViewModel(application: Application) : AndroidViewModel(application) {
+
+    val mapViewProxy = MapViewProxy()
+
+    // Hold a reference to the feature table.
+    private var damageFeatureTable: ServiceFeatureTable? = null
+
+    // Hold a reference to the feature layer.
+    private var damageLayer: FeatureLayer? = null
+
+    // Hold a reference to the selected feature.
+    var selectedFeature: ArcGISFeature? by mutableStateOf(null)
+
+    // The current feature operation to perform.
+    var currentFeatureOperation by mutableStateOf(FeatureOperationType.CREATE)
+
+    // The list of damage types to update the feature attribute.
+    var damageTypeList: List<String> = mutableListOf()
+
+    var currentDamageType by mutableStateOf("")
+
+    // Create the map with streets basemap.
+    val arcGISMap = ArcGISMap(BasemapStyle.ArcGISStreets).apply {
+        // Zoom to the United States.
+        initialViewpoint = Viewpoint(
+            Point(x = -10800000.0, y = 4500000.0, spatialReference = SpatialReference.webMercator()), scale = 3e7
+        )
+    }
+
+    // Create a snackbar message to display the result of feature operations.
+    var snackBarMessage: String by mutableStateOf("")
+
+    // Create a message dialog view model for handling error messages
+    val messageDialogVM = MessageDialogViewModel()
+
+    init {
+        viewModelScope.launch {
+            // Create a service geodatabase from the feature service.
+            val serviceGeodatabase =
+                ServiceGeodatabase("https://sampleserver6.arcgisonline.com/arcgis/rest/services/DamageAssessment/FeatureServer/0")
+            serviceGeodatabase.load().onSuccess {
+                // Get the feature table from the service geodatabase referencing the Damage Assessment feature service.
+                serviceGeodatabase.getTable(0)?.let { serviceFeatureTable ->
+                    // Load the feature table to get the coded value domain for the attribute field.
+                    serviceFeatureTable.load().onSuccess {
+                        // Hold a reference to the feature table.
+                        damageFeatureTable = serviceFeatureTable
+                        // Get the field from the feature table that will be updated.
+                        val typeDamageField = serviceFeatureTable.fields.first { it.name == "typdamage" }
+                        // Get the coded value domain for the field.
+                        val attributeDomain = typeDamageField.domain as CodedValueDomain
+                        // Add the damage types to the list.
+                        attributeDomain.codedValues.forEach {
+                            damageTypeList += it.name
+                        }
+                        // Create a feature layer to visualize the features in the table.
+                        FeatureLayer.createWithFeatureTable(serviceFeatureTable).let { featureLayer ->
+                            // Hold a reference to the feature layer.
+                            damageLayer = featureLayer
+                            // Add it to the map.
+                            arcGISMap.operationalLayers.add(featureLayer)
+                            // Load the map.
+                            arcGISMap.load().onFailure { error ->
+                                messageDialogVM.showMessageDialog(
+                                    "Failed to load map", error.message.toString()
+                                )
+                            }
+                        }
+                    }.onFailure { error ->
+                        messageDialogVM.showMessageDialog(
+                            "Failed to load feature table", error.message.toString()
+                        )
+                    }
+                }
+            }.onFailure { error ->
+                // Show the message dialog and pass the error message to be displayed in the dialog.
+                messageDialogVM.showMessageDialog(
+                    "Failed to load service geodatabase", error.message.toString()
+                )
+            }
+        }
+    }
+
+    /**
+     * Directs the behaviour of tap's on the map view.
+     */
+    fun onTap(singleTapConfirmedEvent: SingleTapConfirmedEvent) {
+        if (damageLayer?.loadStatus?.value != LoadStatus.Loaded) {
+            snackBarMessage = "Layer not loaded!"
+            return
+        }
+        when (currentFeatureOperation) {
+            FeatureOperationType.CREATE -> createFeatureAt(singleTapConfirmedEvent.screenCoordinate)
+            FeatureOperationType.DELETE -> deleteFeatureAt(singleTapConfirmedEvent.screenCoordinate)
+            FeatureOperationType.UPDATE_ATTRIBUTE -> selectFeatureForAttributeEditAt(singleTapConfirmedEvent.screenCoordinate)
+            FeatureOperationType.UPDATE_GEOMETRY -> updateFeatureGeometryAt(singleTapConfirmedEvent.screenCoordinate)
+        }
+    }
+
+    /**
+     * Set the current feature operation to perform based on the selected index from the dropdown. Also, reset feature
+     * selection.
+     */
+    fun onFeatureOperationSelected(index: Int) {
+        currentFeatureOperation = FeatureOperationType.entries[index]
+        // Reset the selected feature when the operation changes.
+        damageLayer?.clearSelection()
+        selectedFeature = null
+    }
+
+    /**
+     * Create a new feature at the tapped location with some default attributes
+     */
+    private fun createFeatureAt(screenCoordinate: ScreenCoordinate) {
+        // Create the feature.
+        val feature = damageFeatureTable?.createFeature()?.apply {
+            // Get the normalized geometry for the tapped location and use it as the feature's geometry.
+            mapViewProxy.screenToLocationOrNull(screenCoordinate)?.let { mapPoint ->
+                geometry = GeometryEngine.normalizeCentralMeridian(mapPoint)
+                // Set feature attributes.
+                attributes["typdamage"] = "Minor"
+                attributes["primcause"] = "Earthquake"
+            }
+        }
+        feature?.let {
+            viewModelScope.launch {
+                // Add the feature to the table.
+                damageFeatureTable?.addFeature(it)
+                // Apply the edits to the service on the service geodatabase.
+                damageFeatureTable?.serviceGeodatabase?.applyEdits()
+                // Update the feature to get the updated objectid - a temporary ID is used before the feature is added.
+                it.refresh()
+                // Confirm feature addition.
+                snackBarMessage = "Created feature ${it.attributes["objectid"]}"
+            }
+        }
+    }
+
+    /**
+     * Selects a feature at the tapped location in preparation for deletion.
+     */
+    private fun deleteFeatureAt(screenCoordinate: ScreenCoordinate) {
+        damageLayer?.let { damageLayer ->
+            // Clear any existing selection.
+            damageLayer.clearSelection()
+            selectedFeature = null
+            viewModelScope.launch {
+                // Determine if a user tapped on a feature.
+                mapViewProxy.identify(damageLayer, screenCoordinate, 10.dp).onSuccess { identifyResult ->
+                    selectedFeature = (identifyResult.geoElements.firstOrNull() as? ArcGISFeature)?.also {
+                        damageLayer.selectFeature(it)
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Delete the selected feature from the feature table and service geodatabase.
+     */
+    fun deleteSelectedFeature() {
+        selectedFeature?.let {
+            // Get the feature's object id.
+            val featureId = it.attributes["objectid"] as Long
+            viewModelScope.launch {
+                // Delete the feature from the feature table.
+                damageFeatureTable?.deleteFeature(it)?.onSuccess {
+                    snackBarMessage = "Deleted feature $featureId"
+                }?.onFailure {
+                    snackBarMessage = "Failed to delete feature $featureId"
+                }
+                // Apply the edits to the service geodatabase.
+                damageFeatureTable?.serviceGeodatabase?.applyEdits()
+                selectedFeature = null
+            }
+        }
+    }
+
+    /**
+     * Selects a feature at the tapped location in preparation for attribute editing.
+     */
+    private fun selectFeatureForAttributeEditAt(screenCoordinate: ScreenCoordinate) {
+        damageLayer?.let { damageLayer ->
+            // Clear any existing selection.
+            damageLayer.clearSelection()
+            selectedFeature = null
+            viewModelScope.launch {
+                // Determine if a user tapped on a feature.
+                mapViewProxy.identify(damageLayer, screenCoordinate, 10.dp).onSuccess { identifyResult ->
+                    // Get the identified feature.
+                    val identifiedFeature = identifyResult.geoElements.firstOrNull() as? ArcGISFeature
+                    identifiedFeature?.let {
+                        val currentAttributeValue = it.attributes["typdamage"] as String
+                        currentDamageType = currentAttributeValue
+                        selectedFeature = it.also {
+                            damageLayer.selectFeature(it)
+                        }
+                    } ?: run {
+                        // Clear the selected feature if no feature is found.
+                        damageLayer.clearSelection()
+                        selectedFeature = null
+                        currentDamageType = ""
path: samples/manage-features/src/main/java/com/esri/arcgismaps/sample/managefeatures/components/ManageFeaturesViewModel.kt
user: login: eri9000
body: it seem that you're already doing this at the start of the this function. 
Is this needed again?
created_at: 2025-03-19T17:33:12Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,334 @@
+/* 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.managefeatures.components
+
+import android.app.Application
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.viewModelScope
+import com.arcgismaps.LoadStatus
+import com.arcgismaps.data.ArcGISFeature
+import com.arcgismaps.data.CodedValueDomain
+import com.arcgismaps.data.ServiceFeatureTable
+import com.arcgismaps.data.ServiceGeodatabase
+import com.arcgismaps.geometry.GeometryEngine
+import com.arcgismaps.geometry.Point
+import com.arcgismaps.geometry.SpatialReference
+import com.arcgismaps.mapping.ArcGISMap
+import com.arcgismaps.mapping.BasemapStyle
+import com.arcgismaps.mapping.Viewpoint
+import com.arcgismaps.mapping.layers.FeatureLayer
+import com.arcgismaps.mapping.view.ScreenCoordinate
+import com.arcgismaps.mapping.view.SingleTapConfirmedEvent
+import com.arcgismaps.toolkit.geoviewcompose.MapViewProxy
+import com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModel
+import kotlinx.coroutines.launch
+
+
+class ManageFeaturesViewModel(application: Application) : AndroidViewModel(application) {
+
+    val mapViewProxy = MapViewProxy()
+
+    // Hold a reference to the feature table.
+    private var damageFeatureTable: ServiceFeatureTable? = null
+
+    // Hold a reference to the feature layer.
+    private var damageLayer: FeatureLayer? = null
+
+    // Hold a reference to the selected feature.
+    var selectedFeature: ArcGISFeature? by mutableStateOf(null)
+
+    // The current feature operation to perform.
+    var currentFeatureOperation by mutableStateOf(FeatureOperationType.CREATE)
+
+    // The list of damage types to update the feature attribute.
+    var damageTypeList: List<String> = mutableListOf()
+
+    var currentDamageType by mutableStateOf("")
+
+    // Create the map with streets basemap.
+    val arcGISMap = ArcGISMap(BasemapStyle.ArcGISStreets).apply {
+        // Zoom to the United States.
+        initialViewpoint = Viewpoint(
+            Point(x = -10800000.0, y = 4500000.0, spatialReference = SpatialReference.webMercator()), scale = 3e7
+        )
+    }
+
+    // Create a snackbar message to display the result of feature operations.
+    var snackBarMessage: String by mutableStateOf("")
+
+    // Create a message dialog view model for handling error messages
+    val messageDialogVM = MessageDialogViewModel()
+
+    init {
+        viewModelScope.launch {
+            // Create a service geodatabase from the feature service.
+            val serviceGeodatabase =
+                ServiceGeodatabase("https://sampleserver6.arcgisonline.com/arcgis/rest/services/DamageAssessment/FeatureServer/0")
+            serviceGeodatabase.load().onSuccess {
+                // Get the feature table from the service geodatabase referencing the Damage Assessment feature service.
+                serviceGeodatabase.getTable(0)?.let { serviceFeatureTable ->
+                    // Load the feature table to get the coded value domain for the attribute field.
+                    serviceFeatureTable.load().onSuccess {
+                        // Hold a reference to the feature table.
+                        damageFeatureTable = serviceFeatureTable
+                        // Get the field from the feature table that will be updated.
+                        val typeDamageField = serviceFeatureTable.fields.first { it.name == "typdamage" }
+                        // Get the coded value domain for the field.
+                        val attributeDomain = typeDamageField.domain as CodedValueDomain
+                        // Add the damage types to the list.
+                        attributeDomain.codedValues.forEach {
+                            damageTypeList += it.name
+                        }
+                        // Create a feature layer to visualize the features in the table.
+                        FeatureLayer.createWithFeatureTable(serviceFeatureTable).let { featureLayer ->
+                            // Hold a reference to the feature layer.
+                            damageLayer = featureLayer
+                            // Add it to the map.
+                            arcGISMap.operationalLayers.add(featureLayer)
+                            // Load the map.
+                            arcGISMap.load().onFailure { error ->
+                                messageDialogVM.showMessageDialog(
+                                    "Failed to load map", error.message.toString()
+                                )
+                            }
+                        }
+                    }.onFailure { error ->
+                        messageDialogVM.showMessageDialog(
+                            "Failed to load feature table", error.message.toString()
+                        )
+                    }
+                }
+            }.onFailure { error ->
+                // Show the message dialog and pass the error message to be displayed in the dialog.
+                messageDialogVM.showMessageDialog(
+                    "Failed to load service geodatabase", error.message.toString()
+                )
+            }
+        }
+    }
+
+    /**
+     * Directs the behaviour of tap's on the map view.
+     */
+    fun onTap(singleTapConfirmedEvent: SingleTapConfirmedEvent) {
+        if (damageLayer?.loadStatus?.value != LoadStatus.Loaded) {
+            snackBarMessage = "Layer not loaded!"
+            return
+        }
+        when (currentFeatureOperation) {
+            FeatureOperationType.CREATE -> createFeatureAt(singleTapConfirmedEvent.screenCoordinate)
+            FeatureOperationType.DELETE -> deleteFeatureAt(singleTapConfirmedEvent.screenCoordinate)
+            FeatureOperationType.UPDATE_ATTRIBUTE -> selectFeatureForAttributeEditAt(singleTapConfirmedEvent.screenCoordinate)
+            FeatureOperationType.UPDATE_GEOMETRY -> updateFeatureGeometryAt(singleTapConfirmedEvent.screenCoordinate)
+        }
+    }
+
+    /**
+     * Set the current feature operation to perform based on the selected index from the dropdown. Also, reset feature
+     * selection.
+     */
+    fun onFeatureOperationSelected(index: Int) {
+        currentFeatureOperation = FeatureOperationType.entries[index]
+        // Reset the selected feature when the operation changes.
+        damageLayer?.clearSelection()
+        selectedFeature = null
+    }
+
+    /**
+     * Create a new feature at the tapped location with some default attributes
+     */
+    private fun createFeatureAt(screenCoordinate: ScreenCoordinate) {
+        // Create the feature.
+        val feature = damageFeatureTable?.createFeature()?.apply {
+            // Get the normalized geometry for the tapped location and use it as the feature's geometry.
+            mapViewProxy.screenToLocationOrNull(screenCoordinate)?.let { mapPoint ->
+                geometry = GeometryEngine.normalizeCentralMeridian(mapPoint)
+                // Set feature attributes.
+                attributes["typdamage"] = "Minor"
+                attributes["primcause"] = "Earthquake"
+            }
+        }
+        feature?.let {
+            viewModelScope.launch {
+                // Add the feature to the table.
+                damageFeatureTable?.addFeature(it)
+                // Apply the edits to the service on the service geodatabase.
+                damageFeatureTable?.serviceGeodatabase?.applyEdits()
+                // Update the feature to get the updated objectid - a temporary ID is used before the feature is added.
+                it.refresh()
+                // Confirm feature addition.
+                snackBarMessage = "Created feature ${it.attributes["objectid"]}"
+            }
+        }
+    }
+
+    /**
+     * Selects a feature at the tapped location in preparation for deletion.
+     */
+    private fun deleteFeatureAt(screenCoordinate: ScreenCoordinate) {
+        damageLayer?.let { damageLayer ->
+            // Clear any existing selection.
+            damageLayer.clearSelection()
+            selectedFeature = null
+            viewModelScope.launch {
+                // Determine if a user tapped on a feature.
+                mapViewProxy.identify(damageLayer, screenCoordinate, 10.dp).onSuccess { identifyResult ->
+                    selectedFeature = (identifyResult.geoElements.firstOrNull() as? ArcGISFeature)?.also {
+                        damageLayer.selectFeature(it)
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Delete the selected feature from the feature table and service geodatabase.
+     */
+    fun deleteSelectedFeature() {
+        selectedFeature?.let {
+            // Get the feature's object id.
+            val featureId = it.attributes["objectid"] as Long
+            viewModelScope.launch {
+                // Delete the feature from the feature table.
+                damageFeatureTable?.deleteFeature(it)?.onSuccess {
+                    snackBarMessage = "Deleted feature $featureId"
+                }?.onFailure {
+                    snackBarMessage = "Failed to delete feature $featureId"
+                }
+                // Apply the edits to the service geodatabase.
+                damageFeatureTable?.serviceGeodatabase?.applyEdits()
+                selectedFeature = null
+            }
+        }
+    }
+
+    /**
+     * Selects a feature at the tapped location in preparation for attribute editing.
+     */
+    private fun selectFeatureForAttributeEditAt(screenCoordinate: ScreenCoordinate) {
+        damageLayer?.let { damageLayer ->
+            // Clear any existing selection.
+            damageLayer.clearSelection()
+            selectedFeature = null
+            viewModelScope.launch {
+                // Determine if a user tapped on a feature.
+                mapViewProxy.identify(damageLayer, screenCoordinate, 10.dp).onSuccess { identifyResult ->
+                    // Get the identified feature.
+                    val identifiedFeature = identifyResult.geoElements.firstOrNull() as? ArcGISFeature
+                    identifiedFeature?.let {
+                        val currentAttributeValue = it.attributes["typdamage"] as String
+                        currentDamageType = currentAttributeValue
+                        selectedFeature = it.also {
+                            damageLayer.selectFeature(it)
+                        }
+                    } ?: run {
+                        // Clear the selected feature if no feature is found.
+                        damageLayer.clearSelection()
+                        selectedFeature = null
+                        currentDamageType = ""
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Update the attribute value of the selected feature to the new value from the new damage type.
+     */
+    fun onDamageTypeSelected(index: Int) {
+        // Get the new value.
+        currentDamageType = damageTypeList[index]
+        selectedFeature?.let { selectedFeature ->
+            viewModelScope.launch {
+                // Load the feature.
+                selectedFeature.load().onSuccess {
+                    // Update the attribute value.
+                    selectedFeature.attributes["typdamage"] = currentDamageType
+                    // Update the table.
+                    damageFeatureTable?.updateFeature(selectedFeature)
+                    // Update the service on the service geodatabase.
+                    damageFeatureTable?.serviceGeodatabase?.applyEdits()?.onSuccess {
+                        snackBarMessage =
+                            "Updated feature ${selectedFeature.attributes["objectid"]} to $currentDamageType"
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Select a feature, if none is selected. If a feature is selected, update its geometry to the tapped location.
+     */
+    private fun updateFeatureGeometryAt(screenCoordinate: ScreenCoordinate) {
+
+        damageLayer?.let { damageLayer ->
+            when (selectedFeature) {
+                // When no feature is selected.
+                null -> {
path: samples/manage-features/src/main/java/com/esri/arcgismaps/sample/managefeatures/components/ManageFeaturesViewModel.kt
user: login: eri9000
body: i would replace this with an `if-else` 
created_at: 2025-03-19T18:06:03Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,161 @@
+/* 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.managefeatures.screens
+
+import androidx.compose.animation.animateContentSize
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Button
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.SnackbarHost
+import androidx.compose.material3.SnackbarHostState
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.arcgismaps.toolkit.geoviewcompose.MapView
+import com.esri.arcgismaps.sample.managefeatures.components.FeatureOperationType
+import com.esri.arcgismaps.sample.managefeatures.components.ManageFeaturesViewModel
+import com.esri.arcgismaps.sample.sampleslib.components.DropDownMenuBox
+import com.esri.arcgismaps.sample.sampleslib.components.MessageDialog
+import com.esri.arcgismaps.sample.sampleslib.components.SampleTopAppBar
+import kotlinx.coroutines.launch
+
+/**
+ * Main screen layout for the sample app
+ */
+@Composable
+fun ManageFeaturesScreen(sampleName: String) {
+    val mapViewModel: ManageFeaturesViewModel = viewModel()
+
+    var featureManagementDropdownIndex by remember { mutableIntStateOf(0) }
+
+    val snackbarHostState = remember { SnackbarHostState() }
+    val coroutineScope = rememberCoroutineScope()
+
+    Scaffold(
+        topBar = { SampleTopAppBar(title = sampleName) },
+        snackbarHost = {
+            SnackbarHost(
+                modifier = Modifier.padding(bottom = 128.dp),
+                hostState = snackbarHostState
+            )
+        },
+        content = {
+            Column(
+                modifier = Modifier
+                    .fillMaxSize()
+                    .padding(it),
+                horizontalAlignment = Alignment.CenterHorizontally,
+            ) {
+                MapView(
+                    modifier = Modifier
+                        .weight(1f),
+                    mapViewProxy = mapViewModel.mapViewProxy,
+                    arcGISMap = mapViewModel.arcGISMap,
+                    onSingleTapConfirmed = mapViewModel::onTap,
+                ) {
+                    mapViewModel.selectedFeature?.let { selectedFeature ->
+                        // Only show the delete button when on the delete feature operation and a feature is selected.
+                        if (mapViewModel.currentFeatureOperation == FeatureOperationType.DELETE) {
+                            Callout(geoElement = selectedFeature) {
+                                Button(onClick = mapViewModel::deleteSelectedFeature) {
+                                    Text(text = "Delete")
+                                }
+                            }
+                        }
+                        // Only show the dropdown for damage type when on the update feature operation.
+                        if (mapViewModel.currentFeatureOperation == FeatureOperationType.UPDATE_ATTRIBUTE) {
+                            Callout(geoElement = selectedFeature) {
+                                DropDownMenuBox(
+                                    modifier = Modifier
+                                        .padding(8.dp),
+                                    textFieldLabel = "Select damage type",
+                                    textFieldValue = mapViewModel.currentDamageType,
+                                    dropDownItemList = mapViewModel.damageTypeList,
+                                    onIndexSelected = { index ->
+                                        if (mapViewModel.selectedFeature != null) {
+                                            mapViewModel.onDamageTypeSelected(index)
+                                        } else {
+                                            coroutineScope.launch {
+                                                snackbarHostState.showSnackbar("Please select a feature to update")
+                                            }
+                                        }
+                                    })
path: samples/manage-features/src/main/java/com/esri/arcgismaps/sample/managefeatures/screens/ManageFeaturesScreen.kt
user: login: eri9000
body: parenthesis should go in a new line 
created_at: 2025-03-19T18:09:47Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,334 @@
+/* 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.managefeatures.components
+
+import android.app.Application
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.viewModelScope
+import com.arcgismaps.LoadStatus
+import com.arcgismaps.data.ArcGISFeature
+import com.arcgismaps.data.CodedValueDomain
+import com.arcgismaps.data.ServiceFeatureTable
+import com.arcgismaps.data.ServiceGeodatabase
+import com.arcgismaps.geometry.GeometryEngine
+import com.arcgismaps.geometry.Point
+import com.arcgismaps.geometry.SpatialReference
+import com.arcgismaps.mapping.ArcGISMap
+import com.arcgismaps.mapping.BasemapStyle
+import com.arcgismaps.mapping.Viewpoint
+import com.arcgismaps.mapping.layers.FeatureLayer
+import com.arcgismaps.mapping.view.ScreenCoordinate
+import com.arcgismaps.mapping.view.SingleTapConfirmedEvent
+import com.arcgismaps.toolkit.geoviewcompose.MapViewProxy
+import com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModel
+import kotlinx.coroutines.launch
+
+
+class ManageFeaturesViewModel(application: Application) : AndroidViewModel(application) {
+
+    val mapViewProxy = MapViewProxy()
+
+    // Hold a reference to the feature table.
+    private var damageFeatureTable: ServiceFeatureTable? = null
+
+    // Hold a reference to the feature layer.
+    private var damageLayer: FeatureLayer? = null
+
+    // Hold a reference to the selected feature.
+    var selectedFeature: ArcGISFeature? by mutableStateOf(null)
+
+    // The current feature operation to perform.
+    var currentFeatureOperation by mutableStateOf(FeatureOperationType.CREATE)
+
+    // The list of damage types to update the feature attribute.
+    var damageTypeList: List<String> = mutableListOf()
+
+    var currentDamageType by mutableStateOf("")
+
+    // Create the map with streets basemap.
+    val arcGISMap = ArcGISMap(BasemapStyle.ArcGISStreets).apply {
+        // Zoom to the United States.
+        initialViewpoint = Viewpoint(
+            Point(x = -10800000.0, y = 4500000.0, spatialReference = SpatialReference.webMercator()), scale = 3e7
+        )
+    }
+
+    // Create a snackbar message to display the result of feature operations.
+    var snackBarMessage: String by mutableStateOf("")
+
+    // Create a message dialog view model for handling error messages
+    val messageDialogVM = MessageDialogViewModel()
+
+    init {
+        viewModelScope.launch {
+            // Create a service geodatabase from the feature service.
+            val serviceGeodatabase =
+                ServiceGeodatabase("https://sampleserver6.arcgisonline.com/arcgis/rest/services/DamageAssessment/FeatureServer/0")
+            serviceGeodatabase.load().onSuccess {
+                // Get the feature table from the service geodatabase referencing the Damage Assessment feature service.
+                serviceGeodatabase.getTable(0)?.let { serviceFeatureTable ->
+                    // Load the feature table to get the coded value domain for the attribute field.
+                    serviceFeatureTable.load().onSuccess {
+                        // Hold a reference to the feature table.
+                        damageFeatureTable = serviceFeatureTable
+                        // Get the field from the feature table that will be updated.
+                        val typeDamageField = serviceFeatureTable.fields.first { it.name == "typdamage" }
+                        // Get the coded value domain for the field.
+                        val attributeDomain = typeDamageField.domain as CodedValueDomain
+                        // Add the damage types to the list.
+                        attributeDomain.codedValues.forEach {
+                            damageTypeList += it.name
+                        }
+                        // Create a feature layer to visualize the features in the table.
+                        FeatureLayer.createWithFeatureTable(serviceFeatureTable).let { featureLayer ->
+                            // Hold a reference to the feature layer.
+                            damageLayer = featureLayer
+                            // Add it to the map.
+                            arcGISMap.operationalLayers.add(featureLayer)
+                            // Load the map.
+                            arcGISMap.load().onFailure { error ->
+                                messageDialogVM.showMessageDialog(
+                                    "Failed to load map", error.message.toString()
+                                )
+                            }
+                        }
+                    }.onFailure { error ->
+                        messageDialogVM.showMessageDialog(
+                            "Failed to load feature table", error.message.toString()
+                        )
+                    }
+                }
+            }.onFailure { error ->
+                // Show the message dialog and pass the error message to be displayed in the dialog.
+                messageDialogVM.showMessageDialog(
+                    "Failed to load service geodatabase", error.message.toString()
+                )
+            }
+        }
+    }
+
+    /**
+     * Directs the behaviour of tap's on the map view.
+     */
+    fun onTap(singleTapConfirmedEvent: SingleTapConfirmedEvent) {
+        if (damageLayer?.loadStatus?.value != LoadStatus.Loaded) {
+            snackBarMessage = "Layer not loaded!"
+            return
+        }
+        when (currentFeatureOperation) {
+            FeatureOperationType.CREATE -> createFeatureAt(singleTapConfirmedEvent.screenCoordinate)
+            FeatureOperationType.DELETE -> deleteFeatureAt(singleTapConfirmedEvent.screenCoordinate)
+            FeatureOperationType.UPDATE_ATTRIBUTE -> selectFeatureForAttributeEditAt(singleTapConfirmedEvent.screenCoordinate)
+            FeatureOperationType.UPDATE_GEOMETRY -> updateFeatureGeometryAt(singleTapConfirmedEvent.screenCoordinate)
+        }
+    }
+
+    /**
+     * Set the current feature operation to perform based on the selected index from the dropdown. Also, reset feature
+     * selection.
+     */
+    fun onFeatureOperationSelected(index: Int) {
+        currentFeatureOperation = FeatureOperationType.entries[index]
+        // Reset the selected feature when the operation changes.
+        damageLayer?.clearSelection()
+        selectedFeature = null
+    }
+
+    /**
+     * Create a new feature at the tapped location with some default attributes
+     */
+    private fun createFeatureAt(screenCoordinate: ScreenCoordinate) {
+        // Create the feature.
+        val feature = damageFeatureTable?.createFeature()?.apply {
+            // Get the normalized geometry for the tapped location and use it as the feature's geometry.
+            mapViewProxy.screenToLocationOrNull(screenCoordinate)?.let { mapPoint ->
+                geometry = GeometryEngine.normalizeCentralMeridian(mapPoint)
+                // Set feature attributes.
+                attributes["typdamage"] = "Minor"
+                attributes["primcause"] = "Earthquake"
+            }
+        }
+        feature?.let {
+            viewModelScope.launch {
+                // Add the feature to the table.
+                damageFeatureTable?.addFeature(it)
+                // Apply the edits to the service on the service geodatabase.
+                damageFeatureTable?.serviceGeodatabase?.applyEdits()
+                // Update the feature to get the updated objectid - a temporary ID is used before the feature is added.
+                it.refresh()
+                // Confirm feature addition.
+                snackBarMessage = "Created feature ${it.attributes["objectid"]}"
+            }
+        }
+    }
+
+    /**
+     * Selects a feature at the tapped location in preparation for deletion.
+     */
+    private fun deleteFeatureAt(screenCoordinate: ScreenCoordinate) {
+        damageLayer?.let { damageLayer ->
+            // Clear any existing selection.
+            damageLayer.clearSelection()
+            selectedFeature = null
+            viewModelScope.launch {
+                // Determine if a user tapped on a feature.
+                mapViewProxy.identify(damageLayer, screenCoordinate, 10.dp).onSuccess { identifyResult ->
+                    selectedFeature = (identifyResult.geoElements.firstOrNull() as? ArcGISFeature)?.also {
+                        damageLayer.selectFeature(it)
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Delete the selected feature from the feature table and service geodatabase.
+     */
+    fun deleteSelectedFeature() {
+        selectedFeature?.let {
+            // Get the feature's object id.
+            val featureId = it.attributes["objectid"] as Long
+            viewModelScope.launch {
+                // Delete the feature from the feature table.
+                damageFeatureTable?.deleteFeature(it)?.onSuccess {
+                    snackBarMessage = "Deleted feature $featureId"
+                }?.onFailure {
+                    snackBarMessage = "Failed to delete feature $featureId"
+                }
+                // Apply the edits to the service geodatabase.
+                damageFeatureTable?.serviceGeodatabase?.applyEdits()
+                selectedFeature = null
+            }
+        }
+    }
+
+    /**
+     * Selects a feature at the tapped location in preparation for attribute editing.
+     */
+    private fun selectFeatureForAttributeEditAt(screenCoordinate: ScreenCoordinate) {
+        damageLayer?.let { damageLayer ->
+            // Clear any existing selection.
+            damageLayer.clearSelection()
+            selectedFeature = null
+            viewModelScope.launch {
+                // Determine if a user tapped on a feature.
+                mapViewProxy.identify(damageLayer, screenCoordinate, 10.dp).onSuccess { identifyResult ->
+                    // Get the identified feature.
+                    val identifiedFeature = identifyResult.geoElements.firstOrNull() as? ArcGISFeature
+                    identifiedFeature?.let {
+                        val currentAttributeValue = it.attributes["typdamage"] as String
+                        currentDamageType = currentAttributeValue
+                        selectedFeature = it.also {
+                            damageLayer.selectFeature(it)
+                        }
+                    } ?: run {
+                        // Clear the selected feature if no feature is found.
+                        damageLayer.clearSelection()
+                        selectedFeature = null
+                        currentDamageType = ""
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Update the attribute value of the selected feature to the new value from the new damage type.
+     */
+    fun onDamageTypeSelected(index: Int) {
+        // Get the new value.
+        currentDamageType = damageTypeList[index]
+        selectedFeature?.let { selectedFeature ->
+            viewModelScope.launch {
+                // Load the feature.
+                selectedFeature.load().onSuccess {
+                    // Update the attribute value.
+                    selectedFeature.attributes["typdamage"] = currentDamageType
+                    // Update the table.
+                    damageFeatureTable?.updateFeature(selectedFeature)
+                    // Update the service on the service geodatabase.
+                    damageFeatureTable?.serviceGeodatabase?.applyEdits()?.onSuccess {
+                        snackBarMessage =
+                            "Updated feature ${selectedFeature.attributes["objectid"]} to $currentDamageType"
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Select a feature, if none is selected. If a feature is selected, update its geometry to the tapped location.
+     */
+    private fun updateFeatureGeometryAt(screenCoordinate: ScreenCoordinate) {
+
+        damageLayer?.let { damageLayer ->
+            when (selectedFeature) {
+                // When no feature is selected.
+                null -> {
+                    viewModelScope.launch {
+                        // Determine if a user tapped on a feature.
+                        mapViewProxy.identify(damageLayer, screenCoordinate, 10.dp).onSuccess { identifyResult ->
+                            // Get the identified feature.
+                            val identifiedFeature = identifyResult.geoElements.firstOrNull() as? ArcGISFeature
+                            identifiedFeature?.let {
+                                selectedFeature = it.also {
+                                    damageLayer.selectFeature(it)
+                                }
+                            }
+                        }
+                    }
+                }
+                // When a feature is selected, update its geometry to the tapped location.
+                else -> {
+                    mapViewProxy.screenToLocationOrNull(screenCoordinate)?.let { mapPoint ->
+                        // Normalize the point - needed when the tapped location is over the international date line.
+                        val destinationPoint = GeometryEngine.normalizeCentralMeridian(mapPoint)
+                        viewModelScope.launch {
+                            selectedFeature?.let { selectedFeature ->
+                                // Load the feature.
+                                selectedFeature.load().onSuccess {
+                                    // Update the geometry of the selected feature.
+                                    selectedFeature.geometry = destinationPoint
+                                    // Apply the edit to the feature table.
+                                    damageFeatureTable?.updateFeature(selectedFeature)
+                                    // Push the update to the service with the service geodatabase.
+                                    damageFeatureTable?.serviceGeodatabase?.applyEdits()?.onSuccess {
+                                        snackBarMessage = "Moved feature ${selectedFeature.attributes["objectid"]}"
+                                    }?.onFailure {
+                                        snackBarMessage =
+                                            "Failed to move feature ${selectedFeature.attributes["objectid"]}"
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+enum class FeatureOperationType(val operationName: String, val instruction: String) {
+    CREATE("Create feature", "Tap on the map to create a new feature."),
+    DELETE("Delete feature", "Select an existing feature to delete it."),
+    UPDATE_ATTRIBUTE("Update attribute", "Select an existing feature to edit its attribute."),
+    UPDATE_GEOMETRY("Update geometry", "Select an existing feature and tap the map to move it to a new position.")
path: samples/manage-features/src/main/java/com/esri/arcgismaps/sample/managefeatures/components/ManageFeaturesViewModel.kt
user: login: eri9000
body: ```suggestion
    UPDATE_GEOMETRY("Update location of geometry", "Select an existing feature and tap the map to move it to a new position.")
```
created_at: 2025-03-19T18:25:16Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,334 @@
+/* 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.managefeatures.components
+
+import android.app.Application
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.viewModelScope
+import com.arcgismaps.LoadStatus
+import com.arcgismaps.data.ArcGISFeature
+import com.arcgismaps.data.CodedValueDomain
+import com.arcgismaps.data.ServiceFeatureTable
+import com.arcgismaps.data.ServiceGeodatabase
+import com.arcgismaps.geometry.GeometryEngine
+import com.arcgismaps.geometry.Point
+import com.arcgismaps.geometry.SpatialReference
+import com.arcgismaps.mapping.ArcGISMap
+import com.arcgismaps.mapping.BasemapStyle
+import com.arcgismaps.mapping.Viewpoint
+import com.arcgismaps.mapping.layers.FeatureLayer
+import com.arcgismaps.mapping.view.ScreenCoordinate
+import com.arcgismaps.mapping.view.SingleTapConfirmedEvent
+import com.arcgismaps.toolkit.geoviewcompose.MapViewProxy
+import com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModel
+import kotlinx.coroutines.launch
+
+
+class ManageFeaturesViewModel(application: Application) : AndroidViewModel(application) {
+
+    val mapViewProxy = MapViewProxy()
+
+    // Hold a reference to the feature table.
+    private var damageFeatureTable: ServiceFeatureTable? = null
+
+    // Hold a reference to the feature layer.
+    private var damageLayer: FeatureLayer? = null
+
+    // Hold a reference to the selected feature.
+    var selectedFeature: ArcGISFeature? by mutableStateOf(null)
+
+    // The current feature operation to perform.
+    var currentFeatureOperation by mutableStateOf(FeatureOperationType.CREATE)
+
+    // The list of damage types to update the feature attribute.
+    var damageTypeList: List<String> = mutableListOf()
+
+    var currentDamageType by mutableStateOf("")
+
+    // Create the map with streets basemap.
+    val arcGISMap = ArcGISMap(BasemapStyle.ArcGISStreets).apply {
+        // Zoom to the United States.
+        initialViewpoint = Viewpoint(
+            Point(x = -10800000.0, y = 4500000.0, spatialReference = SpatialReference.webMercator()), scale = 3e7
+        )
+    }
+
+    // Create a snackbar message to display the result of feature operations.
+    var snackBarMessage: String by mutableStateOf("")
+
+    // Create a message dialog view model for handling error messages
+    val messageDialogVM = MessageDialogViewModel()
+
+    init {
+        viewModelScope.launch {
+            // Create a service geodatabase from the feature service.
+            val serviceGeodatabase =
+                ServiceGeodatabase("https://sampleserver6.arcgisonline.com/arcgis/rest/services/DamageAssessment/FeatureServer/0")
+            serviceGeodatabase.load().onSuccess {
+                // Get the feature table from the service geodatabase referencing the Damage Assessment feature service.
+                serviceGeodatabase.getTable(0)?.let { serviceFeatureTable ->
+                    // Load the feature table to get the coded value domain for the attribute field.
+                    serviceFeatureTable.load().onSuccess {
+                        // Hold a reference to the feature table.
+                        damageFeatureTable = serviceFeatureTable
+                        // Get the field from the feature table that will be updated.
+                        val typeDamageField = serviceFeatureTable.fields.first { it.name == "typdamage" }
+                        // Get the coded value domain for the field.
+                        val attributeDomain = typeDamageField.domain as CodedValueDomain
+                        // Add the damage types to the list.
+                        attributeDomain.codedValues.forEach {
+                            damageTypeList += it.name
+                        }
+                        // Create a feature layer to visualize the features in the table.
+                        FeatureLayer.createWithFeatureTable(serviceFeatureTable).let { featureLayer ->
+                            // Hold a reference to the feature layer.
+                            damageLayer = featureLayer
+                            // Add it to the map.
+                            arcGISMap.operationalLayers.add(featureLayer)
+                            // Load the map.
+                            arcGISMap.load().onFailure { error ->
+                                messageDialogVM.showMessageDialog(
+                                    "Failed to load map", error.message.toString()
+                                )
+                            }
+                        }
+                    }.onFailure { error ->
+                        messageDialogVM.showMessageDialog(
+                            "Failed to load feature table", error.message.toString()
+                        )
+                    }
+                }
+            }.onFailure { error ->
+                // Show the message dialog and pass the error message to be displayed in the dialog.
+                messageDialogVM.showMessageDialog(
+                    "Failed to load service geodatabase", error.message.toString()
+                )
+            }
+        }
+    }
+
+    /**
+     * Directs the behaviour of tap's on the map view.
+     */
+    fun onTap(singleTapConfirmedEvent: SingleTapConfirmedEvent) {
+        if (damageLayer?.loadStatus?.value != LoadStatus.Loaded) {
+            snackBarMessage = "Layer not loaded!"
+            return
+        }
+        when (currentFeatureOperation) {
+            FeatureOperationType.CREATE -> createFeatureAt(singleTapConfirmedEvent.screenCoordinate)
+            FeatureOperationType.DELETE -> deleteFeatureAt(singleTapConfirmedEvent.screenCoordinate)
+            FeatureOperationType.UPDATE_ATTRIBUTE -> selectFeatureForAttributeEditAt(singleTapConfirmedEvent.screenCoordinate)
+            FeatureOperationType.UPDATE_GEOMETRY -> updateFeatureGeometryAt(singleTapConfirmedEvent.screenCoordinate)
+        }
+    }
+
+    /**
+     * Set the current feature operation to perform based on the selected index from the dropdown. Also, reset feature
+     * selection.
+     */
+    fun onFeatureOperationSelected(index: Int) {
+        currentFeatureOperation = FeatureOperationType.entries[index]
+        // Reset the selected feature when the operation changes.
+        damageLayer?.clearSelection()
+        selectedFeature = null
+    }
+
+    /**
+     * Create a new feature at the tapped location with some default attributes
+     */
+    private fun createFeatureAt(screenCoordinate: ScreenCoordinate) {
+        // Create the feature.
+        val feature = damageFeatureTable?.createFeature()?.apply {
+            // Get the normalized geometry for the tapped location and use it as the feature's geometry.
+            mapViewProxy.screenToLocationOrNull(screenCoordinate)?.let { mapPoint ->
+                geometry = GeometryEngine.normalizeCentralMeridian(mapPoint)
+                // Set feature attributes.
+                attributes["typdamage"] = "Minor"
+                attributes["primcause"] = "Earthquake"
+            }
+        }
+        feature?.let {
+            viewModelScope.launch {
+                // Add the feature to the table.
+                damageFeatureTable?.addFeature(it)
+                // Apply the edits to the service on the service geodatabase.
+                damageFeatureTable?.serviceGeodatabase?.applyEdits()
+                // Update the feature to get the updated objectid - a temporary ID is used before the feature is added.
+                it.refresh()
+                // Confirm feature addition.
+                snackBarMessage = "Created feature ${it.attributes["objectid"]}"
+            }
+        }
+    }
+
+    /**
+     * Selects a feature at the tapped location in preparation for deletion.
+     */
+    private fun deleteFeatureAt(screenCoordinate: ScreenCoordinate) {
+        damageLayer?.let { damageLayer ->
+            // Clear any existing selection.
+            damageLayer.clearSelection()
+            selectedFeature = null
+            viewModelScope.launch {
+                // Determine if a user tapped on a feature.
+                mapViewProxy.identify(damageLayer, screenCoordinate, 10.dp).onSuccess { identifyResult ->
+                    selectedFeature = (identifyResult.geoElements.firstOrNull() as? ArcGISFeature)?.also {
+                        damageLayer.selectFeature(it)
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Delete the selected feature from the feature table and service geodatabase.
+     */
+    fun deleteSelectedFeature() {
+        selectedFeature?.let {
+            // Get the feature's object id.
+            val featureId = it.attributes["objectid"] as Long
+            viewModelScope.launch {
+                // Delete the feature from the feature table.
+                damageFeatureTable?.deleteFeature(it)?.onSuccess {
+                    snackBarMessage = "Deleted feature $featureId"
+                }?.onFailure {
+                    snackBarMessage = "Failed to delete feature $featureId"
+                }
+                // Apply the edits to the service geodatabase.
+                damageFeatureTable?.serviceGeodatabase?.applyEdits()
+                selectedFeature = null
path: samples/manage-features/src/main/java/com/esri/arcgismaps/sample/managefeatures/components/ManageFeaturesViewModel.kt
user: login: TADraeseke
body: Good point! Moved up to the onSuccess block
created_at: 2025-03-19T21:35:42Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,334 @@
+/* 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.managefeatures.components
+
+import android.app.Application
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.viewModelScope
+import com.arcgismaps.LoadStatus
+import com.arcgismaps.data.ArcGISFeature
+import com.arcgismaps.data.CodedValueDomain
+import com.arcgismaps.data.ServiceFeatureTable
+import com.arcgismaps.data.ServiceGeodatabase
+import com.arcgismaps.geometry.GeometryEngine
+import com.arcgismaps.geometry.Point
+import com.arcgismaps.geometry.SpatialReference
+import com.arcgismaps.mapping.ArcGISMap
+import com.arcgismaps.mapping.BasemapStyle
+import com.arcgismaps.mapping.Viewpoint
+import com.arcgismaps.mapping.layers.FeatureLayer
+import com.arcgismaps.mapping.view.ScreenCoordinate
+import com.arcgismaps.mapping.view.SingleTapConfirmedEvent
+import com.arcgismaps.toolkit.geoviewcompose.MapViewProxy
+import com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModel
+import kotlinx.coroutines.launch
+
+
+class ManageFeaturesViewModel(application: Application) : AndroidViewModel(application) {
+
+    val mapViewProxy = MapViewProxy()
+
+    // Hold a reference to the feature table.
+    private var damageFeatureTable: ServiceFeatureTable? = null
+
+    // Hold a reference to the feature layer.
+    private var damageLayer: FeatureLayer? = null
+
+    // Hold a reference to the selected feature.
+    var selectedFeature: ArcGISFeature? by mutableStateOf(null)
+
+    // The current feature operation to perform.
+    var currentFeatureOperation by mutableStateOf(FeatureOperationType.CREATE)
+
+    // The list of damage types to update the feature attribute.
+    var damageTypeList: List<String> = mutableListOf()
+
+    var currentDamageType by mutableStateOf("")
+
+    // Create the map with streets basemap.
+    val arcGISMap = ArcGISMap(BasemapStyle.ArcGISStreets).apply {
+        // Zoom to the United States.
+        initialViewpoint = Viewpoint(
+            Point(x = -10800000.0, y = 4500000.0, spatialReference = SpatialReference.webMercator()), scale = 3e7
+        )
+    }
+
+    // Create a snackbar message to display the result of feature operations.
+    var snackBarMessage: String by mutableStateOf("")
+
+    // Create a message dialog view model for handling error messages
+    val messageDialogVM = MessageDialogViewModel()
+
+    init {
+        viewModelScope.launch {
+            // Create a service geodatabase from the feature service.
+            val serviceGeodatabase =
+                ServiceGeodatabase("https://sampleserver6.arcgisonline.com/arcgis/rest/services/DamageAssessment/FeatureServer/0")
+            serviceGeodatabase.load().onSuccess {
+                // Get the feature table from the service geodatabase referencing the Damage Assessment feature service.
+                serviceGeodatabase.getTable(0)?.let { serviceFeatureTable ->
+                    // Load the feature table to get the coded value domain for the attribute field.
+                    serviceFeatureTable.load().onSuccess {
+                        // Hold a reference to the feature table.
+                        damageFeatureTable = serviceFeatureTable
+                        // Get the field from the feature table that will be updated.
+                        val typeDamageField = serviceFeatureTable.fields.first { it.name == "typdamage" }
+                        // Get the coded value domain for the field.
+                        val attributeDomain = typeDamageField.domain as CodedValueDomain
+                        // Add the damage types to the list.
+                        attributeDomain.codedValues.forEach {
+                            damageTypeList += it.name
+                        }
+                        // Create a feature layer to visualize the features in the table.
+                        FeatureLayer.createWithFeatureTable(serviceFeatureTable).let { featureLayer ->
+                            // Hold a reference to the feature layer.
+                            damageLayer = featureLayer
+                            // Add it to the map.
+                            arcGISMap.operationalLayers.add(featureLayer)
+                            // Load the map.
+                            arcGISMap.load().onFailure { error ->
+                                messageDialogVM.showMessageDialog(
+                                    "Failed to load map", error.message.toString()
+                                )
+                            }
+                        }
+                    }.onFailure { error ->
+                        messageDialogVM.showMessageDialog(
+                            "Failed to load feature table", error.message.toString()
+                        )
+                    }
+                }
+            }.onFailure { error ->
+                // Show the message dialog and pass the error message to be displayed in the dialog.
+                messageDialogVM.showMessageDialog(
+                    "Failed to load service geodatabase", error.message.toString()
+                )
+            }
+        }
+    }
+
+    /**
+     * Directs the behaviour of tap's on the map view.
+     */
+    fun onTap(singleTapConfirmedEvent: SingleTapConfirmedEvent) {
+        if (damageLayer?.loadStatus?.value != LoadStatus.Loaded) {
+            snackBarMessage = "Layer not loaded!"
+            return
+        }
+        when (currentFeatureOperation) {
+            FeatureOperationType.CREATE -> createFeatureAt(singleTapConfirmedEvent.screenCoordinate)
+            FeatureOperationType.DELETE -> deleteFeatureAt(singleTapConfirmedEvent.screenCoordinate)
+            FeatureOperationType.UPDATE_ATTRIBUTE -> selectFeatureForAttributeEditAt(singleTapConfirmedEvent.screenCoordinate)
+            FeatureOperationType.UPDATE_GEOMETRY -> updateFeatureGeometryAt(singleTapConfirmedEvent.screenCoordinate)
+        }
+    }
+
+    /**
+     * Set the current feature operation to perform based on the selected index from the dropdown. Also, reset feature
+     * selection.
+     */
+    fun onFeatureOperationSelected(index: Int) {
+        currentFeatureOperation = FeatureOperationType.entries[index]
+        // Reset the selected feature when the operation changes.
+        damageLayer?.clearSelection()
+        selectedFeature = null
+    }
+
+    /**
+     * Create a new feature at the tapped location with some default attributes
+     */
+    private fun createFeatureAt(screenCoordinate: ScreenCoordinate) {
+        // Create the feature.
+        val feature = damageFeatureTable?.createFeature()?.apply {
+            // Get the normalized geometry for the tapped location and use it as the feature's geometry.
+            mapViewProxy.screenToLocationOrNull(screenCoordinate)?.let { mapPoint ->
+                geometry = GeometryEngine.normalizeCentralMeridian(mapPoint)
+                // Set feature attributes.
+                attributes["typdamage"] = "Minor"
+                attributes["primcause"] = "Earthquake"
+            }
+        }
+        feature?.let {
+            viewModelScope.launch {
+                // Add the feature to the table.
+                damageFeatureTable?.addFeature(it)
+                // Apply the edits to the service on the service geodatabase.
+                damageFeatureTable?.serviceGeodatabase?.applyEdits()
+                // Update the feature to get the updated objectid - a temporary ID is used before the feature is added.
+                it.refresh()
+                // Confirm feature addition.
+                snackBarMessage = "Created feature ${it.attributes["objectid"]}"
+            }
+        }
+    }
+
+    /**
+     * Selects a feature at the tapped location in preparation for deletion.
+     */
+    private fun deleteFeatureAt(screenCoordinate: ScreenCoordinate) {
+        damageLayer?.let { damageLayer ->
+            // Clear any existing selection.
+            damageLayer.clearSelection()
+            selectedFeature = null
+            viewModelScope.launch {
+                // Determine if a user tapped on a feature.
+                mapViewProxy.identify(damageLayer, screenCoordinate, 10.dp).onSuccess { identifyResult ->
+                    selectedFeature = (identifyResult.geoElements.firstOrNull() as? ArcGISFeature)?.also {
+                        damageLayer.selectFeature(it)
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Delete the selected feature from the feature table and service geodatabase.
+     */
+    fun deleteSelectedFeature() {
+        selectedFeature?.let {
+            // Get the feature's object id.
+            val featureId = it.attributes["objectid"] as Long
+            viewModelScope.launch {
+                // Delete the feature from the feature table.
+                damageFeatureTable?.deleteFeature(it)?.onSuccess {
+                    snackBarMessage = "Deleted feature $featureId"
+                }?.onFailure {
+                    snackBarMessage = "Failed to delete feature $featureId"
+                }
+                // Apply the edits to the service geodatabase.
+                damageFeatureTable?.serviceGeodatabase?.applyEdits()
+                selectedFeature = null
+            }
+        }
+    }
+
+    /**
+     * Selects a feature at the tapped location in preparation for attribute editing.
+     */
+    private fun selectFeatureForAttributeEditAt(screenCoordinate: ScreenCoordinate) {
+        damageLayer?.let { damageLayer ->
+            // Clear any existing selection.
+            damageLayer.clearSelection()
+            selectedFeature = null
+            viewModelScope.launch {
+                // Determine if a user tapped on a feature.
+                mapViewProxy.identify(damageLayer, screenCoordinate, 10.dp).onSuccess { identifyResult ->
+                    // Get the identified feature.
+                    val identifiedFeature = identifyResult.geoElements.firstOrNull() as? ArcGISFeature
+                    identifiedFeature?.let {
+                        val currentAttributeValue = it.attributes["typdamage"] as String
+                        currentDamageType = currentAttributeValue
+                        selectedFeature = it.also {
+                            damageLayer.selectFeature(it)
+                        }
+                    } ?: run {
+                        // Clear the selected feature if no feature is found.
+                        damageLayer.clearSelection()
+                        selectedFeature = null
+                        currentDamageType = ""
path: samples/manage-features/src/main/java/com/esri/arcgismaps/sample/managefeatures/components/ManageFeaturesViewModel.kt
user: login: TADraeseke
body: Good point, removed!
created_at: 2025-03-19T21:38:20Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,334 @@
+/* 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.managefeatures.components
+
+import android.app.Application
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.viewModelScope
+import com.arcgismaps.LoadStatus
+import com.arcgismaps.data.ArcGISFeature
+import com.arcgismaps.data.CodedValueDomain
+import com.arcgismaps.data.ServiceFeatureTable
+import com.arcgismaps.data.ServiceGeodatabase
+import com.arcgismaps.geometry.GeometryEngine
+import com.arcgismaps.geometry.Point
+import com.arcgismaps.geometry.SpatialReference
+import com.arcgismaps.mapping.ArcGISMap
+import com.arcgismaps.mapping.BasemapStyle
+import com.arcgismaps.mapping.Viewpoint
+import com.arcgismaps.mapping.layers.FeatureLayer
+import com.arcgismaps.mapping.view.ScreenCoordinate
+import com.arcgismaps.mapping.view.SingleTapConfirmedEvent
+import com.arcgismaps.toolkit.geoviewcompose.MapViewProxy
+import com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModel
+import kotlinx.coroutines.launch
+
+
+class ManageFeaturesViewModel(application: Application) : AndroidViewModel(application) {
+
+    val mapViewProxy = MapViewProxy()
+
+    // Hold a reference to the feature table.
+    private var damageFeatureTable: ServiceFeatureTable? = null
+
+    // Hold a reference to the feature layer.
+    private var damageLayer: FeatureLayer? = null
+
+    // Hold a reference to the selected feature.
+    var selectedFeature: ArcGISFeature? by mutableStateOf(null)
+
+    // The current feature operation to perform.
+    var currentFeatureOperation by mutableStateOf(FeatureOperationType.CREATE)
+
+    // The list of damage types to update the feature attribute.
+    var damageTypeList: List<String> = mutableListOf()
+
+    var currentDamageType by mutableStateOf("")
+
+    // Create the map with streets basemap.
+    val arcGISMap = ArcGISMap(BasemapStyle.ArcGISStreets).apply {
+        // Zoom to the United States.
+        initialViewpoint = Viewpoint(
+            Point(x = -10800000.0, y = 4500000.0, spatialReference = SpatialReference.webMercator()), scale = 3e7
+        )
+    }
+
+    // Create a snackbar message to display the result of feature operations.
+    var snackBarMessage: String by mutableStateOf("")
+
+    // Create a message dialog view model for handling error messages
+    val messageDialogVM = MessageDialogViewModel()
+
+    init {
+        viewModelScope.launch {
+            // Create a service geodatabase from the feature service.
+            val serviceGeodatabase =
+                ServiceGeodatabase("https://sampleserver6.arcgisonline.com/arcgis/rest/services/DamageAssessment/FeatureServer/0")
+            serviceGeodatabase.load().onSuccess {
+                // Get the feature table from the service geodatabase referencing the Damage Assessment feature service.
+                serviceGeodatabase.getTable(0)?.let { serviceFeatureTable ->
+                    // Load the feature table to get the coded value domain for the attribute field.
+                    serviceFeatureTable.load().onSuccess {
+                        // Hold a reference to the feature table.
+                        damageFeatureTable = serviceFeatureTable
+                        // Get the field from the feature table that will be updated.
+                        val typeDamageField = serviceFeatureTable.fields.first { it.name == "typdamage" }
+                        // Get the coded value domain for the field.
+                        val attributeDomain = typeDamageField.domain as CodedValueDomain
+                        // Add the damage types to the list.
+                        attributeDomain.codedValues.forEach {
+                            damageTypeList += it.name
+                        }
+                        // Create a feature layer to visualize the features in the table.
+                        FeatureLayer.createWithFeatureTable(serviceFeatureTable).let { featureLayer ->
+                            // Hold a reference to the feature layer.
+                            damageLayer = featureLayer
+                            // Add it to the map.
+                            arcGISMap.operationalLayers.add(featureLayer)
+                            // Load the map.
+                            arcGISMap.load().onFailure { error ->
+                                messageDialogVM.showMessageDialog(
+                                    "Failed to load map", error.message.toString()
+                                )
+                            }
+                        }
+                    }.onFailure { error ->
+                        messageDialogVM.showMessageDialog(
+                            "Failed to load feature table", error.message.toString()
+                        )
+                    }
+                }
+            }.onFailure { error ->
+                // Show the message dialog and pass the error message to be displayed in the dialog.
+                messageDialogVM.showMessageDialog(
+                    "Failed to load service geodatabase", error.message.toString()
+                )
+            }
+        }
+    }
+
+    /**
+     * Directs the behaviour of tap's on the map view.
+     */
+    fun onTap(singleTapConfirmedEvent: SingleTapConfirmedEvent) {
+        if (damageLayer?.loadStatus?.value != LoadStatus.Loaded) {
+            snackBarMessage = "Layer not loaded!"
+            return
+        }
+        when (currentFeatureOperation) {
+            FeatureOperationType.CREATE -> createFeatureAt(singleTapConfirmedEvent.screenCoordinate)
+            FeatureOperationType.DELETE -> deleteFeatureAt(singleTapConfirmedEvent.screenCoordinate)
+            FeatureOperationType.UPDATE_ATTRIBUTE -> selectFeatureForAttributeEditAt(singleTapConfirmedEvent.screenCoordinate)
+            FeatureOperationType.UPDATE_GEOMETRY -> updateFeatureGeometryAt(singleTapConfirmedEvent.screenCoordinate)
+        }
+    }
+
+    /**
+     * Set the current feature operation to perform based on the selected index from the dropdown. Also, reset feature
+     * selection.
+     */
+    fun onFeatureOperationSelected(index: Int) {
+        currentFeatureOperation = FeatureOperationType.entries[index]
+        // Reset the selected feature when the operation changes.
+        damageLayer?.clearSelection()
+        selectedFeature = null
+    }
+
+    /**
+     * Create a new feature at the tapped location with some default attributes
+     */
+    private fun createFeatureAt(screenCoordinate: ScreenCoordinate) {
+        // Create the feature.
+        val feature = damageFeatureTable?.createFeature()?.apply {
+            // Get the normalized geometry for the tapped location and use it as the feature's geometry.
+            mapViewProxy.screenToLocationOrNull(screenCoordinate)?.let { mapPoint ->
+                geometry = GeometryEngine.normalizeCentralMeridian(mapPoint)
+                // Set feature attributes.
+                attributes["typdamage"] = "Minor"
+                attributes["primcause"] = "Earthquake"
+            }
+        }
+        feature?.let {
+            viewModelScope.launch {
+                // Add the feature to the table.
+                damageFeatureTable?.addFeature(it)
+                // Apply the edits to the service on the service geodatabase.
+                damageFeatureTable?.serviceGeodatabase?.applyEdits()
+                // Update the feature to get the updated objectid - a temporary ID is used before the feature is added.
+                it.refresh()
+                // Confirm feature addition.
+                snackBarMessage = "Created feature ${it.attributes["objectid"]}"
+            }
+        }
+    }
+
+    /**
+     * Selects a feature at the tapped location in preparation for deletion.
+     */
+    private fun deleteFeatureAt(screenCoordinate: ScreenCoordinate) {
+        damageLayer?.let { damageLayer ->
+            // Clear any existing selection.
+            damageLayer.clearSelection()
+            selectedFeature = null
+            viewModelScope.launch {
+                // Determine if a user tapped on a feature.
+                mapViewProxy.identify(damageLayer, screenCoordinate, 10.dp).onSuccess { identifyResult ->
+                    selectedFeature = (identifyResult.geoElements.firstOrNull() as? ArcGISFeature)?.also {
+                        damageLayer.selectFeature(it)
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Delete the selected feature from the feature table and service geodatabase.
+     */
+    fun deleteSelectedFeature() {
+        selectedFeature?.let {
+            // Get the feature's object id.
+            val featureId = it.attributes["objectid"] as Long
+            viewModelScope.launch {
+                // Delete the feature from the feature table.
+                damageFeatureTable?.deleteFeature(it)?.onSuccess {
+                    snackBarMessage = "Deleted feature $featureId"
+                }?.onFailure {
+                    snackBarMessage = "Failed to delete feature $featureId"
+                }
+                // Apply the edits to the service geodatabase.
+                damageFeatureTable?.serviceGeodatabase?.applyEdits()
+                selectedFeature = null
+            }
+        }
+    }
+
+    /**
+     * Selects a feature at the tapped location in preparation for attribute editing.
+     */
+    private fun selectFeatureForAttributeEditAt(screenCoordinate: ScreenCoordinate) {
+        damageLayer?.let { damageLayer ->
+            // Clear any existing selection.
+            damageLayer.clearSelection()
+            selectedFeature = null
+            viewModelScope.launch {
+                // Determine if a user tapped on a feature.
+                mapViewProxy.identify(damageLayer, screenCoordinate, 10.dp).onSuccess { identifyResult ->
+                    // Get the identified feature.
+                    val identifiedFeature = identifyResult.geoElements.firstOrNull() as? ArcGISFeature
+                    identifiedFeature?.let {
+                        val currentAttributeValue = it.attributes["typdamage"] as String
+                        currentDamageType = currentAttributeValue
+                        selectedFeature = it.also {
+                            damageLayer.selectFeature(it)
+                        }
+                    } ?: run {
+                        // Clear the selected feature if no feature is found.
+                        damageLayer.clearSelection()
+                        selectedFeature = null
+                        currentDamageType = ""
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Update the attribute value of the selected feature to the new value from the new damage type.
+     */
+    fun onDamageTypeSelected(index: Int) {
+        // Get the new value.
+        currentDamageType = damageTypeList[index]
+        selectedFeature?.let { selectedFeature ->
+            viewModelScope.launch {
+                // Load the feature.
+                selectedFeature.load().onSuccess {
+                    // Update the attribute value.
+                    selectedFeature.attributes["typdamage"] = currentDamageType
+                    // Update the table.
+                    damageFeatureTable?.updateFeature(selectedFeature)
+                    // Update the service on the service geodatabase.
+                    damageFeatureTable?.serviceGeodatabase?.applyEdits()?.onSuccess {
+                        snackBarMessage =
+                            "Updated feature ${selectedFeature.attributes["objectid"]} to $currentDamageType"
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Select a feature, if none is selected. If a feature is selected, update its geometry to the tapped location.
+     */
+    private fun updateFeatureGeometryAt(screenCoordinate: ScreenCoordinate) {
+
+        damageLayer?.let { damageLayer ->
+            when (selectedFeature) {
+                // When no feature is selected.
+                null -> {
path: samples/manage-features/src/main/java/com/esri/arcgismaps/sample/managefeatures/components/ManageFeaturesViewModel.kt
user: login: TADraeseke
body: I like it as a when--leaving as is
created_at: 2025-03-19T21:39:38Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,334 @@
+/* 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.managefeatures.components
+
+import android.app.Application
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.viewModelScope
+import com.arcgismaps.LoadStatus
+import com.arcgismaps.data.ArcGISFeature
+import com.arcgismaps.data.CodedValueDomain
+import com.arcgismaps.data.ServiceFeatureTable
+import com.arcgismaps.data.ServiceGeodatabase
+import com.arcgismaps.geometry.GeometryEngine
+import com.arcgismaps.geometry.Point
+import com.arcgismaps.geometry.SpatialReference
+import com.arcgismaps.mapping.ArcGISMap
+import com.arcgismaps.mapping.BasemapStyle
+import com.arcgismaps.mapping.Viewpoint
+import com.arcgismaps.mapping.layers.FeatureLayer
+import com.arcgismaps.mapping.view.ScreenCoordinate
+import com.arcgismaps.mapping.view.SingleTapConfirmedEvent
+import com.arcgismaps.toolkit.geoviewcompose.MapViewProxy
+import com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModel
+import kotlinx.coroutines.launch
+
+
+class ManageFeaturesViewModel(application: Application) : AndroidViewModel(application) {
+
+    val mapViewProxy = MapViewProxy()
+
+    // Hold a reference to the feature table.
+    private var damageFeatureTable: ServiceFeatureTable? = null
+
+    // Hold a reference to the feature layer.
+    private var damageLayer: FeatureLayer? = null
+
+    // Hold a reference to the selected feature.
+    var selectedFeature: ArcGISFeature? by mutableStateOf(null)
+
+    // The current feature operation to perform.
+    var currentFeatureOperation by mutableStateOf(FeatureOperationType.CREATE)
+
+    // The list of damage types to update the feature attribute.
+    var damageTypeList: List<String> = mutableListOf()
+
+    var currentDamageType by mutableStateOf("")
+
+    // Create the map with streets basemap.
+    val arcGISMap = ArcGISMap(BasemapStyle.ArcGISStreets).apply {
+        // Zoom to the United States.
+        initialViewpoint = Viewpoint(
+            Point(x = -10800000.0, y = 4500000.0, spatialReference = SpatialReference.webMercator()), scale = 3e7
+        )
+    }
+
+    // Create a snackbar message to display the result of feature operations.
+    var snackBarMessage: String by mutableStateOf("")
+
+    // Create a message dialog view model for handling error messages
+    val messageDialogVM = MessageDialogViewModel()
+
+    init {
+        viewModelScope.launch {
+            // Create a service geodatabase from the feature service.
+            val serviceGeodatabase =
+                ServiceGeodatabase("https://sampleserver6.arcgisonline.com/arcgis/rest/services/DamageAssessment/FeatureServer/0")
+            serviceGeodatabase.load().onSuccess {
+                // Get the feature table from the service geodatabase referencing the Damage Assessment feature service.
+                serviceGeodatabase.getTable(0)?.let { serviceFeatureTable ->
+                    // Load the feature table to get the coded value domain for the attribute field.
+                    serviceFeatureTable.load().onSuccess {
+                        // Hold a reference to the feature table.
+                        damageFeatureTable = serviceFeatureTable
+                        // Get the field from the feature table that will be updated.
+                        val typeDamageField = serviceFeatureTable.fields.first { it.name == "typdamage" }
+                        // Get the coded value domain for the field.
+                        val attributeDomain = typeDamageField.domain as CodedValueDomain
+                        // Add the damage types to the list.
+                        attributeDomain.codedValues.forEach {
+                            damageTypeList += it.name
+                        }
+                        // Create a feature layer to visualize the features in the table.
+                        FeatureLayer.createWithFeatureTable(serviceFeatureTable).let { featureLayer ->
+                            // Hold a reference to the feature layer.
+                            damageLayer = featureLayer
+                            // Add it to the map.
+                            arcGISMap.operationalLayers.add(featureLayer)
+                            // Load the map.
+                            arcGISMap.load().onFailure { error ->
+                                messageDialogVM.showMessageDialog(
+                                    "Failed to load map", error.message.toString()
+                                )
+                            }
+                        }
+                    }.onFailure { error ->
+                        messageDialogVM.showMessageDialog(
+                            "Failed to load feature table", error.message.toString()
+                        )
+                    }
+                }
+            }.onFailure { error ->
+                // Show the message dialog and pass the error message to be displayed in the dialog.
+                messageDialogVM.showMessageDialog(
+                    "Failed to load service geodatabase", error.message.toString()
+                )
+            }
+        }
+    }
+
+    /**
+     * Directs the behaviour of tap's on the map view.
+     */
+    fun onTap(singleTapConfirmedEvent: SingleTapConfirmedEvent) {
+        if (damageLayer?.loadStatus?.value != LoadStatus.Loaded) {
+            snackBarMessage = "Layer not loaded!"
+            return
+        }
+        when (currentFeatureOperation) {
+            FeatureOperationType.CREATE -> createFeatureAt(singleTapConfirmedEvent.screenCoordinate)
+            FeatureOperationType.DELETE -> deleteFeatureAt(singleTapConfirmedEvent.screenCoordinate)
+            FeatureOperationType.UPDATE_ATTRIBUTE -> selectFeatureForAttributeEditAt(singleTapConfirmedEvent.screenCoordinate)
+            FeatureOperationType.UPDATE_GEOMETRY -> updateFeatureGeometryAt(singleTapConfirmedEvent.screenCoordinate)
+        }
+    }
+
+    /**
+     * Set the current feature operation to perform based on the selected index from the dropdown. Also, reset feature
+     * selection.
+     */
+    fun onFeatureOperationSelected(index: Int) {
+        currentFeatureOperation = FeatureOperationType.entries[index]
+        // Reset the selected feature when the operation changes.
+        damageLayer?.clearSelection()
+        selectedFeature = null
+    }
+
+    /**
+     * Create a new feature at the tapped location with some default attributes
+     */
+    private fun createFeatureAt(screenCoordinate: ScreenCoordinate) {
+        // Create the feature.
+        val feature = damageFeatureTable?.createFeature()?.apply {
+            // Get the normalized geometry for the tapped location and use it as the feature's geometry.
+            mapViewProxy.screenToLocationOrNull(screenCoordinate)?.let { mapPoint ->
+                geometry = GeometryEngine.normalizeCentralMeridian(mapPoint)
+                // Set feature attributes.
+                attributes["typdamage"] = "Minor"
+                attributes["primcause"] = "Earthquake"
+            }
+        }
+        feature?.let {
+            viewModelScope.launch {
+                // Add the feature to the table.
+                damageFeatureTable?.addFeature(it)
+                // Apply the edits to the service on the service geodatabase.
+                damageFeatureTable?.serviceGeodatabase?.applyEdits()
+                // Update the feature to get the updated objectid - a temporary ID is used before the feature is added.
+                it.refresh()
+                // Confirm feature addition.
+                snackBarMessage = "Created feature ${it.attributes["objectid"]}"
+            }
+        }
+    }
+
+    /**
+     * Selects a feature at the tapped location in preparation for deletion.
+     */
+    private fun deleteFeatureAt(screenCoordinate: ScreenCoordinate) {
+        damageLayer?.let { damageLayer ->
+            // Clear any existing selection.
+            damageLayer.clearSelection()
+            selectedFeature = null
+            viewModelScope.launch {
+                // Determine if a user tapped on a feature.
+                mapViewProxy.identify(damageLayer, screenCoordinate, 10.dp).onSuccess { identifyResult ->
+                    selectedFeature = (identifyResult.geoElements.firstOrNull() as? ArcGISFeature)?.also {
+                        damageLayer.selectFeature(it)
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Delete the selected feature from the feature table and service geodatabase.
+     */
+    fun deleteSelectedFeature() {
+        selectedFeature?.let {
+            // Get the feature's object id.
+            val featureId = it.attributes["objectid"] as Long
+            viewModelScope.launch {
+                // Delete the feature from the feature table.
+                damageFeatureTable?.deleteFeature(it)?.onSuccess {
+                    snackBarMessage = "Deleted feature $featureId"
+                }?.onFailure {
+                    snackBarMessage = "Failed to delete feature $featureId"
+                }
+                // Apply the edits to the service geodatabase.
+                damageFeatureTable?.serviceGeodatabase?.applyEdits()
+                selectedFeature = null
+            }
+        }
+    }
+
+    /**
+     * Selects a feature at the tapped location in preparation for attribute editing.
+     */
+    private fun selectFeatureForAttributeEditAt(screenCoordinate: ScreenCoordinate) {
+        damageLayer?.let { damageLayer ->
+            // Clear any existing selection.
+            damageLayer.clearSelection()
+            selectedFeature = null
+            viewModelScope.launch {
+                // Determine if a user tapped on a feature.
+                mapViewProxy.identify(damageLayer, screenCoordinate, 10.dp).onSuccess { identifyResult ->
+                    // Get the identified feature.
+                    val identifiedFeature = identifyResult.geoElements.firstOrNull() as? ArcGISFeature
+                    identifiedFeature?.let {
+                        val currentAttributeValue = it.attributes["typdamage"] as String
+                        currentDamageType = currentAttributeValue
+                        selectedFeature = it.also {
+                            damageLayer.selectFeature(it)
+                        }
+                    } ?: run {
+                        // Clear the selected feature if no feature is found.
+                        damageLayer.clearSelection()
+                        selectedFeature = null
+                        currentDamageType = ""
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Update the attribute value of the selected feature to the new value from the new damage type.
+     */
+    fun onDamageTypeSelected(index: Int) {
+        // Get the new value.
+        currentDamageType = damageTypeList[index]
+        selectedFeature?.let { selectedFeature ->
+            viewModelScope.launch {
+                // Load the feature.
+                selectedFeature.load().onSuccess {
+                    // Update the attribute value.
+                    selectedFeature.attributes["typdamage"] = currentDamageType
+                    // Update the table.
+                    damageFeatureTable?.updateFeature(selectedFeature)
+                    // Update the service on the service geodatabase.
+                    damageFeatureTable?.serviceGeodatabase?.applyEdits()?.onSuccess {
+                        snackBarMessage =
+                            "Updated feature ${selectedFeature.attributes["objectid"]} to $currentDamageType"
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Select a feature, if none is selected. If a feature is selected, update its geometry to the tapped location.
+     */
+    private fun updateFeatureGeometryAt(screenCoordinate: ScreenCoordinate) {
+
+        damageLayer?.let { damageLayer ->
+            when (selectedFeature) {
+                // When no feature is selected.
+                null -> {
+                    viewModelScope.launch {
+                        // Determine if a user tapped on a feature.
+                        mapViewProxy.identify(damageLayer, screenCoordinate, 10.dp).onSuccess { identifyResult ->
+                            // Get the identified feature.
+                            val identifiedFeature = identifyResult.geoElements.firstOrNull() as? ArcGISFeature
+                            identifiedFeature?.let {
+                                selectedFeature = it.also {
+                                    damageLayer.selectFeature(it)
+                                }
+                            }
+                        }
+                    }
+                }
+                // When a feature is selected, update its geometry to the tapped location.
+                else -> {
+                    mapViewProxy.screenToLocationOrNull(screenCoordinate)?.let { mapPoint ->
+                        // Normalize the point - needed when the tapped location is over the international date line.
+                        val destinationPoint = GeometryEngine.normalizeCentralMeridian(mapPoint)
+                        viewModelScope.launch {
+                            selectedFeature?.let { selectedFeature ->
+                                // Load the feature.
+                                selectedFeature.load().onSuccess {
+                                    // Update the geometry of the selected feature.
+                                    selectedFeature.geometry = destinationPoint
+                                    // Apply the edit to the feature table.
+                                    damageFeatureTable?.updateFeature(selectedFeature)
+                                    // Push the update to the service with the service geodatabase.
+                                    damageFeatureTable?.serviceGeodatabase?.applyEdits()?.onSuccess {
+                                        snackBarMessage = "Moved feature ${selectedFeature.attributes["objectid"]}"
+                                    }?.onFailure {
+                                        snackBarMessage =
+                                            "Failed to move feature ${selectedFeature.attributes["objectid"]}"
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+enum class FeatureOperationType(val operationName: String, val instruction: String) {
+    CREATE("Create feature", "Tap on the map to create a new feature."),
+    DELETE("Delete feature", "Select an existing feature to delete it."),
+    UPDATE_ATTRIBUTE("Update attribute", "Select an existing feature to edit its attribute."),
+    UPDATE_GEOMETRY("Update geometry", "Select an existing feature and tap the map to move it to a new position.")
path: samples/manage-features/src/main/java/com/esri/arcgismaps/sample/managefeatures/components/ManageFeaturesViewModel.kt
user: login: TADraeseke
body: Leaving as is--this is in line with common sample design
created_at: 2025-03-19T21:39:53Z
author_association: COLLABORATOR

PR comments:
user: login: TADraeseke
created_at: 2025-03-18T19:24:56Z
body: Thanks @shubham7109 -- ready for re-review. enum a big improvement, thanks!
author_association: COLLABORATOR
user: login: TADraeseke
created_at: 2025-03-19T21:41:16Z
body: Thanks @eri9000! Merging!
author_association: COLLABORATOR