Skip to content

Conversation

aednlaxer
Copy link
Contributor

This PR adds Advanced markers support to the web implementation of google_maps_flutter.
Approved combined PR: #7882
Approved and merged platform interface PR: #9737
Issue: #155526

Pre-Review Checklist

  • I read the [Contributor Guide] and followed the process outlined there for submitting PRs.
  • I read the [Tree Hygiene] page, which explains my responsibilities.
  • I read and followed the [relevant style guides] and ran [the auto-formatter].
  • I signed the [CLA].
  • The title of the PR starts with the name of the package surrounded by square brackets, e.g. [shared_preferences]
  • I [linked to at least one issue that this PR fixes] in the description above.
  • I updated pubspec.yaml with an appropriate new version according to the [pub versioning philosophy], or I have commented below to indicate which [version change exemption] this PR falls under[^1].
  • I updated CHANGELOG.md to add a description of the change, [following repository CHANGELOG style], or I have commented below to indicate which [CHANGELOG exemption] this PR falls under[^1].
  • I updated/added any relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or I have commented below to indicate which [test exemption] this PR falls under[^1].
  • All existing and new tests are passing.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This PR adds support for Advanced Markers to the web implementation of google_maps_flutter. The changes are extensive and well-structured, refactoring the marker handling logic to support both legacy and advanced markers through generics and abstract classes. New tests have been added for the new functionality, and existing ones have been updated. The implementation looks solid, but I've found a couple of critical issues that would prevent the code from compiling or running correctly.

Comment on lines 84 to 85
clusterManagersController: clusterManagersController!
as ClusterManagersController<gmaps.AdvancedMarkerElement>,

Choose a reason for hiding this comment

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

critical

There's a typo here. clusterManagersController is used, but it should be _clusterManagersController. This will cause a compilation error as clusterManagersController is not defined in this scope.

          clusterManagersController: _clusterManagersController!
              as ClusterManagersController<gmaps.AdvancedMarkerElement>,

Copy link
Collaborator

Choose a reason for hiding this comment

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

Obviously this does compile, but it does make it order-sensitive to when the private variable is set, which we don't want, so this should use _clusterManagersController.

Choose a reason for hiding this comment

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

Done.

import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
import 'package:sanitize_html/sanitize_html.dart';
import 'package:stream_transform/stream_transform.dart';
import 'package:web/web.dart' as web;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is this being imported twice, once with a namespace?

Choose a reason for hiding this comment

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

It's imported with namespace because some types are found from multiple packages. I think we can remove the second import and use to these types with "web" namespace everywhere.

Choose a reason for hiding this comment

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

Done.

Comment on lines 84 to 85
clusterManagersController: clusterManagersController!
as ClusterManagersController<gmaps.AdvancedMarkerElement>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Obviously this does compile, but it does make it order-sensitive to when the private variable is set, which we don't want, so this should use _clusterManagersController.

/// The `MarkerController` class wraps a [gmaps.AdvancedMarkerElement]
/// or [gmaps.Marker], how it handles events, and its associated (optional)
/// [gmaps.InfoWindow] widget.
abstract class MarkerController<T, O> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I missed this before; isn't this a breaking change for this package? It looks like this (via the part directive) is public API, and the public constructor can no longer be directly called.

That's not a significant issue if we don't expect people to generally be creating one directly, but it does mean we need to version this PR as a major version change.

Choose a reason for hiding this comment

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

Since the web package is still in the pre-release version I raised the version number to 0.6.0.

