Skip to content

[google_maps_flutter] TileOverlay is broken since google_maps_flutter 2.0.0 #77500

@liorboyango

Description

@liorboyango

TileOverlay is broken since version 2.0.0 of the google_maps_flutter plugin.
getTile function in the TileProvider implementation is never called.
I dug a bit deeper and found that the tileProvider inside the TileOverlay is considered null later on, even-though it was assigned with valid non-null TileProvider.
Nothing to indicate in the logs.

Steps to Reproduce

  1. Create example app with Flutter 2.0.0 & Dart 2.12.0, google_maps_flutter 2.0.1.
  2. The code in main.dart is almost exact as in the tile_overlay.dart in example/lib/
    it was also broken in runtime, throwing type '_CompactLinkedHashSet<TileOverlay?>' is not a subtype of type 'Set<TileOverlay>' in type cast so i had to change it a little bit, see main.dart below.
  3. Run the app
  4. Click on 'Add tile overlay' button
main.dart
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(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Maps 2.0.x Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: MyHomePage(title: 'Maps 2.0.x Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({
    Key? key,
    required this.title,
  }) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  GoogleMapController? controller;
  TileOverlay? _tileOverlay;

  void _onMapCreated(GoogleMapController controller) {
    this.controller = controller;
  }

  @override
  void dispose() {
    super.dispose();
  }

  void _removeTileOverlay() {
    setState(() {
      _tileOverlay = null;
    });
  }

  void _addTileOverlay() {
    final TileOverlay tileOverlay = TileOverlay(
      tileOverlayId: TileOverlayId('tile_overlay_1'),
      tileProvider: _DebugTileProvider(),
    );
    setState(() {
      _tileOverlay = tileOverlay;
    });
  }

  void _clearTileCache() {
    if (_tileOverlay != null && controller != null) {
      controller!.clearTileCache(_tileOverlay!.tileOverlayId);
    }
  }

  @override
  Widget build(BuildContext context) {
    Set<TileOverlay> overlays = <TileOverlay>{
      if (_tileOverlay != null) _tileOverlay!,
    };
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      body: Column(
        mainAxisSize: MainAxisSize.min,
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: <Widget>[
          Center(
            child: SizedBox(
              width: 350.0,
              height: 300.0,
              child: GoogleMap(
                initialCameraPosition: const CameraPosition(
                  target: LatLng(59.935460, 30.325177),
                  zoom: 7.0,
                ),
                tileOverlays: overlays,
                onMapCreated: _onMapCreated,
              ),
            ),
          ),
          TextButton(
            child: const Text('Add tile overlay'),
            onPressed: _addTileOverlay,
          ),
          TextButton(
            child: const Text('Remove tile overlay'),
            onPressed: _removeTileOverlay,
          ),
          TextButton(
            child: const Text('Clear tile cache'),
            onPressed: _clearTileCache,
          ),
        ],
      ),
    );
  }
}

class _DebugTileProvider implements TileProvider {
  _DebugTileProvider() {
    boxPaint.isAntiAlias = true;
    boxPaint.color = Colors.blue;
    boxPaint.strokeWidth = 2.0;
    boxPaint.style = PaintingStyle.stroke;
  }

  static const int width = 100;
  static const int height = 100;
  static final Paint boxPaint = Paint();
  static final TextStyle textStyle = TextStyle(
    color: Colors.red,
    fontSize: 20,
  );

  @override
  Future<Tile> getTile(int x, int y, int? zoom) async {
    final ui.PictureRecorder recorder = ui.PictureRecorder();
    final Canvas canvas = Canvas(recorder);
    final TextSpan textSpan = TextSpan(
      text: '$x,$y',
      style: textStyle,
    );
    final TextPainter textPainter = TextPainter(
      text: textSpan,
      textDirection: TextDirection.ltr,
    );
    textPainter.layout(
      minWidth: 0.0,
      maxWidth: width.toDouble(),
    );
    final Offset offset = const Offset(0, 0);
    textPainter.paint(canvas, offset);
    canvas.drawRect(
        Rect.fromLTRB(0, 0, width.toDouble(), width.toDouble()), boxPaint);
    final ui.Picture picture = recorder.endRecording();
    final Uint8List byteData = await picture
        .toImage(width, height)
        .then((ui.Image image) =>
            image.toByteData(format: ui.ImageByteFormat.png))
        .then((ByteData? byteData) => byteData!.buffer.asUint8List());
    return Tile(width, height, byteData);
  }
}

Expected results: getTile should be called, following by a custom TileOverlay that should appear on the map.

Actual results: tileProvider is null, TileProvider.noTile is returned instead of Tile from getTile. Therefore nothing happens in the UI

flutter doctor -v
[✓] Flutter (Channel stable, 2.0.0, on macOS 11.2.2 20D80 darwin-x64, locale en-IL)
    • Flutter version 2.0.0 at /Users/myuser/development/flutter
    • Framework revision 60bd88df91 (4 days ago), 2021-03-03 09:13:17 -0800
    • Engine revision 40441def69
    • Dart version 2.12.0

[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
    • Android SDK at /Users/myuser/Library/Android/sdk
    • Platform android-S, build-tools 30.0.3
    • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Xcode 12.4, Build version 12D4e
    • CocoaPods version 1.10.1

[✓] Android Studio (version 4.1)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495)

[✓] Connected device (1 available)
    • sdk gphone x86 64 (mobile) • emulator-5554 • android-x64 • Android 11 (API 30) (emulator)

• No issues found!
`
</details>
<details>
<summary>pubspec.yaml</summary>
`
name: maps_test
description: A new Flutter application.

# The following line prevents the package from being accidentally published to
# pub.dev using `pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev

# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.0+1

environment:
  sdk: ">=2.12.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter

  google_maps_flutter: ^2.0.1

dev_dependencies:
  flutter_test:
    sdk: flutter

# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

# The following section is specific to Flutter.
flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

After clicking 'Add tile overlay':

Nothing happens:

Metadata

Metadata

Labels

P1High-priority issues at the top of the work lista: null-safetySupport for Dart's null safety featurec: regressionIt was better in the past than it is nowfound in release: 2.0Found to occur in 2.0found in release: 2.1Found to occur in 2.1has reproducible stepsThe issue has been confirmed reproducible and is ready to work onp: mapsGoogle Maps pluginpackageflutter/packages repository. See also p: labels.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions