Flutter google maps: Change selected marker icon dynamically - flutter

I am using google map package on my flutter project. I have a page in which google map is shown in a full screen and carousel of products (horizontally scrollable) over it. Clicking each marker will slide carousel to reveal the product info widget.
I have following codes
CameraPosition _productLocation;
Set<Marker> markers = Set();
int _currentIndex = 0;
BitmapDescriptor defaultIcon =
BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueRed); //default marker
BitmapDescriptor selectedIcon =
BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueViolet); //selected marker
#override
void initState() {
super.initState();
_productLocation = CameraPosition(
target: LatLng(0, 0),
zoom: 16,
);
if (widget.productList.length > 0) {
widget.productList.asMap().forEach((index, v) {
Marker resultMarker = Marker(
icon: _currentIndex == index ? selectedIcon : defaultIcon,
consumeTapEvents: true,
markerId: MarkerId(v.slug),
position:
LatLng(double.parse(v.latitude), double.parse(v.longitude)),
onTap: () {
setState(() {
_currentIndex = index;
});
buttonCarouselController.animateToPage(index);
});
// Add it to Set
markers.add(resultMarker);
});
}
}
#override
Widget build(BuildContext context) {
size = Screen(MediaQuery.of(context).size);
return Scaffold(
backgroundColor: Colors.white,
extendBodyBehindAppBar: true,
body: Container(
width: double.infinity,
height: double.infinity,
child: GoogleMap(
markers: markers,
mapType: MapType.normal,
initialCameraPosition: _productLocation,
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller);
},
),
),
);
Problem
The first time when we enter the page, i.e. if _currentIndex==0, the first marker is blue in color and first product info widget is shown, rest are red color. Something that doesn't work is when I click another marker, I want this marker to turn blue and rest all red. So in short, I want selected marker to be blue. Can anybody please throw some light on it?
Thanks

Your issue is that your markers set their color in initState() which only happens once for the Widget. (so the setState() fires, but the markers Set is unchanged)
You need to move the Set<Marker> markers population either into build() or refactor it so that it get's re-populated from the marker onTap()

Related

Marker positions got updated but the marker did not get redrawn on google map when using geolocator pub dev

I am trying to make a moving google map marker according to the user location, I use the geolocator listen function from the geolocator pub dev. The position of the markers got updated however the marker did not show up on the google map at all I tried using setState(){} to refresh the map, however nothing change.
Here's the function that I use
void listenToLocation()
{
Position? userposition;
final LocationSettings _locationSettings = LocationSettings(accuracy: LocationAccuracy.high,distanceFilter: 100);
userStreamlocation = Geolocator.getPositionStream(locationSettings: _locationSettings).listen(
(userposition) {
print(userposition == null ? 'Unknown' : '${userposition.latitude.toString()}, ${userposition.longitude.toString()}');
useablepos=LatLng(userposition.latitude, userposition.latitude);
_Navmarkers.remove(UserMarker);
print('USABLE POSITION:' + useablepos.toString());
UserMarker = Marker(
markerId: MarkerId('User'),
icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueCyan),
position: useablepos,
draggable: true,);
_Navmarkers.add(UserMarker);
setState(){};
print('User Location :' + UserMarker.position.toString());
});
}
I am able to remove the user marker and replace it with a new marker that have the new position however the marker did not show up on the googlemap
Here's the image to show that the LatLng keep getting updated,
Edit: UI Screenshot, there is only a single button that function to start the listen function
Update: I tried several thing and the only thing I found out is that the listen run outside the map state, I still have no idea what to do
I am able to fix this after finding out that the marker didn't got updated because it is in a different state than the main widget, I fix it trough the use of the statebuilder widget. Apparently without statefulbuilder, the setState refresh the whole app but not what the function do. here's the code snip
StatefulBuilder(
builder: (context, setMapState) {
setMapState(
() {},
);
void _whenMapCreated(GoogleMapController controller) async {
//debugPrint('>>>>>>>>> onMapCreated');
mapController = controller;
_Navmarkers.clear();
_navPolylines.clear();
var direction =
await LocationService().getDirectionNoWP(userOrigin, userGoal);
_setPolylines(direction['polyline_decoded']);
setMapState(
() {},
);
}
return Column(children: [
Expanded(
child: GoogleMap(
mapType: MapType.normal,
initialCameraPosition:
CameraPosition(target: _kGooglePlex, zoom: 11),
markers: _Navmarkers,
polylines: _navPolylines,
onMapCreated: _whenMapCreated,
)),
Row(
children: [
ButtonBar(
children: [
IconButton(
onPressed: () {
Position? userposition;
const LocationSettings _locationSettings =
LocationSettings(
accuracy: LocationAccuracy.high,
distanceFilter: 100,
);
userStreamlocation = Geolocator.getPositionStream(
locationSettings: _locationSettings)
.listen((userposition) {
_Navmarkers.clear();
_Navmarkers.add(Marker(
markerId: MarkerId('User'),
position: LatLng(userposition.latitude,
userposition.longitude),
icon: BitmapDescriptor.defaultMarkerWithHue(
BitmapDescriptor.hueRose)));
setMapState(() {
});
});
},
icon: Icon(Icons.navigation)),
IconButton(
onPressed: () {
setMapState(
() {
},
);
},
icon: Icon(Icons.refresh)),
IconButton(
onPressed: () {
dispose();
},
icon: Icon(Icons.stop))
],
)
],
)
]);
},
),

