Skip to content

[google_maps_flutter_web] AdvancedMarker with BitmapDescriptor.bytes flickers during position updates #188291

Closed
flutter/packages
#11952

Description

@Gibbo97

Steps to reproduce

AdvancedMarker with BitmapDescriptor.bytes flickers/disappears on web during position updates

When using google_maps_flutter_web with AdvancedMarker icons backed by BitmapDescriptor.bytes / BytesMapBitmap, the marker content disappears briefly whenever the marker position is updated repeatedly.

This only appears to happen on web. The same marker movement is smooth when the marker uses PinConfig with a TextGlyph.

Depending on how heavy the surrounding application is, the flicker appears worse / it takes longer to finally draw the marker.

Reproduction outline

  1. Create a web GoogleMap using advanced markers.
  2. Add an AdvancedMarker whose icon is created with BitmapDescriptor.bytes(...) or BytesMapBitmap(...).
  3. Repeatedly update the marker with the same marker ID and same icon, but a new position.
  4. Observe that the marker content disappears/flickers during movement.
  5. Repeat with a PinConfig(glyph: TextGlyph(...)).
  6. Observe that the PinConfig / TextGlyph marker moves smoothly.

Investigation

The web implementation appears to rebuild and reassign AdvancedMarkerElement.content on every marker update, even when only the marker position changed.

For byte-backed bitmap markers, rebuilding the content creates a new image/blob-backed DOM node. Reassigning that content during every position update causes the marker to temporarily disappear while the image content loads/renders again.

PinConfig / TextGlyph does not show the same behavior because it creates synchronous DOM content rather than a byte-backed image that needs to reload.

Suggested fix

Only update AdvancedMarkerElement.content when the visual marker content actually changes, such as when the icon, alpha, visibility, or rotation changes. Position-only updates should update the marker position without replacing the existing DOM content.

Expected results

Updating an AdvancedMarker position should move the existing marker content smoothly, without the icon disappearing or flickering.

Actual results

When the marker uses byte-backed bitmap content, moving the marker causes the marker icon/text image to disappear from the map until the marker remains in the same position long enough for the content to render again. During frequent position updates, this creates a very flickery experience.

Code sample

Code sample
import 'dart:async';
import 'dart:math' as math;
import 'dart:typed_data';
import 'dart:ui' as ui;

import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

void main() {
  runApp(const MaterialApp(home: AdvancedMarkerBytesFlickerRepro()));
}

class AdvancedMarkerBytesFlickerRepro extends StatefulWidget {
  const AdvancedMarkerBytesFlickerRepro({super.key});

  @override
  State<AdvancedMarkerBytesFlickerRepro> createState() =>
      _AdvancedMarkerBytesFlickerReproState();
}

