mapbox / mapbox/mapbox-maps-flutter

Clusters are not visible if map's style is any other than a default MapboxStyles.LIGHT

Offen
#349 2 Kommentare 1 Reaktion 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

Vorherrschende Sprache
Dart
Sterne
380
Forks
204
PR-Merge-Kennzahlen
Keine gemergten PRs in 30 T.

Beschreibung

Version mapbox_maps_flutter: ^0.5.1 is being used.
Flutter version 3.16.2

If I apply my own map style other than a default MapboxStyles.LIGHT, all clusters and unClusteredLayer are below the main map layout and not visible - means that I see them during some milliseconds (before map layout was loaded) and later map just covers them. Even if I use MapboxStyles.DARK the same problem exists.

Here is my code:

import 'dart:convert';

import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:ieye/features/map/domain/entities/lat_lng.dart';
import 'package:ieye/index.dart';
import 'package:ieye/router/router.gr.dart';
import 'package:ieye/theme.dart';
import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart';

class AnnotationClickListener extends OnPointAnnotationClickListener {
  final BuildContext context;

  AnnotationClickListener({required this.context});
  @override
  void onPointAnnotationClick(PointAnnotation annotation) {
    context.read<MapScreenBloc>().add(OnPlaceClickEvent(id: annotation.textField ?? ''));
  }
}

@RoutePage()
class MapScreen extends StatefulWidget {
  const MapScreen({super.key});

  @override
  State<MapScreen> createState() => _MapScreenState();
}