I am trying to get the latitude and longitude using mapbox but it always just print my initial location and not the updated center of camera location

I'm trying to get the the longitude and latitude of the map using mapbox, but instead of getting the lat, lng of the current camera location it only prints the intial camera location
late MapboxMapController _mapController;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: MapboxMap(
accessToken: dotenv.env['MAPBOX_ACCESS_TOKEN'],
initialCameraPosition: CameraPosition(
target: LatLng(37.7749, -122.4194),
zoom: 11.0,
),
onMapCreated: (controller) {
setState(() {
_mapController = controller;
});
},
),
),
],
),
floatingActionButton: FloatingActionButton.extended(
icon: const Icon(Icons.location_on_outlined),
onPressed: () async {
// Get the center point of the map
LatLng? center = _mapController.cameraPosition?.target;
// Do something with the picked location
print('Picked location: $center');
},
label: const Text('Pick Location')));
}
I am using web version of Mapbox, I think You need get it from Event not from your controller.
I found this package, maybe help you.
https://github.com/mapbox/mapbox-maps-flutter

Flutter Syncfusion Map move marker programmatically from MapLatLng to another

Thanks in advance for anyone that help with this question. I have one marker (blue) representing user current location of user, but thus is mock MapLatLng as the user will be able to tap on the map where they will like to go next next. I would like to know how I can programmatically move the blue to the red marker to mimic a travel. I am using flutter syncfusion map, thanks again for any feedback
You can use MapShapeLayerController.pixelToLatLng method to convert the tapped local position into the MapLatLng. To update the map zoomLevel and focalLatLng programmatically, set the new zoom level to the MapZoomPanBehavior.zoomLevel property and center position to the MapZoomPanBehavior.focalLatLng property. I have attached the code snippet for your reference.
late MapZoomPanBehavior _zoomPanBehavior;
late MapShapeLayerController _shapeLayerController;
late MapShapeSource _mapShapeSource;
late List<MapLatLng> _markers;
#override
void initState() {
_mapShapeSource =
const MapShapeSource.asset('assets/usa.json', shapeDataField: 'name');
_shapeLayerController = MapShapeLayerController();
_zoomPanBehavior = MapZoomPanBehavior();
_markers = [const MapLatLng(40.26755819027706, -74.5658675759431)];
super.initState();
}
#override
void dispose() {
_shapeLayerController.dispose();
_markers.clear();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: Stack(children: <Widget>[
_buildMapsWidget(),
GestureDetector(
onTapUp: (details) {
// Converting the tapped point into MapLatLng and adding the marker
// in that position.
final MapLatLng latLng =
_shapeLayerController.pixelToLatLng(details.localPosition);
_markers.add(latLng);
_shapeLayerController.insertMarker(_markers.length - 1);
},
),
Align(
alignment: Alignment.bottomCenter,
child: ElevatedButton(
onPressed: (() {
// Programmatically change the zoomLevel and focalLatLng with animation.
_zoomPanBehavior.zoomLevel = 3;
_zoomPanBehavior.focalLatLng = const MapLatLng(39.466, -116.83);
}),
child: const Text('Nevada, USA')),
)
]),
);
}
Widget _buildMapsWidget() {
return SfMaps(
layers: <MapLayer>[
MapShapeLayer(
source: _mapShapeSource,
showDataLabels: true,
initialMarkersCount: _markers.length,
markerBuilder: (context, index) {
return MapMarker(
latitude: _markers[index].latitude,
longitude: _markers[index].longitude,
child: const Icon(Icons.location_on, color: Colors.red),
);
},
zoomPanBehavior: _zoomPanBehavior,
controller: _shapeLayerController,
dataLabelSettings: const MapDataLabelSettings(
textStyle: TextStyle(color: Colors.black, fontSize: 10),
overflowMode: MapLabelOverflow.hide),
),
],
);
}
Also, refer to the following knowledge base and user guide documentation links to know more about updating the markers and changing zoomLevel and focalLatLng dynamically.
KB links:
Add/Update markers - https://www.syncfusion.com/kb/13025/how-to-update-the-markers-dynamically-using-flutter-maps
Update latitude and longitude programmatically - https://www.syncfusion.com/kb/12366/how-to-zoom-and-pan-programmatically-in-flutter-maps
UG Links:
Add/Update markers - https://help.syncfusion.com/flutter/maps/markers#for-shape-layer
Update latitude and longitude programmatically - https://help.syncfusion.com/flutter/maps/zoom-pan#update-the-center-latitude-and-longitude-programmatically

