Skip to content

[google_maps_flutter] share marker icons between identical bitmaps - #12957

Open
jopmiddelkamp wants to merge 1 commit into
flutter:mainfrom
jopmiddelkamp:fix/share-marker-uiimages
Open

jopmiddelkamp wants to merge 1 commit into
flutter:mainfrom
jopmiddelkamp:fix/share-marker-uiimages

Conversation

@jopmiddelkamp

Copy link
Copy Markdown

On iOS the plugin builds a new UIImage for every marker whose icon comes from BitmapDescriptor.bytes, even when two markers hand it identical bytes and identical scaling. That is PlatformBitmap.createIcon in the Swift packages and FGMIconFromBitmap in the Objective-C one.

The Google Maps SDK allocates marker texture space per UIImage instance. It does not look at the image content. So a map with 4000 markers that share 22 images still asks for 4000 texture slots, the atlases run out, and the SDK starts logging

Reached the max number of texture atlases, can not allocate more.
Failed to allocate texture space for marker

An app-level cache does not get you out of this. The app I hit it in already creates each BitmapDescriptor once and reuses it across markers, and the plugin still creates one UIImage per marker.

This change caches the finished icon in a process-wide NSCache. The key is the SHA-256 of the bytes plus bitmapScaling, imagePixelRatio, width, height and screenScale, which is every input that code path reads, so two bitmaps only share an instance when they would have produced the same image.

UIImage is immutable, so sharing one between markers is safe. NSCache is thread-safe and drops its contents when the system is short on memory. Only the bytes-map branch changes. No Dart, no platform interface.

I measured it on an iPhone 17 simulator (iOS 26.4) with GoogleMaps SDK 9.4.0, using a sample app with 4000 markers, 22 distinct images and the marker geometry from a production fleet app, which averages 24753 physical pixels per marker on a 3x screen. Same seed, same 12 pans, only the plugin swapped:

google_maps_flutter_ios Atlas errors
2.18.6 (published) 3585
this change 0

Three measurements point at the instance count rather than the images themselves. Going from 110 distinct images to 22 changed nothing, 928 errors either way. Image size does matter: at 1500 markers over the same area, 29 x 29 px icons gave 0 errors and 96 x 96 px icons gave 300. But shrinking them stops helping once you are past the limit, and halving the pixels per marker only moved the count from 3722 to 3585. Sharing the UIImage takes it to 0. That is why this tends to bite apps with large labelled markers and stays invisible in a small test.

The same change is in all three iOS implementations, so it reaches everyone. That includes google_maps_flutter_ios, which is still the iOS default_package of google_maps_flutter. The README there says the package will not receive new feature updates, and this is a bug fix, so I read that as in scope. Say if you would rather have it as a separate PR.

One thing I should be straight about: I reproduce the atlas exhaustion, not the visual corruption. In production we see markers drawn with pieces of other markers' images, or missing entirely. On the simulator the SDK logs the failure and still draws. The linked issue keeps those two apart.

Fixes flutter/flutter#193105

Pre-Review Checklist

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

Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the gemini-code-assist bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.

Footnotes

  1. Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling. 2

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request implements caching of UIImage instances created from BitmapDescriptor.bytes across several iOS Google Maps packages to prevent exhausting the Maps SDK's texture atlases. It introduces NSCache storage keyed by SHA256 hashes of the image data and scaling parameters, and adds corresponding unit tests. Feedback suggests adding a defensive check for nil or empty NSData before executing CC_SHA256 in the Objective-C implementation to avoid potential undefined behavior.

Comment on lines +205 to +215
static NSString *FGMBytesMapIconCacheKey(FGMPlatformBitmapBytesMap *bitmap, CGFloat screenScale) {
NSData *data = bitmap.byteData.data;
unsigned char digest[CC_SHA256_DIGEST_LENGTH];
CC_SHA256(data.bytes, (CC_LONG)data.length, digest);
NSData *digestData = [NSData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH];
NSString *contentHash = [digestData base64EncodedStringWithOptions:0];
long scaling = (long)bitmap.bitmapScaling;
return
[NSString stringWithFormat:@"%@|%ld|%f|%@|%@|%f", contentHash, scaling,
bitmap.imagePixelRatio, bitmap.width, bitmap.height, screenScale];
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

To ensure robust defensive programming, we should guard against potential nil or empty NSData before calling CC_SHA256. Passing a NULL pointer to CC_SHA256 (which happens if data is nil or empty) can trigger static analysis warnings or undefined behavior depending on the environment. Checking data.length > 0 before hashing is a safer approach.

static NSString *FGMBytesMapIconCacheKey(FGMPlatformBitmapBytesMap *bitmap, CGFloat screenScale) {
  NSData *data = bitmap.byteData.data;
  NSString *contentHash = @"";
  if (data.length > 0) {
    unsigned char digest[CC_SHA256_DIGEST_LENGTH];
    CC_SHA256(data.bytes, (CC_LONG)data.length, digest);
    NSData *digestData = [NSData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH];
    contentHash = [digestData base64EncodedStringWithOptions:0];
  }
  long scaling = (long)bitmap.bitmapScaling;
  return
      [NSString stringWithFormat:@"%@|%ld|%f|%@|%@|%f", contentHash, scaling,
                                 bitmap.imagePixelRatio, bitmap.width, bitmap.height, screenScale];
}

@jopmiddelkamp jopmiddelkamp changed the title Share marker icons between identical bitmaps share marker icons between identical bitmaps Sep 22, 2026
@elliette elliette changed the title share marker icons between identical bitmaps [google_maps_flutter] share marker icons between identical bitmaps Sep 22, 2026
@elliette elliette added the triage-ios Should be looked at in iOS triage label Sep 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[google_maps_flutter][ios] Corrupted and missing marker icons when many markers reuse the same bitmap descriptor

2 participants