I have a problem with location and google maps in flutter: I'm building an app whom first screen is a map from Google and I'm trying to use user location as "initialCameraPosition" of the map. The problem is that when I start the application when the location already on, everything is okay, I see the map clearly and also the user location is okay. Instead, when I start the application with the location off, even though I able the location when the app starts then I cannot see the map anymore and it seems that the location is null.
Here's my code (map screen):
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:provider/provider.dart';
import 'package:stabia_go/location.dart';
import 'package:stabia_go/markers.dart';
class GoogleMapScreen extends StatefulWidget {
#override
_GoogleMapScreenState createState() => _GoogleMapScreenState();
}
class _GoogleMapScreenState extends State<GoogleMapScreen> {
Set<Marker> _markers = {};
void _onMapCreated(GoogleMapController controller) {
setState(() {
_markers.addAll(markers);
});
}
#override
void initState() {
super.initState();
Provider.of<LocationProvider>(context, listen: false).initialization();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('Percorso Suggerito'),
),
body: googleMapUI(),
);
}
Widget googleMapUI() {
return Consumer<LocationProvider>(builder: (consumerContext, model, child) {
if (model.locationPosition != null) {
return GoogleMap(
markers: _markers,
initialCameraPosition:
CameraPosition(target: model.locationPosition, zoom: 15),
myLocationEnabled: true,
myLocationButtonEnabled: true,
onMapCreated: _onMapCreated,
);
}
return Container(
child: Center(
child: CircularProgressIndicator(),
),
);
});
}
}
Here's the code of the location file:
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:location/location.dart';
class LocationProvider with ChangeNotifier {
Location _location;
Location get location => _location;
LatLng _locationPosition;
LatLng get locationPosition => _locationPosition;
LocationProvider() {
_location = new Location();
}
initialization() async {
await getUserLocation();
}
getUserLocation() async {
bool serviceEnabled;
PermissionStatus permissionGranted;
serviceEnabled = await location.serviceEnabled();
if (!serviceEnabled) {
serviceEnabled = await location.requestService();
if (!serviceEnabled) {
return;
}
}
permissionGranted = await location.hasPermission();
if (permissionGranted == PermissionStatus.denied) {
permissionGranted = await location.requestPermission();
if (permissionGranted != PermissionStatus.granted) {
return;
}
}
location.onLocationChanged.listen(
(LocationData currentLocation) {
_locationPosition = LatLng(
currentLocation.latitude,
currentLocation.longitude,
);
print(_locationPosition);
notifyListeners();
},
);
}
}
Where's my mistake? I'm using Location, Provider and Google Maps flutter packages
Try to check for location access when your app is launching. if permission is denied the redirect user to allow location access. if user allow then everything will work fine else user deny access then show a hard coded location.
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;
});
}
}
I wrote this function to save contact number, but it can't save on local storage
Future _saveContact() async {
Contact contact = Contact();
contact.familyName = 'FreeZone';
contact.phones = [Item(label: "mobile", value: '01752591591')];
contact.emails = [Item(label: "work", value: 'info#34.71.214.132')];
if (await Permission.contacts.request().isGranted) {
await ContactsService.addContact(contact);
print("Contact added successfully");
return contact;
}
}
dependencies:
contacts_service: ^0.6.3
permission_handler: ^8.3.0
How to save contact according to the above-given Name, Number, Email?
I could see 2 plugins in pub.dev that can do this for you in Android and iOS.
flutter_contact - A Flutter plugin to retrieve, create and save contacts and contact-related events on Android and iOS devices.
contacts_service - A Flutter plugin to retrieve and manage contacts on Android and iOS devices.
Please have a look into them.
Add this :
dependencies:
contacts_service: ^0.6.3
then:
import 'package:contacts_service_example/contacts_list_page.dart';
import 'package:contacts_service_example/contacts_picker_page.dart';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
void main() => runApp(ContactsExampleApp());
// iOS only: Localized labels language setting is equal to CFBundleDevelopmentRegion value (Info.plist) of the iOS project
// Set iOSLocalizedLabels=false if you always want english labels whatever is the CFBundleDevelopmentRegion value.
const iOSLocalizedLabels = false;
class ContactsExampleApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
routes: <String, WidgetBuilder>{
'/add': (BuildContext context) => AddContactPage(),
'/contactsList': (BuildContext context) => ContactListPage(),
'/nativeContactPicker': (BuildContext context) => ContactPickerPage(),
},
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
void initState() {
super.initState();
_askPermissions(null);
}
Future<void> _askPermissions(String routeName) async {
PermissionStatus permissionStatus = await _getContactPermission();
if (permissionStatus == PermissionStatus.granted) {
if (routeName != null) {
Navigator.of(context).pushNamed(routeName);
}
} else {
_handleInvalidPermissions(permissionStatus);
}
}
Future<PermissionStatus> _getContactPermission() async {
PermissionStatus permission = await Permission.contacts.status;
if (permission != PermissionStatus.granted &&
permission != PermissionStatus.permanentlyDenied) {
PermissionStatus permissionStatus = await Permission.contacts.request();
return permissionStatus;
} else {
return permission;
}
}
void _handleInvalidPermissions(PermissionStatus permissionStatus) {
if (permissionStatus == PermissionStatus.denied) {
final snackBar = SnackBar(content: Text('Access to contact data denied'));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
} else if (permissionStatus == PermissionStatus.permanentlyDenied) {
final snackBar =
SnackBar(content: Text('Contact data not available on device'));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Contacts Plugin Example')),
body: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
ElevatedButton(
child: const Text('Contacts list'),
onPressed: () => _askPermissions('/contactsList'),
),
ElevatedButton(
child: const Text('Native Contacts picker'),
onPressed: () => _askPermissions('/nativeContactPicker'),
),
],
),
),
);
}
}
I think I solved your problem.
_saveContact () async {
// 'without Future' is working
var newPerson = Contact();
// newPerson uses Contact Package
newPerson.givenName = 'FreeZone';
newPerson.phones = [Item(label: "mobile", value: '01752591591')];
newPerson.emails = [Item(label: "work", value: 'info#34.71.214.132')];
if (await Permission.contacts.status.isGranted) {
await ContactsService.addContact(newPerson);
var contacts = await ContactsService.getContacts();
print("Contact added successfully");
return contacts;
// setState(() {
// //setState isn't necessary, it just shows 'contact' directly on a screen.
// name = contacts;
// // I put 'contacts' in 'name' directly
// });
}
}
Actually, I was in trouble using 'newPerson.phones'.
I was wondering how to put my parameter in 'phone number'.
However, with your code, I could know how to write the code.
Thank you and please accept this answer as a small token of my appreciation.
And it is what I wrote you helped.
addPerson (given,family,number) async {
var newPerson = Contact();
newPerson.givenName = given;
newPerson.familyName = family;
newPerson.phones = [Item(label: "mobile", value: number)];
// I wrote 'newPerson.phones = [number];' and it was wrong.
await ContactsService.addContact(newPerson);
// adding newPerson
var contacts = await ContactsService.getContacts();
// call all of contacts
setState(() {
name = contacts;
});
// to show the contacts directly, I use 'setState'.
}
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
everyone.
I'm trying to develop a PWA with flutter 2.2.1 that shows a map using Mapbox_gl and displays the user current location using Geolocator.
So far everything works as expected while debuging the app, but when I run:
flutter build
or
flutter build --release
and then run
firebase deploy
the site gets uploaded, the map shows as intended and it asks for permissions but the user's location is never shown and Google Chrome's Console throws this error:
Uncaught TypeError: m.gfR is not a function
at Object.avh (main.dart.js:20405)
at main.dart.js:65755
at aiD.a (main.dart.js:5853)
at aiD.$2 (main.dart.js:34394)
at ahm.$1 (main.dart.js:34386)
at Rx.o1 (main.dart.js:35356)
at adi.$0 (main.dart.js:34770)
at Object.tQ (main.dart.js:5975)
at a5.mn (main.dart.js:34687)
at ada.$0 (main.dart.js:34731)
Here's the code I'm using on flutter:
mapbox.dart
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:geolocator/geolocator.dart';
import 'package:kkc/main.dart';
import 'package:mapbox_gl/mapbox_gl.dart';
import 'package:kkc/services/location_service.dart';
class Mapbox extends StatefulWidget {
const Mapbox();
#override
State createState() => MapboxState();
}
class MapboxState extends State<Mapbox> {
final Random _rnd = new Random();
Position? _currentLocation;
LatLng _currentCoordinates = new LatLng(0,0);
final List<_PositionItem> _positionItems = <_PositionItem>[];
StreamSubscription<Position>? _positionStreamSubscription;
late MapboxMapController _mapController;
List<Marker> _markers = [];
List<_MarkerState> _markerStates = [];
CameraPosition _kInitialPosition = CameraPosition(
target: LatLng(19.4274418, -99.1682147),
zoom: 18.0,
tilt: 70,
);
void _addMarkerStates(_MarkerState markerState) {
_markerStates.add(markerState);
}
void _onMapCreated(MapboxMapController controller) {
_mapController = controller;
controller.addListener(() {
if (controller.isCameraMoving) {
_updateMarkerPosition();
}
});
}
void _onStyleLoadedCallback() {
_updateMarkerPosition();
}
void _onCameraIdleCallback() {
_updateMarkerPosition();
}
void _updateMarkerPosition() {
final coordinates = <LatLng>[];
for (final markerState in _markerStates) {
coordinates.add(markerState.getCoordinate());
}
_mapController.toScreenLocationBatch(coordinates).then((points) {
_markerStates.asMap().forEach((i, value) {
_markerStates[i].updatePosition(points[i]);
});
});
}
void _addMarker(Point<double> point, LatLng coordinates) {
setState(() {
_markers.add(Marker(_rnd.nextInt(100000).toString(), coordinates, point, _addMarkerStates));
});
}
#override
void initState() {
super.initState();
_getCurrentLocation();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: Stack(children: [
MapboxMap(
accessToken: Kukulcan.MAPBOX_ACCESS_TOKEN,
trackCameraPosition: true,
onMapCreated: _onMapCreated,
onCameraIdle: _onCameraIdleCallback,
onStyleLoadedCallback: _onStyleLoadedCallback,
initialCameraPosition: _kInitialPosition,
),
IgnorePointer(
ignoring: true,
child: Stack(
children: _markers,
))
]),
);
}
void _getCurrentLocation() async {
_currentLocation = await LocationService.startLocationService();
_currentCoordinates = new LatLng(_currentLocation!.latitude,_currentLocation!.longitude);
await _mapController.animateCamera(CameraUpdate.newLatLng(_currentCoordinates));
_addMarker(new Point(1, 1), _currentCoordinates);
if (_positionStreamSubscription == null) {
final positionStream = Geolocator.getPositionStream();
_positionStreamSubscription = positionStream.handleError((error) {
_positionStreamSubscription?.cancel();
_positionStreamSubscription = null;
}).listen((position) => setState(() => _positionItems.add(
_PositionItem(_PositionItemType.position, position.toString()))));
_positionStreamSubscription?.pause();
}
}
}
class Marker extends StatefulWidget {
final Point _initialPosition;
LatLng _coordinate;
final void Function(_MarkerState) _addMarkerState;
Marker(
String key, this._coordinate, this._initialPosition, this._addMarkerState)
: super(key: Key(key));
#override
State<StatefulWidget> createState() {
final state = _MarkerState(_initialPosition);
_addMarkerState(state);
return state;
}
}
class _MarkerState extends State with TickerProviderStateMixin {
final _iconSize = 80.0;
Point _position;
_MarkerState(this._position);
#override
Widget build(BuildContext context) {
var ratio = 1.0;
//web does not support Platform._operatingSystem
if (!kIsWeb) {
// iOS returns logical pixel while Android returns screen pixel
ratio = Platform.isIOS ? 1.0 : MediaQuery.of(context).devicePixelRatio;
}
return Positioned(
left: _position.x / ratio - _iconSize / 2,
top: _position.y / ratio - _iconSize / 2,
child: Image.asset('assets/img/pin.png', height: _iconSize));
}
void updatePosition(Point<num> point) {
setState(() {
_position = point;
});
}
LatLng getCoordinate() {
return (widget as Marker)._coordinate;
}
}
enum _PositionItemType {
permission,
position,
}
class _PositionItem {
_PositionItem(this.type, this.displayValue);
final _PositionItemType type;
final String displayValue;
}
Does anyone have an idea on what's the problem?
Cheers!
Anyway the solution i found is to use --no-sound-null-safety argument as stated by geolocat documentation
I quote:
NOTE: due to a bug in the dart:html library the web version of the Geolocator plugin does not work with sound null safety enabled and compiled in release mode. Running the App in release mode with sound null safety enabled results in a Uncaught TypeError (see issue #693). The current workaround would be to build your App with sound null safety disabled in release mode: