I have a Flutter app which requires location services. I have already implemented getting a users current location, permissions, etc...
My app has to position a marker on a map when first open based on the current location of the user. (This is what I need help with)
The user can later touch the map at any other point to place the marker at that point. (This I have already done)
I have successfully created a function which can retrieve the current location of a user like this, (Do note that that function is async)
Future<LatLng> get currentLocation async {
Position pos = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high,
);
return LatLng(pos.latitude, pos.longitude);
}
I currently have LatLng markerPoint = LatLng(12.9716, 77.5946); as my initial marker point inside of my HomePage like this,
class _HomePageState extends State<HomePage> {
// Initial marker location
LatLng markerPoint = LatLng(12.9716, 77.5946);
...
But I want the current location returned by my function to replace the values given here. I am unable to do something like LatLng markerPoint = await _locationService.currentLocation; because I cannot make that part of the class async.
So, that's basically my problem. I want to initialise my variable through an asynchronous function, but I haven't found a way to do that since the area I am in, does not allow for async functions.
Help will be appreciated.
PS:
My remaining build function which sets the marker point whenever touched is over here for your reference.
#override
Widget build(BuildContext context) {
return Stack(
children: [
FlutterMap(
options: MapOptions(
onTap: (tapPosition, point) async {
setState(() {
markerPoint = point;
});
},
//
center: markerPoint,
zoom: 10.0,
// this is required to disable rotation of the map
// the map will behave wierd when you rotate it if it's deleted
// the map will not scroll properly and not place markers at the exact locations.
// if the below line in removed
interactiveFlags: InteractiveFlag.all & ~InteractiveFlag.rotate,
),
nonRotatedChildren: [
TileLayer(
urlTemplate: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
subdomains: const ['a', 'b', 'c'],
),
MarkerLayer(
markers: [
Marker(
width: 100.0,
height: 100.0,
point: markerPoint,
builder: (ctx) => const Icon(
Icons.location_on,
color: Colors.red,
size: 40,
),
),
],
),
],
),
SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Card(
child: TextField(
decoration: const InputDecoration(
prefixIcon: Icon(Icons.location_on_outlined),
hintText: "Search for a location",
contentPadding: EdgeInsets.all(16.0),
),
onTap: () async {
// This is here for debugging only, kindly ignore
await requestPermission();
},
),
),
],
),
),
),
],
);
}
hope you are calling the get current location api in the initState() method
eg :
#override
void initState() {
super.initState();
fetchCurrentLocation();
}
void fetchCurrentLocation() async{
//calling the api
var currentLocation = await _locationService.currentLocation;
//setState will update the values in real time
setState(() {
markerPoint = currentLocation
});
}
Related
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'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
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
I have a map in flutter where I have to commute the user to a specific point using directions. The red marker on the map when on pressed automatically shows directions on the bottom right but I want to show an arrow head to the user to click the directions on the bottom right because user may not notice it by themselves: https://imgur.com/a/lv9Xq9T
This is the code snippet :
void getLocation() async {
var location = await currentLocation.getLocation();
currentLocation.onLocationChanged.listen((LocationData loc) {
mapcontroller.animateCamera(CameraUpdate.newCameraPosition(CameraPosition(
target: LatLng(loc.latitude ?? 0.0, loc.longitude ?? 0.0), zoom: 5)));
setState(() {
clat = loc.latitude;
clng = loc.longitude;
_markers.addAll({
Marker(
markerId: const MarkerId('Shopkeeper Position'),
icon: BitmapDescriptor.defaultMarker,
// position: LatLng(20.7708612, 73.7235274))
position: LatLng(
widget.latitude,
widget.longitude,
),
onTap: () {
print("");
print("");
print("Marker pressed");
print("");
print("");
Container(
alignment: Alignment.bottomCenter,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Get Directions",
style: GoogleFonts.roboto(
fontSize: 40,
fontWeight: FontWeight.w800,
color: Colors.blue,
),
),
Image.asset(
'assets/arrowHead.gif',
width: 200,
height: 200,
),
],
),
);
},
)
});
});
getPolypoints();
});
}
All i get when I tap on the marker are the print statements. I would want to display the row widget as well. How can i do this ?
You need to move your directions container and show/hide depending on the onTap.
Something like this:
isTapped ?? directionsContainer : Container() // empty container if isTapped is false
Update your onTap: to update state with a boolean:
setState(() {
isTapped = !isTapped;
})
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.