///
/// [T] must extend [JSObject]. It's not specified in code because our mocking
/// framework does not support mocking JSObjects.
abstract class MarkersController<T, O> extends GeometryController {
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; this looks breaking.

sanitize_html: ^2.0.0
stream_transform: ^2.0.0
web: ">=0.5.1 <2.0.0"
web: ">=1.0.0 <2.0.0"
Copy link
Collaborator

Choose a reason for hiding this comment

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

This can just be ^1.0.0

Choose a reason for hiding this comment

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

Done.

@stuartmorgan-g
Copy link
Collaborator

From triage: @aednlaxer Are you still planning on updating this PR?

@stuartmorgan-g
Copy link
Collaborator

FYI: After merging in main, you'll need to update the license header in all the new files with a new copy from an existing file, as the format has changed slightly.

@aednlaxer
Copy link
Contributor Author

From triage: @aednlaxer Are you still planning on updating this PR?

Yes, please don't close it

@illuminati1911 illuminati1911 force-pushed the feature/advanced_markers_google_maps_flutter_web branch from 9da73a7 to 65b8c81 Compare September 25, 2025 08:48
Copy link
Contributor

@mdebbar mdebbar left a comment

Choose a reason for hiding this comment

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

Overall, it looks good to me! I just have a few minor code improvements.

options.glyphColor = _getCssColor(circleGlyph.color);
case final TextGlyph textGlyph:
final web.Element element = web.document.createElement('p');
element.innerHTML = textGlyph.text.toJS;
Copy link
Contributor

Choose a reason for hiding this comment

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

It's safer to use element.text:

Suggested change
element.innerHTML = textGlyph.text.toJS;
element.text = textGlyph.text;

Comment on lines +476 to +479
element.setAttribute(
'style',
'color: ${_getCssColor(textGlyph.textColor!)}',
);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
element.setAttribute(
'style',
'color: ${_getCssColor(textGlyph.textColor!)}',
);
element.style.color = _getCssColor(textGlyph.textColor!);

Comment on lines +341 to +350
icon.setAttribute(
'style',
<String>[
if (size != null) ...<String>[
'width: ${size.width.toStringAsFixed(1)}px;',
'height: ${size.height.toStringAsFixed(1)}px;',
],
if (opacity != null) 'opacity: $opacity;',
if (isVisible != null) 'visibility: ${isVisible ? 'visible' : 'hidden'};',
].join(' '),
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
icon.setAttribute(
'style',
<String>[
if (size != null) ...<String>[
'width: ${size.width.toStringAsFixed(1)}px;',
'height: ${size.height.toStringAsFixed(1)}px;',
],
if (opacity != null) 'opacity: $opacity;',
if (isVisible != null) 'visibility: ${isVisible ? 'visible' : 'hidden'};',
].join(' '),
final iconStyle = icon.style;
if (size != null) {
iconStyle
..width = '${size.width.toStringAsFixed(1)}px'
..height = '${size.height.toStringAsFixed(1)}px';
}
if (opacity != null) {
iconStyle.opacity = opacity;
}
if (isVisible != null) {
iconStyle.visibility = isVisible ? 'visible' : 'hidden';
}

});

@override
void initializeMarkerListener({
Copy link
Contributor

Choose a reason for hiding this comment

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

We typically use the "add listener" terminology in the flutter code base: https://api.flutter.dev/flutter/search.html?q=addlistener

What do you think about renaming this to addMarkerListeners?

if (_infoWindow != null && newInfoWindowContent != null) {
_infoWindow.content = newInfoWindowContent;
if (onTap != null) {
marker.onClick.listen((gmaps.MapMouseEvent event) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to unsubscribe from these listeners when the marker is removed?

If yes, it can easily be done:

_subscriptions = [
  if (onTap != null) marker.onClick.listen((_) => onTap.call()),

  if (onDragStart != null) marker.onDragstart.listen((event) {
    marker.position = event.latLng;
    onDragStart.call(event.latLng ?? _nullGmapsLatLng);
  }),

  ...
];

and later in void remove():

_subscriptions?.forEach((sub) => sub.cancel);
_subscriptions = null;

required VoidCallback? onTap,
}) {
if (onTap != null) {
marker.onClick.listen((gmaps.MapMouseEvent event) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same question about unsubscribing from listeners.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants