Skip to content

Show Callout Sample #137

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 7 commits into from
Oct 3, 2023
Merged

Show Callout Sample #137

merged 7 commits into from
Oct 3, 2023

Conversation

prupani-7
Copy link

Description

PR to add a new Kotlin sample "Show Callout" in Maps category.

Links and Data

Sample Epic: runtime/kotlin/issues/1117

What To Review

  • Review the code to make sure it is easy to follow like other samples on Android
  • README.md and README.metadata.json files

How to Test

Run the sample on the sample viewer or the repo.

@shubham7109 shubham7109 added the New sample New Kotlin sample using ArcGIS Maps SDK label Sep 26, 2023
@shubham7109 shubham7109 self-requested a review September 26, 2023 18:07
Copy link
Collaborator

@shubham7109 shubham7109 left a comment

Choose a reason for hiding this comment

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

Nice sample @prupani-7, just a few minor comments mainly to use the MapViewModel to update and show the callout on the MapView

Comment on lines 41 to 46
@Composable
fun ComposeMapView(
modifier: Modifier = Modifier,
mapViewModel: MapViewModel,
application: Application
) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think with this sample we should keep the business logic in the MapViewModel, where the mapPoint and the TextView would be set. The ComposeMapView should return a lambda on every tap event which is then consumed by the MainScreen similar to the Show coordinates in multiple formats sample. Would probably be helpful to have the Callout and viewpoint as properties of the MapViewState

onSingleTap = { singleTapConfirmedEvent ->
mapViewModel.onMapTapped(singleTapConfirmedEvent.mapPoint)
}