class _AdvancedMarkerBytesFlickerReproState
    extends State<AdvancedMarkerBytesFlickerRepro> {
  static const LatLng _center = LatLng(-33.852, 151.211);
  static const String _mapId = 'DEMO_MAP_ID';

  BitmapDescriptor? _bytesIcon;
  Timer? _timer;
  int _tick = 0;

  final BitmapDescriptor _pinIcon = BitmapDescriptor.pinConfig(
    backgroundColor: Colors.blue,
    borderColor: Colors.blue,
    glyph: const TextGlyph(text: 'PIN', textColor: Colors.white),
  );

  @override
  void initState() {
    super.initState();
    _createBytesIcon().then((BitmapDescriptor icon) {
      if (!mounted) {
        return;
      }
      setState(() {
        _bytesIcon = icon;
      });

      _timer = Timer.periodic(const Duration(milliseconds: 80), (_) {
        setState(() {
          _tick++;
        });
      });
    });
  }

  @override
  void dispose() {
    _timer?.cancel();
    super.dispose();
  }

  Set<AdvancedMarker> _markers() {
    final BitmapDescriptor? bytesIcon = _bytesIcon;
    if (bytesIcon == null) {
      return <AdvancedMarker>{};
    }

    final double phase = _tick / 8.0;
    final double latOffset = math.sin(phase) * 0.0015;

    return <AdvancedMarker>{
      AdvancedMarker(
        markerId: const MarkerId('bytes-marker'),
        position: LatLng(_center.latitude + latOffset, _center.longitude - 0.003),
        icon: bytesIcon,
      ),
      AdvancedMarker(
        markerId: const MarkerId('pin-marker'),
        position: LatLng(_center.latitude + latOffset, _center.longitude + 0.003),
        icon: _pinIcon,
      ),
    };
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GoogleMap(
        mapId: _mapId,
        markerType: GoogleMapMarkerType.advancedMarker,
        initialCameraPosition: const CameraPosition(target: _center, zoom: 16),
        markers: _markers(),
      ),
    );
  }

  Future<BitmapDescriptor> _createBytesIcon() async {
    final Uint8List bytes = await _createPngBytes('BYTES');
    return BitmapDescriptor.bytes(bytes, width: 96, height: 42);
  }

  Future<Uint8List> _createPngBytes(String text) async {
    const double width = 192;
    const double height = 84;

    final ui.PictureRecorder recorder = ui.PictureRecorder();
    final Canvas canvas = Canvas(recorder);

    final Paint fill = Paint()..color = Colors.red;
    canvas.drawRRect(
      RRect.fromRectAndRadius(
        const Rect.fromLTWH(0, 0, width, height),
        const Radius.circular(18),
      ),
      fill,
    );

    final TextPainter painter = TextPainter(
      text: TextSpan(
        text: text,
        style: const TextStyle(
          color: Colors.white,
          fontSize: 34,
          fontWeight: FontWeight.bold,
        ),
      ),
      textDirection: TextDirection.ltr,
    )..layout(maxWidth: width);

    painter.paint(
      canvas,
      Offset((width - painter.width) / 2, (height - painter.height) / 2),
    );

    final ui.Image image = await recorder.endRecording().toImage(
      width.toInt(),
      height.toInt(),
    );
    final ByteData? byteData = await image.toByteData(
      format: ui.ImageByteFormat.png,
    );

    return byteData!.buffer.asUint8List();
  }
}

Screenshots or Video

Screenshots / Video demonstration
Screen.Recording.2026-06-22.at.4.16.36.PM.mov
Screenshots / Video demonstration
Screen.Recording.2026-06-22.at.3.49.39.PM.mov

Logs

Logs
[Paste your logs here]

Flutter Doctor output

Doctor output
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.44.0, on macOS 26.4.1 25E253 darwin-arm64, locale en-NZ)
[✓] Android toolchain - develop for Android devices (Android SDK version 35.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 26.5)
[✓] Chrome - develop for the web
[✓] Connected device (2 available)
    ! Error: Browsing on the local area network for Jack’s iPad. Ensure the device is unlocked and attached with a cable or associated with the same local area network as this Mac.
      The device must be opted into Developer Mode to connect wirelessly. (code -27)
    ! Error: Browsing on the local area network for iPhone (2). Ensure the device is unlocked and attached with a cable or associated with the same local area network as this Mac.
      The device must be opted into Developer Mode to connect wirelessly. (code -27)
    ! Error: Browsing on the local area network for iPhone. Ensure the device is unlocked and attached with a cable or associated with the same local area network as this Mac.
      The device must be opted into Developer Mode to connect wirelessly. (code -27)
[✓] Network resources
• No issues found!

Activity

  1. suryaKommana2662 commented on Jun 22, 2026

    @suryaKommana2662
    Contributor

    @Gibbo97 Thanks for the detailed report, reproducible sample, investigation, video demonstrations, and the additional information regarding the proposed fix - #11952

    This issue contains sufficient information to investigate and appears to affect the web implementation of google_maps_flutter when updating AdvancedMarker instances backed by byte-based bitmap content. Routing this issue to the team-ecosystem for further investigation.

  2. added
    p: mapsGoogle Maps plugin
    platform-webWeb applications specifically
    packageflutter/packages repository. See also p: labels.
    and removed
    in triagePresently being triaged by the triage team
    on Jun 22, 2026
  3. added
    team-webOwned by Web platform team
    and removed on Jun 22, 2026
  4. added
    P2Important issues not at the top of the work list
    triaged-webTriaged by Web platform team
    on Jun 24, 2026
  5. github-actions commented on Sep 22, 2026

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  6. locked as resolved and limited conversation to collaborators on Sep 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work listp: mapsGoogle Maps pluginpackageflutter/packages repository. See also p: labels.platform-webWeb applications specificallyteam-webOwned by Web platform teamtriaged-webTriaged by Web platform team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions