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

Alan/generategeodatabasereplicafromfeatureservice migration #297

Merged

Conversation

alan-edi
Copy link
Collaborator

@alan-edi alan-edi commented Dec 24, 2024

Description

Migrate the generate-geodatabase-replica-from-feature-service sample from XML to Compose.

Links and Data

What To Review

How to Test

Run the sample on the sample viewer or the repo.

Copy link
Collaborator

@hud10837 hud10837 left a comment

Choose a reason for hiding this comment

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

Looks great, a few small comments

*/
fun calculateDownloadArea() {
// upper left corner of the area to take offline
val minScreenPoint = ScreenCoordinate(200.0, 200.0)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Have we double checked if this is local or global device coordinates? If it is not local to the map view, we should make sure that it is

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The doc for mapViewProxy.screenToLocationOrNull says it "Converts a screen coordinate (in pixels) to a coordinate within the mapview's spatial reference" so it seems OK to me, and it works fine.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I did some testing and also seems to work okay 👍

Copy link
Collaborator Author

@alan-edi alan-edi left a comment

Choose a reason for hiding this comment

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

@hud10837 I've adopted all your suggestions apart from one. Please have another look.

*/
fun calculateDownloadArea() {
// upper left corner of the area to take offline
val minScreenPoint = ScreenCoordinate(200.0, 200.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.

The doc for mapViewProxy.screenToLocationOrNull says it "Converts a screen coordinate (in pixels) to a coordinate within the mapview's spatial reference" so it seems OK to me, and it works fine.

@alan-edi alan-edi requested a review from hud10837 December 30, 2024 16:58
Copy link
Collaborator

@hud10837 hud10837 left a comment

Choose a reason for hiding this comment

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

LGTM 👍

Copy link
Collaborator

@01smito01 01smito01 left a comment

Choose a reason for hiding this comment

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

Have left some comments about handling UI state in a more idiomatic way, but all seems fine beyond that.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Fiddled about with the bounding box so I could get a screenshot free of watermark/UI elements. Use it if you want:
generate-geodatabase-replica-from-feature-service

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks, but actually I think it's better to show the UI elements, so I'm fairly happy with the screenshot I've got.

Comment on lines 98 to 110
// job progress dialog visibility state
var showJobProgressDialog by mutableStateOf(false)
private set

// job progress percentage
var jobProgress by mutableIntStateOf(0)
private set

// state variables indicating if the buttons are enabled
var resetButtonEnabled by mutableStateOf(false)
private set
var generateButtonEnabled by mutableStateOf(false)
private set
Copy link
Collaborator

Choose a reason for hiding this comment

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

This all works and we can keep it the way it is, but I'm a bit sceptical of having so much Compose state in the ViewModel.

There's quite a few places in the VM where buttons or dialogues are enabled/disabled, which is a layer of implicit UI state that would be better to make explicit (EG as an enum class), and expose that to the UI through a StateFlow. But it could be a lot of work.

Comment on lines 134 to 141
geodatabaseSyncTask.load().onFailure { error ->
messageDialogVM.showMessageDialog(
title = "Failed to load GeodatabaseSyncTask",
description = error.message.toString()
)
}.onSuccess {
generateButtonEnabled = true
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Think this reads a bit cleaner if onSuccess and onFailure are swapped around.

* Reset the map to its original state.
*/
fun resetMap() {
// clear any layers and symbols already on the map
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
// clear any layers and symbols already on the map
// clear any layers and symbols already on the map

Comment on lines 250 to 255
// create a flow-collection for the job's progress
viewModelScope.launch(Dispatchers.Main) {
job.progress.collect { progress ->
jobProgress = progress
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could expose job.progress to the UI directly as well - although there isn't always a generateGeodatabaseJob, so it may not be so simple.

.padding(16.dp),
horizontalArrangement = Arrangement.SpaceEvenly
) {
// the Reset Map button
Copy link
Collaborator

Choose a reason for hiding this comment

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

Think this comment (and the one for the other button) are a bit superfluous.

// display progress dialog while generating a geodatabase replica
if (mapViewModel.showJobProgressDialog) {
JobLoadingDialog(
title = "Generating geodatabase replica...",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could extract this string into strings.xml since all the other ones are.

@alan-edi
Copy link
Collaborator Author

Thanks @01smito01, good feedback. I've made changes as you suggested, including reworking the UI state handling. Please have another look.

@alan-edi alan-edi requested a review from 01smito01 January 13, 2025 14:00
Copy link
Collaborator

@01smito01 01smito01 left a comment

Choose a reason for hiding this comment

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

Looks good - a few comments on naming which I think could be clearer. The only issue is that I haven't figured out how.

READY_FOR_GENERATE,
GENERATING,
REPLICA_DISPLAYED
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Add newline


enum class UiStatus {
STARTING,
READY_FOR_GENERATE,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Reads slightly odd to me, how about READY_TO_GENERATE or READY_FOR_GENERATION?

Comment on lines 96 to 98
// state flow to expose current UI state to UI
private val _uiStateFlow = MutableStateFlow(UiState(status = UiStatus.STARTING))
val uiStateFlow = _uiStateFlow.asStateFlow()
Copy link
Collaborator

Choose a reason for hiding this comment

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

There are a lot of things called uiState/status here - wonder if some of them could be renamed for clarity.

Not 100% sure what to do: maybe UiStateGenerateGeodatabaseJobStatus or UiStatusGeodatabaseGenerationStatus?

Think the comment could be rewritten too - surely we're exposing application state to the UI, not UI state? UI state is the product of whatever data we pass to the UI.

Button(
onClick = {
mapViewModel.resetMap()
},
enabled = mapViewModel.resetButtonEnabled
enabled = uiState.status == UiStatus.REPLICA_DISPLAYED
Copy link
Collaborator

Choose a reason for hiding this comment

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

On naming: uiState.status feels particularly redundant/confusing.

@alan-edi
Copy link
Collaborator Author

@01smito01 I've renamed READY_FOR_GENERATE to READY_TO_GENERATE, and UiStatus to AppStatus.

As for UiState, that seems to me to be valid usage. The Compose docs show several examples where a ViewModel holds a UiState, for example in:

and the UI layer guide doc, when talking about State Holders, includes several diagrams showing the ViewModel holding the UI state.

Please have another look.

@alan-edi alan-edi requested a review from 01smito01 January 14, 2025 12:55
Copy link
Collaborator

@01smito01 01smito01 left a comment

Choose a reason for hiding this comment

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

LGTM 👍

@alan-edi
Copy link
Collaborator Author

Thanks @01smito01

@alan-edi alan-edi merged commit d4610d2 into v.next Jan 14, 2025
1 check passed
@alan-edi alan-edi deleted the alan/generategeodatabasereplicafromfeatureservice_migration branch January 14, 2025 15:02
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.

3 participants