Skip to content
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

FeatureForm : support for UtilityAssociations #776

Open
wants to merge 59 commits into
base: v.next
Choose a base branch
from

Conversation

kaushikrw
Copy link
Collaborator

@kaushikrw kaushikrw commented Mar 10, 2025

Related to issue: #kotlin/5456

Description:

Adds UtilityAssociations support for FeatureForm.

UtilityAssociations UI structure

  • A new UtilityAssociationsFormElement is displayed if it is part of the form info.
    • First step is to call element.fetchAssociationsFilterResults which fetches the list of associations organized within a specific structure represented by the UtilityAssociationsFilterResult class.
    • Once fetched, this element will display the list of UtilityAssociationsFilters.
    • Each filter will have a list of UtilityAssociations that are grouped by layer, sublayer, table or subtable. This is represented by the UtilityAssociationGroupResult class.
    • Finally, once you click on a group, this will display the actual UtilityAssociations present in the UtilityAssociationGroupResult.

Navigation Support

  • The component allows you to navigate to the FeatureForm of an ArcGISFeature on the other end of the UtilityAssociation. This is achieved by adding a navigation graph within the component.
  • Since with compose navigation only Serializable or Parcelable objects can be saved, for example during configuration changes, this means the FeatureForm api object cannot be used here and has to hoisted out of the navigation graph.
  • Hence, a Stack data structure is created out of the composition that will track all the FeatureForms visited. This stack will closely track the current navigation stack and provide the source of truth for the navigation destinations.

API Changes

Since the Stack has to be out of the composition, this means providing a State holder class for the FeatureForm composable. This state holder can then be hoisted up, like in a ViewModel. Hence the proposed API changes -

  • A new FeatureFormState class which internally creates and holds on to this Stack of FeatureForm api objects.
  • Provides an observable activeFeatureForm property that will always point to the current FeatureForm instance being edited.
  • Deprecate the older FeatureForm composable in favor of the new one.
    • Internally, the deprecated FeatureForm composable utilizes the FeatureFormState.
    • This composable will also not display any UtilityAssociationsFormElements and will function and look exactly like it used to.
  • Deprecated the ValidationErrorVisibility class as the updated workflow specifies to show all errors when the form is opened. This setting will possibly be part of the web map spec in a future release.
  • FeatureForm composable provides an optional onDimiss callback. If this is provided, a "close" icon is displayed which when clicked raises this event.
  • FeatureForm composable provides a boolean showFormActions. If this is true (default), A "Discard" and "Save" buttons within an action bar is displayed when there are edits in the form.
  • A new FeatureFormEditingEvent is introduced which is raised when the user "Saves" or "Discards" edits on the Form. (see ui changes below).

UI Changes

  • FeatureForm.hasEdits property is used to show an action bar on top of the form if showFormActions is true which enables two options -
    • "Save" - saves the edits using FeatureForm.finishEditing() and raises the FeatureFormEditingEvent.SavedEdits if only the result of the operation is successful.
    • "Discard" - discards any edits using FeatureForm.discarEdits() and raises the FeatureFormEditingEvent.DiscardedEdits.
  • "Close" - if the corresponding onDimiss callback is provided, a close icon is shown. If there are active edits on the form as indicated by FeatureForm.hasEdits, a dialog is presented to save or discard edits. These actions will have the same behavior as above.
  • When attempting to navigate to another Feature, the same workflow applies. If there are edits, a dialog is presented to save/discard.
  • If the user attempts to save and there are validation errors as indicated by FeatureForm.validationErrors, a dialog is presented with this information.

These changes should satisfy the workflows mentioned in the issue here.

Internal Changes

  • Since any internal states that are created for other FormElements are now stored in the FeatureFormState class, this means we no longer need any rememberSaveable methods on these elements.

Test Changes

  • Updated the tests that used any of the removed APIs.

Test Data

I've added some test data in the linked issue.

