I have a sample app that I am building that fetches multiple markers from Firestore and displays them on Google Maps.
The issue is that the changes (markers added/deleted/updated) aren't displayed in real time since it only rebuilds in init state. I think that I could use Stream Builder to listen to the changes and update them in real time.
I am not sure how to do it with Google Maps and Firestore, since the syntax is kinda weird with snapshots, I tried many ways, but not successful. Here's my code:
#override
void initState() {
populateMarkers();
super.initState();
}
populateMarkers() {
FirebaseFirestore.instance.collection('marker').get().then((documents) {
if (documents.docs.isNotEmpty) {
for (int i = 0; i < documents.docs.length; i++) {
initMarker(
documents.docs[i].data(), documents.docs[i].id);
}
}
});
}
void initMarker(request, requestId) {
var markerIdVal = requestId;
final MarkerId markerId = MarkerId(markerIdVal);
//creating new markers
final Marker marker = Marker(
markerId: markerId,
position:
LatLng(request['location'].latitude, request['location'].longitude),
infoWindow:
InfoWindow(title: request['name'], snippet: request['description']),
onTap: () => print('Test'),
);
setState(() {
markers[markerId] = marker;
//print(markerId);
});
}
Widget loadMap() {
return GoogleMap(
markers: Set<Marker>.of(markers.values),
mapType: MapType.normal,
initialCameraPosition:
CameraPosition(target: LatLng(43.8031287, 20.453008), zoom: 12.0),
onMapCreated: onMapCreated,
// },
);
}
and in the buider, I just call loadMap() function as the body. As I mentioned, this works fine, but I would like to use Stream Builder for these functions to update in real time. Any ideas how to do it?
This thread has a similar issue, but I feel it's not the best way to do it:
Flutter - Provide Real-Time Updates to Google Maps Using StreamBuilder
Here is the full code in case someone wants to test it out:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
class MapScreen extends StatefulWidget {
#override
_MapScreenState createState() => _MapScreenState();
}
class _MapScreenState extends State<MapScreen> {
Map<MarkerId, Marker> markers = <MarkerId, Marker>{}; //--> google
GoogleMapController _controller;
#override
void initState() {
populateMarkers();
super.initState();
}
onMapCreated(GoogleMapController controller) async {
_controller = controller;
String value = await DefaultAssetBundle.of(context)
.loadString('assets/map_style.json');
_controller.setMapStyle(value);
}
populateMarkers() {
FirebaseFirestore.instance.collection('marker').get().then((documents) {
if (documents.docs.isNotEmpty) {
for (int i = 0; i < documents.docs.length; i++) {
initMarker(
documents.docs[i].data(), documents.docs[i].id); //maybe error
//documents.docs[i].data, documents.docs[i].id
}
}
});
}
void initMarker(request, requestId) {
var markerIdVal = requestId;
final MarkerId markerId = MarkerId(markerIdVal);
//creating new markers
final Marker marker = Marker(
markerId: markerId,
position:
LatLng(request['location'].latitude, request['location'].longitude),
infoWindow:
InfoWindow(title: request['name'], snippet: request['description']),
onTap: () => print('Test'),
);
setState(() {
markers[markerId] = marker;
//print(markerId);
});
}
Widget loadMap() {
return GoogleMap(
// markers: markers.values.toSet(),
markers: Set<Marker>.of(markers.values),
mapType: MapType.normal,
initialCameraPosition:
CameraPosition(target: LatLng(44.8031267, 20.432008), zoom: 12.0),
onMapCreated: onMapCreated,
cameraTargetBounds: CameraTargetBounds(LatLngBounds(
northeast: LatLng(44.8927468, 20.5509553),
southwest: LatLng(44.7465138, 20.2757283))),
mapToolbarEnabled: false,
// onMapCreated: (GoogleMapController controller) {
// _controller = controller;
// },
);
}
void dispose() {
_controller.dispose();
super.dispose();
}
//-----------------------------------
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('App'),
centerTitle: true,
backgroundColor: Colors.blue[700],
actions: [
// ElevatedButton(
// onPressed: () => refresh(),
// child: Text('REFRESH'),
// )
],
),
body: loadMap(),
);
}
}
So you want to subscribe to a stream coming from a firestore collection, and use this data to update markers on a map in real-time, is that right?
Please note that you absolutely need to separate your services from your widgets, or you'll end up quickly with a jumbled mess of a code.
In one file you have the class defining access to your database (such as API calls, or Firestore in this case). ANY write or read with Firestore will go through this service.
class FirestoreService {
FirestoreService._();
static final instance = FirestoreService._();
Stream<List<T>> collectionStream<T>({
required String path,
required T Function(Map<String, dynamic> data, String documentID) builder,
}) {
Query query = FirebaseFirestore.instance.collection(path);
final Stream<QuerySnapshot> snapshots = query.snapshots();
return snapshots.map((snapshot) {
final result = snapshot.docs
.map((snapshot) =>
builder(snapshot.data() as Map<String, dynamic>, snapshot.id))
.where((value) => value != null)
.toList();
return result;
});
}
}
In another file, you'll have the repository service, that contains the CRUD operations. Each operation makes a call to the DB service (defined in the previous step) and uses the objects's serialization (toJson, send to DB) or parsing (fromJson, get from DB) methods.
class MarkersRepo {
final _service = FirestoreService.instance;
Stream<List<Marker>> getMarkers() {
return _service.collectionStream(
path: 'marker',
builder:(json, docId){
return Marker.fromJson(json);
}
);
}
}
In another file, you define your Marker object model with the serialization and parsing methods. Please don't use strings to access directly the document properties in your code, as that is error-prone and again will cause messy code. Instead, you define those once and for all in the object model.
class Marker{
//properties of the object such as location, name and description
//toJson and fromJson methods
}
And finally in your widget file, as you noted yourself you are only reading the document once, in the initstate, so the view does not update. Instead, one simple option is to have a StreamBuilder inside your build method, extract the markers there and then display them:
Widget build(Buildcontext context){
return StreamBuilder(
stream: MarkersRepos().getMarkers(),
builder:(context, snapshot){
//check for connection state and errors, see streambuilder documentation
final List<Marker> markers = snapshot.data;
return loadMap(markers);
}
);
}
EDIT: added more details
Related
This code successfully shows nearby restaurants using Google Places API based on my current location. When I manually move the map position on my phone, though, new restaurant markers do not show in the new location of the map. Below is my code. I also have commented out something I was trying to do to update the markers to the new camera position. I used onCameraMove in the Google Maps. Any help with updating new restaurant markers based on the new camera position is greatly appreciated.
import 'package:family_farms_forever/utilities/secrets.dart';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:google_maps_webservice/places.dart';
import 'package:google_maps_flutter_platform_interface/src/types/marker_updates.dart';
void main() => runApp(const MyApp());
final places = GoogleMapsPlaces(apiKey: Secrets.iosApiKey);
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Market Map",
home: Scaffold(
// We'll change the AppBar title later
appBar: AppBar(title: const Text("My Location")),
body: const MyMarketMap()),
);
}
}
class MyMarketMap extends StatefulWidget {
const MyMarketMap({super.key});
#override
State<StatefulWidget> createState() {
return _MyMarketMapState();
}
}
class _MyMarketMapState extends State<MyMarketMap> {
late Future<Position> _currentLocation;
late final Set<Marker> _markers = {};
#override
void initState() {
super.initState();
_currentLocation = Geolocator.getCurrentPosition();
}
Future<void> _retrieveNearbyRestaurants(LatLng userLocation) async {
PlacesSearchResponse response = await places.searchNearbyWithRadius(
Location(lat: userLocation.latitude, lng: userLocation.longitude),
10000,
type: "restaurant");
Set<Marker> restaurantMarkers = response.results
.map((result) => Marker(
markerId: MarkerId(result.name),
// Use an icon with different colors to differentiate between current location
// and the restaurants
icon: BitmapDescriptor.defaultMarkerWithHue(
BitmapDescriptor.hueAzure),
infoWindow: InfoWindow(
title: result.name,
snippet:
"Ratings: ${result.rating?.toString() ?? "Not Rated"}"),
position: LatLng(
result.geometry!.location.lat, result.geometry!.location.lng)))
.toSet();
setState(() {
_markers.addAll(restaurantMarkers);
});
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _currentLocation,
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasData) {
// The user location returned from the snapshot
Position snapshotData = snapshot.data;
LatLng userLocation =
LatLng(snapshotData.latitude, snapshotData.longitude);
if (_markers.isEmpty) {
_retrieveNearbyRestaurants(userLocation);
}
// void upDateMarkers() {
// Set<Marker> updatedMarkers =
// {}; //new markers with updated position go here
// updatedMarkers = {};
// /// Then call the SetState function.
// /// I called the MarkersUpdate class inside the setState function.
// /// You can do it your way but remember to call the setState function so that the updated markers reflect on your Flutter app.
// /// Ps: I did not try the second way where the MarkerUpdate is called outside the setState buttechnically it should work.
// setState(() {
// MarkerUpdates.from(Set<Marker>.from(_markers),
// Set<Marker>.from(updatedMarkers));
// _markers = updatedMarkers;
// _markers.addAll(updatedMarkers);
// //swap of markers so that on next marker update the previous marker would be the one which you updated now.
// // And even on the next app startup, it takes the updated markers to show on the map.
// });
// }
return GoogleMap(
myLocationEnabled: true,
zoomGesturesEnabled: true,
//onCameraMove: (position) => upDateMarkers(),
initialCameraPosition: CameraPosition(
target: userLocation,
zoom: 12,
),
markers: _markers
..add(Marker(
markerId: const MarkerId("User Location"),
infoWindow: const InfoWindow(title: "User Location"),
position: userLocation)),
);
} else {
return const Center(child: Text("Failed to get user location."));
}
}
// While the connection is not in the done state yet
return const Center(child: CircularProgressIndicator());
});
}
}
You have to use Stream for getting updates :
StreamBuilder<Position>(
stream: Geolocator.getPositionStream(
locationSettings: LocationSettings()
),
builder: (context, snapshot) {
... Do your Rest Coding Here...
You can control the behavior of the stream by specifying an instance of the [LocationSettings] class for the [locationSettings] parameter. Standard settings are:
LocationSettings.accuracy: allows controlling the precision of the position updates by supplying (defaults to "best");
LocationSettings.distanceFilter: allows controlling the minimum distance the device needs to move before the update is emitted (default value is 0 which indicates no filter is used);
LocationSettings.timeLimit: allows for setting a timeout interval. If between fetching locations the timeout interval is exceeded a [TimeoutException] will be thrown. By default no time limit is configured.
Im having trouble using a custom marker in my GoogleMaps project with flutter. I get this error when the map screen loads
Exception has occurred.
LateError (LateInitializationError: Field 'myMarker' has not been initialized.)
I tried without using late and it says myMarker has to be initialized so I declared it as late and then initialized it in the initState. That didn't work so I tried with a nullable ? and that did not work either. Any help would be appreciated.
Thanks
import 'dart:async';
import 'dart:developer';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:location/location.dart';
import 'dart:math' as math;
import './main.dart' as main;
import './variables.dart' as variables;
import './methods.dart' as methods;
import './mapvariables.dart' as mapVar;
import './marker_information.dart' as markerInfo;
class MapScreen extends StatefulWidget {
const MapScreen({Key? key}) : super(key: key);
#override
_MapScreenState createState() => _MapScreenState();
}
class _MapScreenState extends State<MapScreen> {
Completer<GoogleMapController> _controllerGoogleMap = Completer();
late GoogleMapController newGoogleMapController;
Position? currentPosition;
var geoLocator = Geolocator();
final double dcheck = 0.00014128694207108202;
var location = new Location();
late BitmapDescriptor myMarker;
#override
void initState() {
super.initState();
setMarker();
}
void setMarker() async {
myMarker = await BitmapDescriptor.fromAssetImage(
ImageConfiguration(), 'loginlogo.png');
}
checkpermission_location() async {
var locationStatus = await Permission.location.status;
print(locationStatus);
if (!locationStatus.isGranted) {
print("gr");
print(await Permission.location.value);
await Permission.location.request();
checkpermission_location();
}
if (!locationStatus.isDenied) {
print('de');
await Permission.location.request();
checkLocation();
}
}
void checkClue(var x, var y, markerInfo.ClueLocation marker) {
double distance = methods.distance(marker.lat, marker.long, x, y);
log("distance: $distance");
if ((distance < dcheck)) {
variables.dialogVis = true;
if ((variables.dialogVis) && (marker.compl == false)) {
mapVar.showAlertDialog(context, marker);
variables.dialogVis = false;
marker.compl = true;
}
}
}
void checkLocation() {
location.onLocationChanged.listen((LocationData currentLocation) {
var lat = currentLocation.latitude;
var long = currentLocation.longitude;
checkClue(lat, long, markerInfo.newHamCollege);
checkClue(lat, long, markerInfo.coeFen);
checkClue(lat, long, markerInfo.mathematicalBridge);
checkClue(lat, long, markerInfo.graveYard);
checkClue(lat, long, markerInfo.archeologicalMuseum);
checkClue(lat, long, markerInfo.addenbrokesHospital);
checkClue(lat, long, markerInfo.stMarysBellTower);
checkClue(lat, long, markerInfo.trinityStreet);
checkClue(lat, long, markerInfo.viewOfTheBridgeOfSighs);
});
}
//Initial camera position when maps first load
static const _initalCameraPosition = CameraPosition(
target: LatLng(52.2053, 0.1218),
zoom: 11.5,
);
Marker makeMarker(markerInfo.ClueLocation marker) {
return (Marker(
markerId: MarkerId(marker.title),
infoWindow: InfoWindow(title: marker.title),
icon: myMarker,
position: LatLng(marker.lat, marker.long),
onTap: () {
if (marker.compl) {
mapVar.showAlertDialog(context, marker);
}
}));
}
//Google map widget
#override
Widget build(BuildContext context) {
//Checks if mapAcess is true
if (variables.mapAccess) {
var currentlocation = location.getLocation();
return Scaffold(
body: GoogleMap(
onMapCreated: (GoogleMapController controller) {
controller.setMapStyle(mapVar.mapStyle);
checkpermission_location();
_controllerGoogleMap.complete(controller);
newGoogleMapController = controller;
},
mapType: MapType.normal,
myLocationButtonEnabled: true,
zoomControlsEnabled: true,
myLocationEnabled: true,
zoomGesturesEnabled: true,
markers: {
//Markers located in the variables.dart file
makeMarker(markerInfo.newHamCollege),
makeMarker(markerInfo.coeFen),
makeMarker(markerInfo.mathematicalBridge),
makeMarker(markerInfo.graveYard),
makeMarker(markerInfo.archeologicalMuseum),
//6.??? Waiting for update from Konstantin
makeMarker(markerInfo.addenbrokesHospital),
makeMarker(markerInfo.stMarysBellTower),
makeMarker(markerInfo.trinityStreet),
makeMarker(markerInfo.viewOfTheBridgeOfSighs),
},
initialCameraPosition: _initalCameraPosition,
),
);
}
//Refuses access if 10 Digit key is not provided
return Scaffold(
body: Center(
child: Text('You do not have access to the map, please login')));
}
}
You pretty much have it the same way I do it; the way I've done it is as follows:
Set up the BitmapDescriptor as a nullable property at the top of your state class, as well as I create a markers Set:
Set<Marker>? _markers = <Marker>{};
BitmapDescriptor? myMarker
I create an async method that I run at the beginning of the build method (I do it in the build method because I need the build context sometimes), that asynchronously loads the bitmaps, as in:
void setMarkerIcon() async {
myMarker = await BitmapDescriptor.fromAssetImage(
const ImageConfiguration(size: Size(50, 50)),'loginlogo.png');
}
Then in the build method I just call it:
#override
Widget build(BuildContext context) {
// you can call it here
setMarkerIcons();
return Scaffold(
body: GoogleMap(
markers: _markers!,
onMapCreated: (GoogleMapController ctrl) {
// here after map is loaded, I generate the markers
generateMarkers();
}
)
);
BONUS:
Then as shown above, upon the map getting created, I can go ahead and use the custom markers, based on a list of locations, as such:
void generateMarkers() {
var localMarkers = <Marker>{};
for(var location in locationsList!) {
localMarkers.add(
Marker(
markerId: MarkerId(location.id!),
position: LatLng(location.lat!, location.lng!),
icon: myMarker
)
);
}
if (mounted) {
setState(() {
_markers = localMarkers;
});
}
}
The application that I'm trying to create required the creation of a route that has destinations between the starting and ending point, between the beginning and end provided in the getRouteBetweenCoordinates, I need a way to add a custom Latlong pair that must travel through, instead of finding the quickest route, I need it to route between all points that I provided while still following the road (not just a direct line).
The only method that I could come up with is recalling the setPolyLines function for each stretch that makes up the total route. While this method could get the desired result, it required making multiple API calls, ideally, the entirety of the custom route would be loaded upon that first directions API call.
Here is the code that I'm working with, Is there an easier solution to this problem that I missed? This may be very obvious but I'm new with google maps integration so sorry if that's the case.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:flutter_polyline_points/flutter_polyline_points.dart';
//new polyline between each destination
class Tour extends StatefulWidget {
const Tour({Key? key}) : super(key: key);
#override
_TourState createState() => _TourState();
}
class _TourState extends State<Tour> {
late GoogleMapController mapController;
//poly line variables
Set<Polyline> _polyLine = Set<Polyline>();
List<LatLng> polylineCordinates = [];
late PolylinePoints polylinePoints;
//starting location
static const _start =
CameraPosition(target: LatLng(48.696985, -122.905595), zoom: 17.0);
//METHODS
void _onMapCreated(GoogleMapController controller) {
mapController = controller;
//TODO: provide with start and end point for specific line, end of last ==
//start of next
setPolyLines(PointLatLng(48.696985, -122.905595),
PointLatLng(48.657421, -122.917412));
setPolyLines(PointLatLng(48.657421, -122.917412),
PointLatLng(48.644983, -122.944760));
}
void setPolyLines(PointLatLng start, PointLatLng end) async {
//polyline result DT is a collection of latlng following roads
PolylineResult result = await polylinePoints.getRouteBetweenCoordinates(
"MY API KEY IS HERE",
//route start
start,
//route end
end);
//list of latlng pairs in order of exectecution
//this is preparing the drawing of the line, the set state plots it out
if (result.status == 'OK') {
result.points.forEach((PointLatLng point) {
polylineCordinates.add(LatLng(point.latitude, point.longitude));
});
}
setState(() {
_polyLine.add(Polyline(
width: 10,
//set id to
polylineId: PolylineId("route"),
color: Color(0xFF00BFA6),
points: polylineCordinates));
});
}
#override
void initState() {
polylinePoints = PolylinePoints();
}
#override
void dispose() {
mapController.dispose();
super.dispose();
}
//upon call, modal sheet toggles from the bottom of screen
modalSheet() {
showModalBottomSheet(
context: context,
builder: (context) {
return Column(
children: [
Container(
height: 200,
color: Colors.amber,
),
Container(
height: 100,
color: Colors.blue,
)
],
);
});
}
//adjusts camera position to the _start location
center() {
mapController.animateCamera(CameraUpdate.newCameraPosition(_start));
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: GoogleMap(
polylines: _polyLine,
myLocationButtonEnabled: false,
zoomControlsEnabled: false,
onMapCreated: _onMapCreated,
initialCameraPosition: _start),
floatingActionButton: FloatingActionButton(
onPressed: () => center(), child: Icon(Icons.add)),
);
}
}
You can use wayPoints parameter of getRouteBetweenCoordinates method which accepts a list of PolylineWayPoint (List<PolylineWayPoint>).
PolylineResult result = await polylinePoints.getRouteBetweenCoordinates(
googleAPiKey,
PointLatLng(48.696985, -122.905595),
PointLatLng(48.644983, -122.944760),
wayPoints: [PolylineWayPoint(location: "48.657421,-122.917412")]);
Please see the image below for the result using your sample code.
In my flutter application only the Markers are visible in the Map screen but path is not displaying via polylines ,I have set a debugger point and following if statement is not executed .
if(result.status == 'OK'){
result.points.forEach((PointLatLng point){
polylineCoordinates.add(LatLng(point.latitude,point.longitude));
});
please refer the below code above is extracted form below code.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_polyline_points/flutter_polyline_points.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
class GooglePolylines extends StatefulWidget {
GooglePolylines({Key key}) : super(key: key);
#override
GooglePolyline createState() => GooglePolyline();
}
class GooglePolyline extends State<GooglePolylines>{
GoogleMapController mapController;
double _originLatitude = 26.48424, _originLongitude = 50.04551;
double _destLatitude = 26.46423, _destLongitude = 50.06358;
Map<MarkerId, Marker> markers = {};
String googleAPiKey = "MY KEY";
Set <Polyline> _polyLines = Set<Polyline>();
List <LatLng> polylineCoordinates = [];
PolylinePoints polylinePoints ;
#override
void initState() {
super.initState();
polylinePoints =PolylinePoints();
/// origin marker
_addMarker(LatLng(_originLatitude, _originLongitude), "origin",
BitmapDescriptor.defaultMarker);
/// destination marker
_addMarker(LatLng(_destLatitude, _destLongitude), "destination",
BitmapDescriptor.defaultMarkerWithHue(90));
// setPolyLines();
// _getPolyline();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: GoogleMap(
initialCameraPosition: CameraPosition(
target: LatLng(_originLatitude, _originLongitude), zoom: 15),
myLocationEnabled: true,
tiltGesturesEnabled: true,
compassEnabled: true,
scrollGesturesEnabled: true,
zoomGesturesEnabled: true,
onMapCreated: _onMapCreated,
markers: Set<Marker>.of(markers.values),
//rendering polyLines by map
polylines: _polyLines,
)),
);
}
void _onMapCreated(GoogleMapController controller) async {
mapController = controller;
setPolyLines();
}
_addMarker(LatLng position, String id, BitmapDescriptor descriptor) {
MarkerId markerId = MarkerId(id);
Marker marker =
Marker(markerId: markerId, icon: descriptor, position: position);
markers[markerId] = marker;
}
void setPolyLines() async {
PolylineResult result = await polylinePoints.getRouteBetweenCoordinates(
googleAPiKey,
PointLatLng( _originLatitude, _originLongitude),
PointLatLng(_destLatitude, _destLongitude ),
travelMode: TravelMode.driving,
);
if(result.status == 'OK'){
result.points.forEach((PointLatLng point){
polylineCoordinates.add(LatLng(point.latitude,point.longitude));
});
setState((){
_polyLines.add(Polyline(
visible: true,
width:10,
polylineId:PolylineId('polyLineTest'),
color: Colors.blue,
points:polylineCoordinates
));
});
}
}
}
In the API section google cloud platform console , all the applied requests are shown as errors.
I tried your code using my own API key and I was able to show the polyline (Please see screenshot below). It looks like the issue is with your own API key. To solve the issue, make sure that:
You are using a valid API key and
the Directions API is enabled in your project and
your project is linked to a valid billing account
Check out this documentation for more info.
I added the markers to googlemap.
In this code. At the first load, there is no markers
When I push r for hot reload , markers appear
I just guess,, I might have to do something here???
setState((){
print("items ready and set state");
});
How can I solve this??
class _MapPageState extends State<MapPage> {
Completer<gmap.GoogleMapController> _controller = Completer();
Set<gmap.Marker> _markers = {};
#override
void initState(){
super.initState();
_asyncMethod();
print("init ready");
}
_asyncMethod() async {
_markers = {};
print ("asyncMethod start");
List<dynamic> wayLabels = annotModel['wayLabel'];
wayLabels.forEach((x){
gmap.BitmapDescriptor icon;
gmap.BitmapDescriptor.fromAssetImage(
ImageConfiguration(devicePixelRatio: 2.5),
x['image'][CommonFunc.langKey]).then((onValue) {
icon = onValue;
gmap.Marker marker = gmap.Marker(
markerId: gmap.MarkerId(x['title']),
position: gmap.LatLng(x['latitude'],x['longitude']),
icon: icon
);
_markers.add(marker);
});
});
setState((){
print("items ready and set state");
});
}
Widget makeMyMap(){
gmap.GoogleMap myMap = gmap.GoogleMap(
mapType: gmap.MapType.normal,
markers : _markers,
initialCameraPosition: _kGooglePlex,
onMapCreated: (gmap.GoogleMapController controller) {
_controller.complete(controller);
}
);
return Container(child:myMap);
}
#override
Widget build(BuildContext context){
return
Column(children: <Widget>[
Expanded(child:Stack(children: <Widget>[
makeMyMap(),
]),),
]);
}
}
gmap.BitmapDescriptor.fromAssetImage might takes time so
setState((){
print("items ready and set state");
});
in _asyncMethod()
was called before every markers are set.
That was the problem.
Solution.
change _markers.add(marker);
to
setState((){
_markers = markers;
print("items ready and set state");
});
it works well.
You should try using this package https://pub.dev/packages/after_layout.
Call _asyncMethod inside afterFirstLayout method
#override
void afterFirstLayout(BuildContext context) {
// Calling the same function "after layout" to resolve the issue.
_asyncMethod();
}
also, i think is healthier to assign the markers in _asyncMethod to a local variable an then in the setState change the markers
_asyncMethod() async {
var localMarkers = {};
.
.
.
setState((){
_markers = localMarkers;
});
}