Flutter Google Map Markers are shown only after hot reload - using with cubit and custom marker

I'm struggling second day on this issue.
I use flutter google map to show about hundred custom markers with network image icons, that can be svg or png (using MarkerGenerator).
After opening the map page, MapCubit start to load items from API. In build i have BlocConsumer, where is listener, that build markers when loaded in that cubit and builder that build GoogleMap.
Problem is, that on first opening of page there are no images in markers, only white circle. When I tried to set one image url to all markers, it was loaded properly. Then, when i go on previous page or hot reload (not always), icons are there. On same page i have legend, that draw images from same urls, where images are set properly in most times. Sometimes it is need to go back and forward more times.
I can load icons after click on item in filter, that calls MapCubit, too.
I dont know, if it means something, but next problem, what i have is, that on release and appbundle build, no map is shown, only grey screen, buttons on side and google logo on bottom left.
I tried many tips on internet, but nothing helped.
Preview of first opening of MapPage
Preview of filter at first opening (has all icons)
Preview of second opening of MapPage
Preview of third opening of MapPage (has all icons)
MapPage (MarkerGenerator is in listener and initState becouse of two different uses that needs it)
class _MapAreaState extends State<MapArea> {
MapCubit _mapCubit;
Set<Marker> markers = {};
List<CustomMarker> markerWidgets = [];
bool markersLoaded = false;
#override
void initState() {
_mapCubit = BlocProvider.of<MapCubit>(context);
markers = {};
MarkerGenerator(
_mapCubit.state.items.map((e) => CustomMarker(type: e.type)).toList(),
(bitmaps) {
setState(() {
bitmaps.asMap().forEach((mid, bmp) {
IMapItem item = _mapCubit.state.items[mid];
markers.add(Marker(
markerId: MarkerId(item.title),
position: item.latLng,
icon: BitmapDescriptor.fromBytes(bmp),
// markerId: MarkerId(item.title),
// position: item.latLng,
onTap: () async {
await _mapCubit.showDetail(item);
}));
});
});
}).generate(context);
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
color: tercialBackgroundColor,
child: BlocConsumer<MapCubit, MapState>(
bloc: _mapCubit,
listener: (context, state) {
if (state.changedItems && state.items.isNotEmpty) {
markerWidgets = _mapCubit.state.items
.map((e) => CustomMarker(type: e.type))
.toList();
markers = {};
MarkerGenerator(markerWidgets, (bitmaps) {
setState(() {
bitmaps.asMap().forEach((mid, bmp) {
log(bmp.toString());
IMapItem item = _mapCubit.state.items[mid];
markers.add(Marker(
markerId: MarkerId(item.title),
position: item.latLng,
icon: BitmapDescriptor.fromBytes(bmp),
// markerId: MarkerId(item.title),
// position: item.latLng,
onTap: () async {
await _mapCubit.showDetail(item);
}));
});
});
}).generate(context);
}
},
builder: (context, state) {
return Stack(
children: [
GoogleMap(
zoomControlsEnabled: false,
compassEnabled: false,
markers: markers,
// markers: Set<Marker>.of(state.markers),
initialCameraPosition: CameraPosition(
target: state.items.length == 1
? state.items[0].latLng
: LatLng(49.07389317899512, 19.30980263713778),
zoom: 8.5,
),
minMaxZoomPreference: MinMaxZoomPreference(8, 22),
cameraTargetBounds: CameraTargetBounds(LatLngBounds(
northeast: LatLng(50.16477808289659, 20.56397637952818),
southwest: LatLng(48.75267922516721, 18.76330228064009),
)),
onMapCreated: (GoogleMapController controller) {
if (!_mapCubit.controller.isCompleted) {
rootBundle
.loadString('assets/googleMapsStyle.json')
.then((string) async {
controller.setMapStyle(string);
});
_mapCubit.controller.complete(controller);
log(_mapCubit.controller.toString());
log(controller.toString());
setState(() {
});
}
},
),
// if(state.items.isEmpty)
// FullScreenLoadingSpinner()
],
);
},
),
);
}
}
CustomMarker class
class CustomMarker extends StatelessWidget {
final ItemType type;
const CustomMarker({Key key, this.type}) : super(key: key);
#override
Widget build(BuildContext context) {
// precachePicture(
// svgPicture.pictureProvider,
// Get.context!,
// );
// if(type.icon is /-
return Stack(
clipBehavior: Clip.none,
children: [
Icon(
Icons.add_location,
color: type.color,
size: 56,
),
Positioned(
left: 16,
top: 10,
child: Container(
width: 24,
height: 24,
decoration: BoxDecoration(
color: primaryBackgroundColor,
borderRadius: BorderRadius.circular(10)),
child: Padding(
padding: const EdgeInsets.all(1.0),
child: Center(child: type.icon),
),
),
),
],
);
}
}
Icon setting in ItemType factory, that is used in CustomMarker
icon: map['icon'] != null
? (map['icon'] is Image
? map['icon']
: (map['icon'].substring(map['icon'].length - 4) == '.svg'
? WebsafeSvg.network(
map['icon'],
width: 18,
height: 18,
color: Colors.black,
placeholderBuilder: (BuildContext context) => Container(
padding: const EdgeInsets.all(30.0),
child: const CircularProgressIndicator()),
)
: Image.network(map['icon'])))
Lately somewhen this exception is in console
======== Exception caught by image resource service =====================
The following HttpException was thrown resolving an image codec:
, uri = https://www.xxx.sk/images/svgs/culture.png
When the exception was thrown, this was the stack:
Image provider: NetworkImage("https://www.xxx.sk/images/svgs/culture.png", scale: 1.0)
Image key: NetworkImage("https://www.xxx.sk/images/svgs/culture.png", scale: 1.0)
I dont know, what all to send, so far at least this. Thanks.