class _MapScreenState extends StateWithBLoc<MapScreenBloc, MapScreen>
    with TickerProviderStateMixin, AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  @override
  void onNewsReceived(BlocNews news) {
    if (news is ShowPlaceDetailsScreen) {
      AutoRouter.of(context).pop();
      context.router.push(PlaceDetailsRoute(place: news.place));
    } else if (news is ShowPlaceDetailsBottomSheet) {
      final place = news.place;
      final size = MediaQuery.of(context).size;
      showModalBottomSheet(
        useRootNavigator: true,
        backgroundColor: Colors.white,
        context: context,
        isScrollControlled: true,
        isDismissible: true,
        shape: const RoundedRectangleBorder(
            borderRadius: BorderRadius.vertical(top: Radius.circular(24.0))),
        clipBehavior: Clip.hardEdge,
        builder: (_) {
          return DraggableScrollableSheet(
              initialChildSize: 0.7,
              maxChildSize: ((size.height - context.statusBarHeight) / size.height) * 0.95,
              minChildSize: 0.5,
              expand: false,
              snap: false,
              builder: (_, scrollController) {
                return VenueShortBottomSheet(
                  key: ValueKey<PlaceEntity>(place),
                  scrollController: scrollController,
                  place: place,
                  onDetailsTap: () {
                    bloc.add(ShowPlaceDetailsScreenEvent(place: place));
                  },
                ); //whatever you're returning, does not have to be a Container
              });
        },
      );
    }
  }

  final pageController = PageController();
  late TextEditingController controller;
  late MapboxMap mapboxMap;

  Position center = Position(-0.1882464, 51.5382123);
  Position currentPosition = Position(-0.1882464, 51.5382123);
  double zoom = 10.0;
  bool mapLoaded = false;

  _onMapCreated(MapboxMap mapbox) async {
    setState(() {
      mapboxMap = mapbox;
      mapLoaded = true;
      mapboxMap.style;
    });
    final styleJson = await rootBundle.loadString('assets/map_styles/style.json');
    mapboxMap.style.setStyleJSON(styleJson);
    mapboxMap.compass.updateSettings(CompassSettings(enabled: false, opacity: 0.0));
    mapboxMap.logo.updateSettings(LogoSettings(marginBottom: -100.0));
    mapboxMap.attribution.updateSettings(AttributionSettings(marginBottom: -100.0));
    mapboxMap.scaleBar.updateSettings(ScaleBarSettings(enabled: false));
    mapboxMap.gestures.updateSettings(GesturesSettings(rotateEnabled: false));
    mapboxMap.location.updateSettings(LocationComponentSettings(enabled: false));
  }

  _onMapIdleListener(_) async {
    final cam = await mapboxMap.getCameraState();

    final coordinates = cam.center['coordinates'] as List;
    setState(() {
      zoom = cam.zoom;
      currentPosition = Position(
        num.tryParse(coordinates.first.toString()) ?? 0.0,
        num.tryParse(coordinates.last.toString()) ?? 0.0,
      );
    });
  }

  @override
  void initState() {
    super.initState();
    controller = TextEditingController();
  }

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

  void setZoom(double value) {
    if (value > 18 || value < 4) return;
    setState(() => zoom = value);
    mapboxMap.setCamera(CameraOptions(
      center: Point(coordinates: currentPosition).toJson(),
      zoom: value,
    ));
  }

  void setCenter() {
    mapboxMap.setCamera(CameraOptions(
      center: Point(coordinates: center).toJson(),
      zoom: zoom,
    ));
  }

  void onTap() {
    FocusScopeNode currentFocus = FocusScope.of(context);
    if (!currentFocus.hasPrimaryFocus && currentFocus.focusedChild != null) {
      FocusManager.instance.primaryFocus?.unfocus();
    }
  }

  void setMarkers(BuildContext context, List<PlaceEntity> places) async {
    mapboxMap.annotations.createPointAnnotationManager().then((pointAnnotationManager) async {
      var options = <PointAnnotationOptions>[];

      for (var i = 0; i < places.length; i++) {
        final point = points[i];
        final place = places[i];

        final widget = IEyeMarker(
          place: place,
          onDetailsTap: () {},
        );

        final Uint8List? image = await createImageFromWidget(context, widget);

        options.add(
          PointAnnotationOptions(
            geometry: Point(coordinates: Position(point.lng, point.lat)).toJson(),
            image: image,
            textField: place.id.toString(),
            textColor: 0x00000000,
            iconSize: 1,
          ),
        );
      }

      pointAnnotationManager.createMulti(options);
      pointAnnotationManager
          .addOnPointAnnotationClickListener(AnnotationClickListener(context: context));
    });

    List<Map<String, Object>> features = [];

    for (var i = 0; i < places.length; i++) {
      final point = points[i];
      final place = places[i];
      features.add({
        'type': 'Feature',
        'id': place.id,
        'properties': {
          'point_count_abbreviated': '1',
          'cluster_id': place.id,
          // 'cluster': true,
          // 'point_count': 1
        },
        'geometry': {
          'type': 'Point',
          'coordinates': [point.lng, point.lat, 0.0]
        }
      });
    }

    final data = {
      'type': 'FeatureCollection',
      'crs': {
        'type': 'name',
        'properties': {'name': 'urn:ogc:def:crs:OGC:1.3:CRS84'}
      },
      'features': features,
    };

    final layout = {
      'type': 'geojson',
      'data': data,
      'cluster': true,
      'clusterMaxZoom': 14,
      'clusterRadius': 50,
    };

    mapboxMap.style.styleSourceExists('places').then((value) async {
      if (!value) {
        var source = jsonEncode(layout);
        mapboxMap.style.addStyleSource('places', source.toString());
      }
    });

    mapboxMap.style.styleLayerExists('clusters').then((value) async {
      if (!value) {
        var layer = await rootBundle.loadString('assets/cluster/cluster_layer.json');
        mapboxMap.style.addStyleLayer(layer, null);

        var clusterCountLayer =
            await rootBundle.loadString('assets/cluster/cluster_count_layer.json');
        mapboxMap.style.addStyleLayer(clusterCountLayer, null);

        // var unClusteredLayer =
        //     await rootBundle.loadString('assets/cluster/unclustered_point_layer.json');
        // mapboxMap.style.addStyleLayer(unClusteredLayer, null);
      }
    });
  }

  void setPositionFromGps(LatLng? position) {
    setState(() => center = Position(position?.lng ?? 0.0, position?.lat ?? 0.0));

    mapboxMap.setCamera(
      CameraOptions(
        center: Point(coordinates: Position(position?.lng ?? 0.0, position?.lat ?? 0.0)).toJson(),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    super.build(context);
    final colors = context.theme.extension<IeyeColors>();
    final statusBarHeight = context.statusBarHeight;
    final navBarHeight = context.navBarHeight;
    final height = MediaQuery.of(context).size.height;

    return Scaffold(
      body: BlocConsumer<MapScreenBloc, MapScreenState>(
        listener: (BuildContext context, MapScreenState state) {
          if (state.places.isNotEmpty && mapLoaded) {
            setMarkers(context, state.places);
          }
          if (state.gpsPosition != null) {
            // setPositionFromGps(state.gpsPosition);
          }
        },
        bloc: bloc,
        builder: (context, state) {
          return Stack(
            children: [
              MapWidget(
                key: const ValueKey('mapWidget'),
                resourceOptions: ResourceOptions(
                  accessToken:
                      'pk.eyJ1IjoiaWV5ZSIsImEiOiJjbGN2dDlhaWkxMG44M3BxdHIyb3MyeHZpIn0.D9Uo5M87jYRCnHU7qDYVMQ',
                ),
                styleUri: MapboxStyles.LIGHT,
                cameraOptions: CameraOptions(
                  center: Point(coordinates: currentPosition).toJson(),
                  zoom: zoom,
                ),
                onMapCreated: (MapboxMap mapboxMap) {
                  _onMapCreated(mapboxMap);
                  context.read<MapScreenBloc>().add(OnMapInitEvent());
                },
                onMapIdleListener: _onMapIdleListener,
              ),
              Positioned(
                left: 8,
                right: 8,
                top: statusBarHeight,
                child: const MapSearchTextFieldView(),
              ),
              Positioned(
                right: 8,
                bottom: navBarHeight + 92,
                child: RoundButton(
                  onTap: setCenter,
                  iconAssetPath: UiAssets.navigationFalse,
                  borderRadius: 24,
                  backgroundColor: colors?.opacityWhite80,
                ),
              ),
              Positioned(
                right: 8,
                top: (height - 48 - 48 - 12) / 2,
                child: RoundButton(
                  onTap: () => setZoom(zoom + 1),
                  iconAssetPath: UiAssets.plus,
                  borderRadius: 24,
                  backgroundColor: colors?.opacityWhite80,
                ),
              ),
              Positioned(
                right: 8,
                bottom: (height - 48 - 48 - 12) / 2,
                child: RoundButton(
                  onTap: () => setZoom(zoom - 1),
                  iconAssetPath: UiAssets.minus,
                  borderRadius: 24,
                  backgroundColor: colors?.opacityWhite80,
                ),
              ),
            ],
          );
        },
      ),
    );
  }
}

final points = [
  LatLng(lat: 51.5382123, lng: -0.1882464),
  LatLng(lat: 51.4382123, lng: -0.1982464),
  LatLng(lat: 51.5090229, lng: -0.2886548),
  LatLng(lat: 51.1090229, lng: -0.2786548),
  LatLng(lat: 51.5054563, lng: -0.0798412),
  LatLng(lat: 51.5090215, lng: -0.1959988),
  LatLng(lat: 51.5190215, lng: -0.1859988),
  LatLng(lat: 51.5577676, lng: -0.2008447),
];

Just for info here is my flutter doctor result:

Doctor summary (to see all details, run flutter doctor -v):
[!] Flutter (Channel stable, 3.16.2, on macOS 14.2.1 23C71 darwin-arm64, locale ru-RU)
    ! Warning: `dart` on your path resolves to /opt/homebrew/Cellar/dart-sdk/3.1.5/libexec/bin/dart, which is not inside your current Flutter SDK checkout at /Users/andreifufylev/fvm/versions/3.16.2. Consider
      adding /Users/andreifufylev/fvm/versions/3.16.2/bin to the front of your path.
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 15.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2023.1)
[✓] IntelliJ IDEA Community Edition (version 2023.2.5)
[✓] VS Code (version 1.85.1)
[✓] Connected device (4 available)
[✓] Network resources

! Doctor found issues in 1 category.

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Rechercherichtung

Beginne mit _onMapCreated und setMarkers in der bereitgestellten MapScreen und untersuche anschließend assets/map_styles/style.json sowie die JSON-Dateien der Cluster-Layer. Reproduziere das Problem mit dem bereitgestellten benutzerdefinierten Style und verifiziere, dass die Cluster und der nicht geclusterte Layer sichtbar bleiben, nachdem das Laden des Styles abgeschlossen ist.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
dart
Bereich
mobile
Issue-Typ
Bug
Schwierigkeit
4/5
Geschätzter Aufwand
3-5 Tage
Aktivitätsstatus
Veraltet
Klarheit
Größtenteils klar
Anfängerfreundlichkeit
35/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.