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

[google_maps_flutter] Ground overlay support - platform impls #8563

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

jokerttu
Copy link
Contributor

@jokerttu jokerttu commented Feb 4, 2025

This PR contains platform implementations for the ground overlays support (#8432).

Linked issue: flutter/flutter#26479

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@jokerttu jokerttu force-pushed the feat/google_maps_flutter_ground_overlay_support_platform_impl branch from a859a4d to 43522ae Compare February 5, 2025 17:15
@jokerttu jokerttu marked this pull request as ready for review February 5, 2025 17:37
@stuartmorgan stuartmorgan added triage-ios Should be looked at in iOS triage triage-android Should be looked at in Android triage triage-web Should be looked at in web triage labels Feb 5, 2025
@ditman
Copy link
Member

ditman commented Feb 5, 2025

Looking at web stuff!

@reidbaker reidbaker requested a review from ash-google February 6, 2025 20:20
@stuartmorgan stuartmorgan removed triage-ios Should be looked at in iOS triage triage-android Should be looked at in Android triage labels Feb 6, 2025
Copy link
Contributor

@stuartmorgan stuartmorgan left a comment

Choose a reason for hiding this comment

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

Android and iOS LGTM; I did a full review of both. Please wait for @ditman's review covering web before landing though.

* @return the identifier of the ground overlay.
* @throws IllegalArgumentException if required fields are missing or invalid.
*/
static String interpretGroundOverlayOptions(
Copy link
Contributor

Choose a reason for hiding this comment

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

The return and the parameters should get nullability annotations.

*/
@VisibleForTesting
public static @NonNull Messages.PlatformDoublePair buildGroundOverlayAnchorForPigeon(
GroundOverlay groundOverlay) {
Copy link
Contributor

Choose a reason for hiding this comment

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

@NonNull

}

// Helper method to generate 1x1 pixel base64 encoded png test image
private String generateBase64Image() {
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be pulled out of the other file into a test helper file, rather than duplicating the code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved to TestImageUtils.java

@property(nonatomic, strong, nullable) NSNumber *zoomLevel;

/// Initializes an instance of this class with a GMSGroundOverlay, a map view, and identifier.
- (instancetype _Nullable)initWithGroundOverlay:(GMSGroundOverlay *)groundOverlay
Copy link
Contributor

Choose a reason for hiding this comment

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

Does nullable instancetype not work? (I can never remember the exact set of cases where nullable doesn't work).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I removed the nullable annotation from these instance initialisations as they do not explicitly return nil.
There isn't any handling for null instances on code either.
I would assume that self = [super init]; returns instance, and if not, something is really broken.

bitmapScaling: MapBitmapScaling.none,
);

if (groundOverlayInfo.position != null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: If you make locals for groundOverlay.position and groundOverlay.bounds just before the if block, and use the locals everywhere after that (including the conditions), you won't need most of the force-unwraps below.

@stuartmorgan
Copy link
Contributor

(@ash-google Feel free to review as well, I just had already looked over the mobile platforms in the main PR so I went ahead and did a review here to cover those.)

sink.setClickable(groundOverlay.getClickable());
sink.setImage(toBitmapDescriptor(groundOverlay.getImage(), assetManager, density, wrapper));
if (groundOverlay.getPosition() != null) {
assert groundOverlay.getWidth() != null;

Choose a reason for hiding this comment

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

This should have an assertion message indicating why it is required or at least log an error saying that width is required.

// adjust the width accordingly.
double west = bounds.southwest.longitude;
double east = bounds.northeast.longitude;
double width = (west <= east) ? (east - west) : (360.0 - (west - east));

Choose a reason for hiding this comment

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

Can we extract these constants into local constants with good naming so it's easier to understand why we need these magic numbers?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@ash-google is this what you were looking for? cdf4d25

@ash-google
Copy link

Did a second look over Android and LGTM. Let's wait for @ditman for web.

@jokerttu
Copy link
Contributor Author

jokerttu commented Feb 10, 2025

There is some crash issues on Android integration tests: Linux_android android_device_tests_shard_1 master
Haven't been able to re-produce the crash issue (with same sdk version as tests are runned). Not yet sure if these crashes are related to ground overlay implementation or not. These tests do not fail on each run, but quite often, so I would like to be sure if this is something that is not related to these changes.

@stuartmorgan
Copy link
Contributor

There is some crash issues on Android integration tests: Linux_android android_device_tests_shard_1 master

It's flutter/flutter#159731. We're seeing flare-ups of this on other PRs that touch maps, so I don't think it's related. We've seen spikes in this crash in our CI before, potentially due to server-side changes in Google Maps.

@matijakevic
Copy link

I tested this implementation a bit on Android. Unfortunately, I ran into an issue which occurs when ground overlay images change quickly (<250ms interval). This results in a quick but noticeable black flicker.

The images are stored in-memory as Uint8List and loaded through BytesMapBitmap.

The GroundOverlay has the same unchanging groundOverlayId all the time, only the image is changing.

I am not sure what causes this, maybe the thread that updates the ground overlays doesn't manage to decode bitmaps from raw data quickly enough?

I attached a video demonstrating this on Xiaomi 12T. Maybe I could try creating minimal reproducible example if needed.

out.mp4

sink.setClickable(groundOverlay.getClickable());
sink.setImage(toBitmapDescriptor(groundOverlay.getImage(), assetManager, density, wrapper));
if (groundOverlay.getPosition() != null) {
assert groundOverlay.getWidth() != null
Copy link
Contributor

Choose a reason for hiding this comment

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

By default asserts are stripped in java. If you want to check this value then you will need to throw an exception or log or do some other form of error handling.

boolean isCreatedWithBounds) {

// Dummy image is used as image is required field of PlatformGroundOverlay and converting image
// back to image descriptor is not currently supported.
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add a link to what layer of the stack does not support this functionality? Also why is it safe to use a dummy image?

Copy link
Contributor Author

@jokerttu jokerttu Feb 14, 2025

Choose a reason for hiding this comment

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

This is used only by inpector api, for testing purposes. App facing widget stores groundoverlays on dart implementation, and there is no real reason to fetch the data back, other than testing.
Image is mandatory field on PlatformGroundOverlay (and it should be kept non-nullable), therefore image must be set for the object.
The image is description either contains set of bytes, or path to asset. This info is converted to format google maps uses (BitmapDescription), and the original data is not stored on native code.
So converting read and possibly rescaled asset bitmap back orginal instruction with asset url is not currently possible without storing the original PlatformImageDescriptor to memory; which is not optimal.
Therefore using dummy image here should be fine?

Messages.PlatformGroundOverlay result =
Convert.groundOverlayToPigeon(mockGroundOverlay, "overlay_1", false);

Assert.assertEquals("overlay_1", result.getGroundOverlayId());
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider extracting a helper method that takes a actual ground overlay object and expected values and handles the assertions for equals. It would make new tests easier to write/review.

@@ -21,6 +20,7 @@ const double _kInitialZoomLevel = 5;
const CameraPosition _kInitialCameraPosition =
CameraPosition(target: _kInitialMapCenter, zoom: _kInitialZoomLevel);
const String _kCloudMapId = '000000000000000'; // Dummy map ID.
const double _floatTolerance = 1e-8;
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add a comment for why this value was chosen?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@@ -995,7 +995,7 @@ void googleMapsTests() {
},
// TODO(cyanglaz): un-skip the test when we can test this on CI with API key enabled.
// https://github.com/flutter/flutter/issues/57057
skip: Platform.isAndroid);
skip: true);
Copy link
Contributor

Choose a reason for hiding this comment

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

Did you mean to skip this for all platforms? if so should it be deleted instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, this was committed by accident. Not related to this PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is a legacy of this file starting its life as a copy of the integration tests in google_maps_flutter. In the context of google_maps_flutter_android, Platform.isAndroid is just a confusing way of writing true, so this change was good.

if so should it be deleted instead?

I would prefer we keep it, because of the TODO above. We are starting to see features that do not work without an API key, so we will need to fix the CI limitation at some point. (I was working on it with infra folks a while ago, but that got derailed.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is true; but already reverted this change before I saw @stuartmorgan:n comment about this, as this was chore change, not related to ground overlay at all. I can commit this back, if it is ok to have unrelated changes in the PR as well.

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 fine either way; if you've already reverted it don't worry about adding it back unless you feel like it.

assert(_groundOverlay != null);
setState(() {
_groundOverlay = _groundOverlay!.copyWith(
bearingParam: _groundOverlay!.bearing >= 350
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add a comment explaining where these magic numbers come from?
I believe this is just bumping the bearing 10 degrees and making sure we dont go over 360 but that is after reading the whole pr.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is this ok?

@jokerttu jokerttu requested a review from reidbaker February 19, 2025 08:39
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.

6 participants