Reputation: 153
I am trying to integrate google map in my app and giving it source and destination to place markers. It was running correctly from last few days, no error at all. Suddenly today this error shows up and map is not working. Even though it is not showing google map on the screen just the blank white screen.
class MapView extends StatefulWidget {
@override
_MapViewState createState() => _MapViewState();
}
class _MapViewState extends State<MapView> {
CameraPosition _initialLocation = CameraPosition(target: LatLng(0.0, 0.0));
**late GoogleMapController mapController ;**
late Position _currentPosition;
String _currentAddress = '';
final startAddressController = TextEditingController();
final destinationAddressController = TextEditingController();
final startAddressFocusNode = FocusNode();
final destinationAddressFocusNode = FocusNode();
String _startAddress = '';
String _destinationAddress = '';
double? _placeDistance;
Set<Marker> markers = {};
late PolylinePoints polylinePoints;
List<LatLng> polylineCoordinates = [];
Map<PolylineId, Polyline> polylines = {};
final _scaffoldKey = GlobalKey<ScaffoldState>();
Widget _textField({
required TextEditingController controller,
required FocusNode focusNode,
required String label,
required String hint,
required double width,
required Icon prefixIcon,
Widget? suffixIcon,
required Function(String) locationCallback,
}) {
return Container(
width: width * 0.8,
child: TextField(
onChanged: (value) {
locationCallback(value);
},
controller: controller,
focusNode: focusNode,
decoration: new InputDecoration(
prefixIcon: prefixIcon,
suffixIcon: suffixIcon,
labelText: label,
filled: true,
fillColor: Colors.white,
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10.0),
),
borderSide: BorderSide(
color: Colors.grey.shade400,
width: 2,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10.0),
),
borderSide: BorderSide(
color: Colors.blue.shade300,
width: 2,
),
),
contentPadding: EdgeInsets.all(15),
hintText: hint,
),
),
);
}
// Method for retrieving the current location
_getCurrentLocation() async {
await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high)
.then((Position position) async {
setState(() {
_currentPosition = position;
print('CURRENT POS: $_currentPosition');
**mapController.animateCamera(**
CameraUpdate.newCameraPosition(
CameraPosition(
target: LatLng(position.latitude, position.longitude),
zoom: 18.0,
),
),
);
});
await _getAddress();
}).catchError((e) {
print(e);
});
}
Next part:
_getAddress() async {
try {
List<Placemark> p = await placemarkFromCoordinates(
_currentPosition.latitude, _currentPosition.longitude);
Placemark place = p[0];
setState(() {
_currentAddress =
"${place.name}, ${place.locality}, ${place.postalCode}, ${place.country}";
startAddressController.text = _currentAddress;
_startAddress = _currentAddress;
});
} catch (e) {
print(e);
}
}
// Method for calculating the distance between two places
Future<bool> _calculateDistance() async {
try {
// Retrieving placemarks from addresses
List<Location> startPlacemark = await locationFromAddress(_startAddress);
List<Location> destinationPlacemark =
await locationFromAddress(_destinationAddress);
// Use the retrieved coordinates of the current position,
// instead of the address if the start position is user's
// current position, as it results in better accuracy.
double startLatitude = _startAddress == _currentAddress
? _currentPosition.latitude
: startPlacemark[0].latitude;
double startLongitude = _startAddress == _currentAddress
? _currentPosition.longitude
: startPlacemark[0].longitude;
double destinationLatitude = destinationPlacemark[0].latitude;
double destinationLongitude = destinationPlacemark[0].longitude;
String startCoordinatesString = '($startLatitude, $startLongitude)';
String destinationCoordinatesString =
'($destinationLatitude, $destinationLongitude)';
// Start Location Marker
Marker startMarker = Marker(
markerId: MarkerId(startCoordinatesString),
position: LatLng(startLatitude, startLongitude),
infoWindow: InfoWindow(
title: 'Start $startCoordinatesString',
snippet: _startAddress,
),
icon: BitmapDescriptor.defaultMarker,
);
// Destination Location Marker
Marker destinationMarker = Marker(
markerId: MarkerId(destinationCoordinatesString),
position: LatLng(destinationLatitude, destinationLongitude),
infoWindow: InfoWindow(
title: 'Destination $destinationCoordinatesString',
snippet: _destinationAddress,
),
icon: BitmapDescriptor.defaultMarker,
);
// Adding the markers to the list
markers.add(startMarker);
markers.add(destinationMarker);
print(
'START COORDINATES: ($startLatitude, $startLongitude)',
);
print(
'DESTINATION COORDINATES: ($destinationLatitude, $destinationLongitude)',
);
// Calculating to check that the position relative
// to the frame, and pan & zoom the camera accordingly.
double miny = (startLatitude <= destinationLatitude)
? startLatitude
: destinationLatitude;
double minx = (startLongitude <= destinationLongitude)
? startLongitude
: destinationLongitude;
double maxy = (startLatitude <= destinationLatitude)
? destinationLatitude
: startLatitude;
double maxx = (startLongitude <= destinationLongitude)
? destinationLongitude
: startLongitude;
double southWestLatitude = miny;
double southWestLongitude = minx;
double northEastLatitude = maxy;
double northEastLongitude = maxx;
// Accommodate the two locations within the
// camera view of the map
mapController.animateCamera(
CameraUpdate.newLatLngBounds(
LatLngBounds(
northeast: LatLng(northEastLatitude, northEastLongitude),
southwest: LatLng(southWestLatitude, southWestLongitude),
),
100.0,
),
);
// Calculating the distance between the start and the end positions
// with a straight path, without considering any route
// double distanceInMeters = await Geolocator.bearingBetween(
// startLatitude,
// startLongitude,
// destinationLatitude,
// destinationLongitude,
// );
await _createPolylines(startLatitude, startLongitude, destinationLatitude,
destinationLongitude);
double totalDistance = 0.0;
// Calculating the total distance by adding the distance
// between small segments
for (int i = 0; i < polylineCoordinates.length - 1; i++) {
totalDistance += _coordinateDistance(
polylineCoordinates[i].latitude,
polylineCoordinates[i].longitude,
polylineCoordinates[i + 1].latitude,
polylineCoordinates[i + 1].longitude,
);
}
setState(() {
_placeDistance = totalDistance;
print('DISTANCE: $_placeDistance km');
});
return true;
} catch (e) {
print(e);
}
return false;
}
// Formula for calculating distance between two coordinates
// https://stackoverflow.com/a/54138876/11910277
double _coordinateDistance(lat1, lon1, lat2, lon2) {
var p = 0.017453292519943295;
var c = cos;
var a = 0.5 -
c((lat2 - lat1) * p) / 2 +
c(lat1 * p) * c(lat2 * p) * (1 - c((lon2 - lon1) * p)) / 2;
return 12742 * asin(sqrt(a));
}
// Create the polylines for showing the route between two places
_createPolylines(
double startLatitude,
double startLongitude,
double destinationLatitude,
double destinationLongitude,
) async {
polylinePoints = PolylinePoints();
PolylineResult result = await polylinePoints.getRouteBetweenCoordinates(
Secrets.API_KEY, // Google Maps API Key
PointLatLng(startLatitude, startLongitude),
PointLatLng(destinationLatitude, destinationLongitude),
travelMode: TravelMode.driving,
);
result.points.forEach((PointLatLng point) {
polylineCoordinates.add(LatLng(point.latitude, point.longitude));
ScaffoldMessenger.of(context)
.showSnackBar(
SnackBar(
content: Text(
'Poly here'),
),
);
});
PolylineId id = PolylineId('poly');
Polyline polyline = Polyline(
polylineId: id,
color: Colors.red,
points: polylineCoordinates,
width: 3,
);
polylines[id] = polyline;
setState(() {});
}
@override
void initState() {
super.initState();
_getCurrentLocation();
_calculateDistance();
}
@override
void dispose() {
mapController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
var height = MediaQuery.of(context).size.height;
var width = MediaQuery.of(context).size.width;
return Container(
height: height,
width: width,
child: Scaffold(
key: _scaffoldKey,
body: Stack(
children: <Widget>[
// Map View
GoogleMap(
markers: Set<Marker>.from(markers),
initialCameraPosition: _initialLocation,
myLocationEnabled: true,
myLocationButtonEnabled: false,
mapType: MapType.normal,
zoomGesturesEnabled: true,
zoomControlsEnabled: false,
polylines: Set<Polyline>.of(polylines.values),
onMapCreated: (GoogleMapController controller) {
setState(() {
mapCreated = true;
});
mapController = controller;
},
),
// Show zoom buttons
SafeArea(
child: Padding(
padding: const EdgeInsets.only(left: 10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ClipOval(
child: Material(
color: Colors.blue.shade100, // button color
child: InkWell(
splashColor: Colors.blue, // inkwell color
child: SizedBox(
width: 50,
height: 50,
child: Icon(Icons.add),
),
onTap: () {
mapController.animateCamera(
CameraUpdate.zoomIn(),
);
},
),
),
),
Upvotes: 2
Views: 8364
Reputation: 9754
You have to store the GoogleMapController
when you create the GoogleMap
widget like this:
GoogleMap(
onMapCreated: (GoogleMapController controller) {
// here save the value
mapController = controller;
}
You have to make sure that this onMapCreated
runs before you try to access your mapController
(using a FutureBuilder
for example), otherwise you will keep getting this error message. The other option is to remove late
keyword, allow the member to be null, and check for null value before using.
Also, don't forget to properly dispose the controller:
@override
void dispose() {
mapController.dispose();
super.dispose();
}
In your code, you call _getCurrentLocation
async
function from initState
. It is generally not a good approach, because initState
can't be an async
function, so there is no way to use await
to make sure these functions are completed before your widget is built for the first time.
But in this case the biggest problem is that you will have a valid GoogleMapController
only after GoogleMap
widget is created. That's what onMapCreated
is primarily meant for, to get the GoogleMapController
after the map is created.
And since in _getCurrentLocation
you try to access mapController
, it will not be available yet, because it will likely be executed before the GoogleMap
is created.
Now, _getCurrentLocation
uses await
to get current position, and once this is completed, it tries to call mapController.animateCamera
. Here is the reason why you could experience earlier that it works. This is a typical racing problem. If Geolocator.getCurrentPosition
takes enough time, it can happen that GoogleMap
is already built, therefore you have a valid GoogleMapController
. But you can't be sure, as your current error shows, it depends on how long getting current position takes.
What is the solution? You have to reorganise your code so that you use mapController
only after GoogleMap
is created. For example move your _getCurrentLocation
function call from initState
to onMapCreated
, this way you can be sure that you have a valid map controller. But make sure that no code relies on mapController
before it is available.
Here is some code showing how can you get this work:
class MyPageHomeMap extends StatefulWidget {
const MyPageHomeMap({Key? key}) : super(key: key);
@override
State<MyPageHomeMap> createState() => _MyPageHomeMapState();
}
class _MyPageHomeMapState extends State<MyPageHomeMap> {
bool _mapCreated = false;
late GoogleMapController mapController;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
GoogleMap(
initialCameraPosition: <something>,
onMapCreated: _onMapCreated),
if (!_mapCreated) const Center(child: CircularProgressIndicator())
],
),
);
}
_onMapCreated(GoogleMapController controller) {
mapController = controller;
setState(() {
_mapCreated = true;
});
// get current position here and use mapController when it is completed
}
}
Upvotes: 9