Skip to content

Forms: Check if map has a FeatureFormDefinition #399

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,5 @@ buildNumber=0000-snapshot
ignoreBuildNumber=true
# these versions define the dependency of the ArcGIS Maps SDK for Kotlin dependency
# and are generally not overridden at the command line unless a special build is requested.
sdkVersionNumber=200.4.0
sdkBuildNumber=
sdkVersionNumber=200.5.0
sdkBuildNumber=4212
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ import android.widget.Toast
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
Expand All @@ -41,13 +43,15 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.rounded.Warning
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
Expand Down Expand Up @@ -121,27 +125,34 @@ fun MapScreen(mapViewModel: MapViewModel = hiltViewModel(), onBackPressed: () ->
modifier = Modifier.fillMaxSize(),
topBar = {
val scope = rememberCoroutineScope()
// show the top bar which changes available actions based on if the FeatureForm is
// being shown and is in edit mode
TopFormBar(
title = mapViewModel.portalItem.title,
editingMode = uiState !is UIState.NotEditing,
onClose = {
showDiscardEditsDialog = true
},
onSave = {
scope.launch {
mapViewModel.commitEdits().onFailure {
Log.w("Forms", "Applying edits failed : ${it.message}")
Toast.makeText(
context,
"Applying edits failed : ${it.message}",
Toast.LENGTH_LONG
).show()
Box {
// show the top bar which changes available actions based on if the FeatureForm is
// being shown and is in edit mode
TopFormBar(
title = mapViewModel.portalItem.title,
editingMode = uiState is UIState.Editing,
onClose = {
showDiscardEditsDialog = true
},
onSave = {
scope.launch {
mapViewModel.commitEdits().onFailure {
Log.w("Forms", "Applying edits failed : ${it.message}")
Toast.makeText(
context,
"Applying edits failed : ${it.message}",
Toast.LENGTH_LONG
).show()
}
}
}
}) {
onBackPressed()
}) {
onBackPressed()
}
if (uiState is UIState.Loading) {
LinearProgressIndicator(modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter))
}
}
}
) { padding ->
Expand Down Expand Up @@ -195,11 +206,35 @@ fun MapScreen(mapViewModel: MapViewModel = hiltViewModel(), onBackPressed: () ->
}
}
}
if (uiState is UIState.Committing) {
SubmitForm(errors = (uiState as UIState.Committing).errors) {
mapViewModel.cancelCommit()
when (uiState) {
is UIState.Committing -> {
SubmitForm(errors = (uiState as UIState.Committing).errors) {
mapViewModel.cancelCommit()
}
}

is UIState.Switching -> {
DiscardEditsDialog(
onConfirm = { mapViewModel.selectNewFeature() },
onCancel = { mapViewModel.continueEditing() }
)
}

is UIState.NoFeatureFormDefinition -> {
NoFormDefinitionDialog(
onConfirm = {
mapViewModel.setDefaultState()
},
onCancel = {
mapViewModel.setDefaultState()
onBackPressed()
}
)
}

else -> {}
}

if (showDiscardEditsDialog) {
DiscardEditsDialog(
onConfirm = {
Expand All @@ -211,12 +246,7 @@ fun MapScreen(mapViewModel: MapViewModel = hiltViewModel(), onBackPressed: () ->
}
)
}
if (uiState is UIState.Switching) {
DiscardEditsDialog(
onConfirm = { mapViewModel.selectNewFeature() },
onCancel = { mapViewModel.continueEditing() }
)
}

}

@Composable
Expand All @@ -242,6 +272,37 @@ fun DiscardEditsDialog(onConfirm: () -> Unit, onCancel: () -> Unit) {
)
}

@Composable
fun NoFormDefinitionDialog(
onConfirm: () -> Unit,
onCancel: () -> Unit,
) {
AlertDialog(
onDismissRequest = {},
title = {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Text(text = stringResource(R.string.no_featureform_found), modifier = Modifier.weight(1f))
Image(imageVector = Icons.Rounded.Warning, contentDescription = null)
}
},
confirmButton = {
Button(onClick = onConfirm) {
Text(text = stringResource(R.string.okay))
}
},
dismissButton = {
Button(onClick = onCancel) {
Text(text = stringResource(R.string.exit))
}
},
text = {
Text(text = stringResource(R.string.no_featureform_description))
}
)
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TopFormBar(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,25 @@ sealed class UIState {
/**
* Currently not editing.
*/
object NotEditing : UIState()
data object NotEditing : UIState()

/**
* Loading state that indicates the map is being loaded.
*/
data object Loading : UIState()

/**
* No feature form definition available.
*/
data object NoFeatureFormDefinition : UIState()

/**
* Currently selecting a new Feature
*/
data class Switching(
val oldState: Editing,
val newFeature: ArcGISFeature
): UIState()
) : UIState()

/**
* In editing state with the [featureForm] with the validation error visibility given by
Expand Down Expand Up @@ -93,7 +103,7 @@ data class ErrorInfo(val fieldName: String, val error: FeatureFormValidationExce
* Base class for context aware AndroidViewModel. This class must have only a single application
* parameter.
*/
open class BaseMapViewModel(application: Application): AndroidViewModel(application)
open class BaseMapViewModel(application: Application) : AndroidViewModel(application)

/**
* A view model for the FeatureForms MapView UI
Expand All @@ -115,10 +125,34 @@ class MapViewModel @Inject constructor(

val map: ArcGISMap = ArcGISMap(portalItem)

private val _uiState: MutableState<UIState> = mutableStateOf(UIState.NotEditing)
private val _uiState: MutableState<UIState> = mutableStateOf(UIState.Loading)
val uiState: State<UIState>
get() = _uiState

init {
scope.launch {
// check if this map has a FeatureFormDefinition on any of its layers
checkFeatureFormDefinition()
}
}

private suspend fun checkFeatureFormDefinition() {
map.load()
val layer = map.operationalLayers.firstOrNull {
if (it is FeatureLayer) {
it.load()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it is necessary to load the layer here. The featureFormDefinition will be non-null before the layer is loaded if it exists. It could be loaded below in the else case so that only the layer that is needed is loaded explicitly here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sorenoid It seems like the layer needs to be loaded at this time. I can update this when that changes.

it.featureFormDefinition != null
} else {
false
}
}
_uiState.value = if (layer == null) {
UIState.NoFeatureFormDefinition
} else {
UIState.NotEditing
}
}

/**
* Apply attribute edits to the Geodatabase backing
* the ServiceFeatureTable and refresh the local feature.
Expand Down Expand Up @@ -268,6 +302,9 @@ class MapViewModel @Inject constructor(
}
}

fun setDefaultState() {
_uiState.value = UIState.NotEditing
}
}

/**
Expand Down
4 changes: 4 additions & 0 deletions microapps/FeatureFormsApp/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,8 @@
<string name="discard_edits">Discard Edits?</string>
<string name="all_changes_will_be_lost">All changes made within the form will be lost.</string>
<string name="browse_demo_maps">Browse Demo Maps</string>
<string name="no_featureform_found">No FeatureForm Found!</string>
<string name="okay">Okay</string>
<string name="exit">Exit</string>
<string name="no_featureform_description">FeatureLayers in this map do not contain a FeatureFormDefinition. Feature editing will be disabled.</string>
</resources>