Notes

  • Tests for UN elements will be added in a separate PR. I will also update the deprecated constructor for the other tests here.
  • Readme and doc will be updated in a separate PR.

Pre-merge Checklist

@@ -23,6 +23,7 @@ plugins {
id("artifact-deploy")
id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
alias(libs.plugins.binary.compatibility.validator) apply true
alias(libs.plugins.kotlin.serialization) apply true
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Serialization plugin is needed for compose navigation. Specifically, for the route classes used.

@@ -99,7 +98,7 @@ class DateTimeFieldTests : FeatureFormTestRunner(
}
dateTimeField.assertIsDisplayed()
val helper = dateTimeField.onChildWithContentDescription("supporting text")
val helperMatcher = hasText("Date Entry is Required")
val helperMatcher = hasText("Required")
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Updated the correct text to check for.

evaluateExpressions()
}

internal constructor(
Copy link
Collaborator Author

@kaushikrw kaushikrw Mar 14, 2025

Choose a reason for hiding this comment

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

This constructor is to primarily support the deprecated FeatureForm composable which creates its own FormStateCollection.

form: FeatureForm,
elements: List<FormElement>,
scope: CoroutineScope,
ignoreList : Set<Class<out FormElement>> = emptySet(),
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This param allows the deprecated FeatureForm composable to not display any FormElements such as UtillityAssociationsFormElement.

*/
@Composable
public fun FeatureForm(
featureForm: FeatureForm,
featureFormState: FeatureFormState,
onDismiss: (() -> Unit)?,
Copy link
Collaborator Author

@kaushikrw kaushikrw Mar 14, 2025

Choose a reason for hiding this comment

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

Should this use a different mechanism to hide the close icon? For ex, use a showCloseIcon: Boolean instead of an optional parameter.

Copy link
Collaborator

Choose a reason for hiding this comment

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

use a showCloseIcon: Boolean instead of an optional parameter.

I think that would be a good replacement

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Updated!

@@ -159,8 +149,6 @@ internal class AttachmentElementState(
state.loadWithParentScope()
// scroll to the new attachment after a delay to allow the recomposition to complete
scope.launch {
delay(100)
lazyListState.scrollToItem(0)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Moved this into the composition as I was seeing an exception about doing this scrollToItem during the measurement phase.

@kaushikrw kaushikrw marked this pull request as ready for review March 15, 2025 01:54
@eri9000 eri9000 self-requested a review March 17, 2025 16:48
Copy link
Collaborator

@eri9000 eri9000 left a comment

Choose a reason for hiding this comment

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

Some initial observations/suggestions

}
)
}

// show pick a feature dialog if the layer is a sublayer
is UIState.SelectFeature -> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

could this be a navigation route?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Possibly. I think that would be a good idea. It would also mean moving other screens like the FeatureFormSheet and AddFeatureSheet into separate destinations. If its okay, I will tackle this in a separate PR.

Copy link
Collaborator

@eri9000 eri9000 left a comment

Choose a reason for hiding this comment

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

a few more comments

Comment on lines +682 to +686
DisposableEffect(state) {
onDispose {
state.setNavigationCallback(null)
state.setNavigateBack(null)
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added DisposableEffect to set the navigation events back to null.

Copy link
Collaborator

@eri9000 eri9000 left a comment

Choose a reason for hiding this comment

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

Good work, a few more things.

* Pops the current [FeatureForm] from the stack and sets the previous form as the [activeFeatureForm].
*/
internal fun popBackStack(): Boolean {
return if (store.size <= 1) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
return if (store.size <= 1) {
return if (store.isEmpty()) {

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It's actually meant to check there is more than 1 element in the stack.

Copy link
Collaborator

@eri9000 eri9000 left a comment

Choose a reason for hiding this comment

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

Great work @kaushikrw 👍🏼

Base automatically changed from feature-branches/forms to v.next March 28, 2025 23:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants