mapbox / mapbox/mapbox-maps-flutter
Clusters are not visible if map's style is any other than a default MapboxStyles.LIGHT
Nobody has claimed this yet.
- Dominant language
- Dart
- Stars
- 380
- Forks
- 204
- PR merge metrics
- No merged PRs in 30d
Description
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:
```dart
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().add(OnPlaceClickEvent(id: annotation.textField ?? ''));
}
}
@RoutePage()
class MapScreen extends StatefulWidget {
const MapScreen({super.key});
@override
State createState() => _MapScreenState();
}
class _MapScreenState extends StateWithBLoc
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(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 places) async {
mapboxMap.annotations.createPointAnnotationManager().then((pointAnnotationManager) async {
var options = [];
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> 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();
final statusBarHeight = context.statusBarHeight;
final navBarHeight = context.navBarHeight;
final height = MediaQuery.of(context).size.height;
return Scaffold(
body: BlocConsumer(
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().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:
```dart
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.
```
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with _onMapCreated and setMarkers in the provided MapScreen, then inspect assets/map_styles/style.json and the cluster layer JSON files. Reproduce the issue with the supplied custom style and verify that clusters and the unclustered layer remain visible after the style finishes loading.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- dart
- Domain
- mobile
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100