Flutter cannot refresh GoogleMap after getting coordinates

I have an app that implements GoogleMap.
I can go back to the page and get a nice map with premade settings, but I cannot make the map refresh when i press the button and nothing is set beforehand .
The widget is created on Build() and I have a button that gets the lat and lng from geolocator and also cityname reverse lookup.
That calls
SaveGPS() when it is done with everything.
I normally go directly from the Prefs.lat Prefs.lng, but I tried creating member variables _lng and _lat including a _zoom variable so that setState will work.. it does not work. Not sure what I'm doing wrong.
Container(
width: 350,
height: 350,
child: GoogleMap(
markers: Set<Marker>.of(_markers),
mapType: MapType.satellite,
initialCameraPosition:
CameraPosition(zoom: _zoom, target: LatLng(_lat, _lng))),
),
void saveGps() async {
DateTime now = DateTime.now();
var timezoneOffset = now.timeZoneOffset;
Prefs.cityName = _cityName;
Prefs.offset = timezoneOffset.inMinutes / 60;
setState(() {
_lat = Prefs.lat = _position.latitude;
_lng = Prefs.lng = _position.longitude;
_markers.clear();
_markers.add(Marker(
markerId: MarkerId(Prefs.cityName),
position: LatLng(Prefs.lat, Prefs.lng),
infoWindow: InfoWindow(
title: Prefs.cityName,
)));
_zoom = 17;
});
}
I used a map controller and did an animateCamera call. This was the only way to make it work. Other items update with setState but not this one.
// create this in your class
Completer<GoogleMapController> _controller = Completer();
// build method here..
Container(
width: 350,
height: 350,
child: GoogleMap(
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller);
},
markers: Set<Marker>.of(_markers),
mapType: MapType.satellite,
initialCameraPosition: CameraPosition(
zoom: 16, target: LatLng(Prefs.lat, Prefs.lng))),
),
When you want to save the new map or display it,
call this code here:
final GoogleMapController controller = await _controller.future;
controller.animateCamera(CameraUpdate.newCameraPosition(
CameraPosition(zoom: 17, target: LatLng(Prefs.lat, Prefs.lng))));