Suggested change
@Composable
fun ComposeMapView(
modifier: Modifier = Modifier,
mapViewModel: MapViewModel,
application: Application
) {
@Composable
fun ComposeMapView(
modifier: Modifier = Modifier,
mapViewModel: MapViewModel,
onSingleTap: (SingleTapConfirmedEvent) -> Unit = {}) {

Copy link
Author

@prupani-7 prupani-7 Sep 26, 2023

Choose a reason for hiding this comment

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

Yeah, aware that the business logic goes in MapViewModel, and I tried that approach first, i.e. to add a new property callout to the MapViewState. But we cannot initialize Callout, as it has no constructors

Which is why I adapted the above implemented approach

If you have thoughts on the MapViewState callout property approach , we could discuss on a call

@prupani-7
Copy link
Author

prupani-7 commented Sep 26, 2023

@shubham7109 as Discussed in a call, the logic is now in mapViewModel with calloutContent and the latlonpoint properties of MapViewState and calling callout.show() in the update block of mapView. Thanks with the related implementation suggestions!
Please re-review.

Copy link
Collaborator

@shubham7109 shubham7109 left a comment

Choose a reason for hiding this comment

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

Using the MapViewModel here looks nice! Just two minor comments, but good to approve 👍

Comment on lines 78 to 85
// launch coroutine functions in the composition's CoroutineContext
LaunchedEffect(Unit) {
launch {
mapView.onSingleTapConfirmed.collect {
onSingleTap(it)
}
}
}
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
// launch coroutine functions in the composition's CoroutineContext
LaunchedEffect(Unit) {
launch {
mapView.onSingleTapConfirmed.collect {
onSingleTap(it)
}
}
}
// launch a coroutine to detect a map tap
LaunchedEffect(Unit) {
mapView.onSingleTapConfirmed.collect {
onSingleTap(it)
}
}

I think it's creating two coroutine scopes here as the LaunchedEffect should launch a new coroutine. This seems to have been glossed over before since it is generated by the script. 😅 Maybe in the future I would like to show you the old-school Java project for the new module script and we could chat about how it works.

// launch coroutine functions in the composition's CoroutineContext
LaunchedEffect(Unit) {
launch {
mapView.onSingleTapConfirmed.collect {
onSingleTap(it)
}
}
}

Copy link
Author

@prupani-7 prupani-7 Sep 27, 2023

Choose a reason for hiding this comment

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

This seems to have been glossed over before since it is generated by the script.

Shouldn't launch be removed from the template then?

Comment on lines 46 to 47
// create a textview for the callout
mapViewState.value.calloutContent.text = application.getString(
Copy link
Collaborator

Choose a reason for hiding this comment

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

The TextView is created in the MapViewState

Suggested change
// create a textview for the callout
mapViewState.value.calloutContent.text = application.getString(
// set the callout text to display point coordinates
mapViewState.value.calloutContent.text = application.getString(

@changanxian changanxian self-requested a review September 27, 2023 15:39

fun onMapTapped(mapPoint: Point?) {
// get map point from the Single tap event
mapPoint?.let { mapPoint ->
Copy link
Collaborator

Choose a reason for hiding this comment

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

The argument mapPoint of Lamda closure shadows the mapPoint, it'd better change a different name, e.g 'point'

modifier = modifier,
factory = { mapView },
// recomposes the MapView on changes in the MapViewState
update = { mapView ->
Copy link
Collaborator

Choose a reason for hiding this comment

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

same here, The argument mapView of Lamda closure shadows the variable mapView, it'd better change a different name.

Copy link
Author

@prupani-7 prupani-7 Sep 27, 2023

Choose a reason for hiding this comment

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

I am not sure if I understand this completely.

By shadows the mapView variable do you mean the one created above val mapView = createMapViewInstance() on line 48?
This is something we follow across all samples, and part of the sample template.

To me mapView name still holds valid in the update block.

If at all, we decide to make a change, I would vote for making change to the mapView var created using createMapViewInstance() to something like mapViewInstance or mapViewObject

CC: @shubham7109 since you spearheaded these changes in the initial sample design phase

Copy link
Collaborator

Choose a reason for hiding this comment

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

I mean the line 60, the mapView overshadows the outside variable mapView

fun ComposeMapView(
modifier: Modifier = Modifier,
mapViewModel: MapViewModel,
onSingleTap: (SingleTapConfirmedEvent) -> Unit = {}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could it be better for the callback be a suspend function, onSingleTap: suspend (SingleTapConfirmedEvent) -> Unit = {}

Copy link
Author

Choose a reason for hiding this comment

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

@changanxian I don't think I understand, what value will it bring in current samples, making the onSingleTap callback a suspend function?

Copy link
Collaborator

Choose a reason for hiding this comment

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

It may not be necessary in this case, and keep the current implementation. But it will be an asynchronous call. Imagine if the onSingleTap is called in a loop, it will be different.

Copy link
Author

Choose a reason for hiding this comment

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

okay!

data class MapViewState(val application: Application) {
var arcGISMap: ArcGISMap = ArcGISMap(BasemapStyle.ArcGISNavigationNight)
var viewpoint: Viewpoint = Viewpoint(34.056295, -117.195800, 1000000.0)
var calloutContent: TextView by mutableStateOf(TextView(application))
Copy link
Collaborator

Choose a reason for hiding this comment

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

It is a little odd for me to have a UI TextView in the class mix in other non-UI classes, could it be refactored into a String? Keep the UI TextView outside of the class, we only need to keep track of the state of the contents, right?

Copy link
Author

@prupani-7 prupani-7 Sep 27, 2023

Choose a reason for hiding this comment

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

Good point, this got me thinking that the 2 variables calloutContent and latLonPoint could be MapViewModel properties instead of MapViewState properties.

image

change to

image

TextView needs an applicationContext available in the viewmodel class

@shubham7109 Since we worked on this change together, tagging you for review.

@changanxian
Copy link
Collaborator

@prupani-7 a few comments.

// launch coroutine functions in the composition's CoroutineContext
LaunchedEffect(Unit) {
mapView.onSingleTapConfirmed.collect {
onSingleTap(it)
Copy link
Collaborator

Choose a reason for hiding this comment

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

After this Friday's coffee meeting(9/29), I think you need to change this line to

launch {
                onSingleTap(it)
            }

Copy link
Collaborator

Choose a reason for hiding this comment

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

It is OK for this case, onSingleTap doesn't block anything

)

// launch coroutine functions in the composition's CoroutineContext
LaunchedEffect(Unit) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

It is a little better to change this to LaunchedEffect(mapView)

Copy link
Collaborator

Choose a reason for hiding this comment

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

it may be not necessary.

@changanxian
Copy link
Collaborator

@prupani-7 Looks good

@prupani-7
Copy link
Author

Thanks @changanxian @shubham7109 for your reviews. Merging...

@prupani-7 prupani-7 merged commit 5a9c517 into v.next Oct 3, 2023
@prupani-7 prupani-7 deleted the priy/show-callout branch October 3, 2023 17:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
New sample New Kotlin sample using ArcGIS Maps SDK
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants