Flutter: Google Maps StateError (Bad state: Future already completed) - google-maps

I'm trying to display Google maps inside a dialog box and for the first time it pops up as expected, however the second time it throws and exception: StateError (Bad state: Future already completed).
Completer<GoogleMapController> _controller = Completer();
_displayDialog(){
Alert(
context: context,
style: alertStyle,
title: "Here are your results:",
content: Column(
children: <Widget>[
Container(
width: 200.0,
height: 200.0,
child: GoogleMap(
//mapType: MapType.hybrid,
initialCameraPosition: _kGooglePlex,
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller); //throws here in this line
},
),
),
],
),
Here is a gif to summarize whats happening
Im using rflutter_alert: ^1.0.3 for the dialog box,
and google_maps_flutter: ^0.5.21+15 for Maps.
Thanks in advance!

this verifies if previously I already called "completer ()" if so, you don't need to call "completer ()" again, and just skip it in your code
onMapCreated: (GoogleMapController controller) {
if (!_controller.isCompleted) {
//first calling is false
//call "completer()"
_controller.complete(controller);
}else{
//other calling, later is true,
//don't call again completer()
}
}

I think that the problem is because you are trying to complete a Completer twice which is not allowed. What I did bellow was to create a new Completer each time you call _displayDialog().
_displayDialog(){
Completer<GoogleMapController> _controller = Completer();
Alert(
context: context,
style: alertStyle,
title: "Here are your results:",
content: Column(
children: <Widget>[
Container(
width: 200.0,
height: 200.0,
child: GoogleMap(
//mapType: MapType.hybrid,
initialCameraPosition: _kGooglePlex,
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller); //throws here in this line
},
),
),
],
),

Related

Failed assertion: 'latitude != null': is not true. I've tried several things

Error:
location.dart failed assertion: .... 'latitude != null': is not true.
Apparently, the answer is here:
Flutter - Google Maps doesn´t wait to Location
However, none of these things worked for me.
Attempt:
show an empty Container() while lat and lng is null...
I have no clue what on earth this is... ==?? like with python?
lat == null || lng == null
? Container()
My guess is this guy wants me to assign lat and lng to null and put google map into a container. Here goes nothing:
var lat = null;
var lng = null;
I converted my sizedbox to a container and changed the initial camera position:
before:
SizedBox(
height: 350,
child: GoogleMap(
markers: Set.from(markers),
initialCameraPosition: _myLocation,
myLocationEnabled: true,
compassEnabled: true,
myLocationButtonEnabled: true,
mapType: MapType.normal,
onMapCreated: (GoogleMapController controller) {
controller.setMapStyle(Utils.mapStyles);
}),
after:
Container(
height: 350,
child: GoogleMap(
markers: Set.from(markers),
initialCameraPosition: CameraPosition(
target: LatLng(lat, lng),
zoom: 15.0),
myLocationEnabled: true,
compassEnabled: true,
myLocationButtonEnabled: true,
mapType: MapType.normal,
onMapCreated: (GoogleMapController controller) {
controller.setMapStyle(Utils.mapStyles);
}),
),
Result:
Failed assertion: 'latitude != null': is not true.
The program won't even compile now. to poke a bit, I changed the target to: target: LatLng(null, null),
Same error. Nothing has changed.
<wiped everything, started over>
"you can display a loader until you get your location"
Didn't work.
This is how I'm calling my longitude and latitude points from Google sheets. I'm trying to plot them on buttonpress:
Future<void> _plotCurrent() async {
Map<double,double> m = (await sheet.values.map.column(3, fromRow:2)).map((key, value)=>
MapEntry(double.parse(key), double.parse(value)));
Map<double,double> m2 = (await sheet.values.map.column(4, fromRow:2)).map((key, value)=>
MapEntry(double.parse(key), double.parse(value)));
Iterable _markers = Iterable.generate(10, (index) {
LatLng latLngMarker = LatLng(m["test$index"], m2["test$index"]);
return Marker(markerId: MarkerId("test$index"),position: latLngMarker);
});
setState(() {
markers = _markers;
});
}
I've read some stuff about having to change my Widget build tree into a Future type. However, I'm still terrible with Dart. I don't know to do it. Could this work? Here's the start of my Widget:
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
resizeToAvoidBottomPadding: false,
appBar: AppBar(
title: Text(widget.title, style: TextStyle(fontSize: 11)),
centerTitle: true,
),
body:
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Form(
key: _formKey,
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextFormField(
Please help. Thank you.
Edit:
This makes no sense.. Is geolocator messing this up or something? Here's what I have now:
#override
initState() {
super.initState();
getLocation();
}
var lng, lat;
Future getLocation() async {
Position position = await Geolocator().getCurrentPosition(desiredAccuracy: LocationAccuracy.high);
setState(() {
lat = position.latitude;
lng = position.longitude;
print(lng);
});
}
#override
Widget build(BuildContext context) {
if (lat == null || lng == null) {
return Container();
}
Container(
height: 350,
child: GoogleMap(
markers: Set.from(markers),
initialCameraPosition: CameraPosition(
target: LatLng(lat, lng),
zoom: 15.0),
myLocationEnabled: true,
compassEnabled: true,
myLocationButtonEnabled: true,
mapType: MapType.normal,
onMapCreated: (GoogleMapController controller) {
controller.setMapStyle(Utils.mapStyles);
}),
);
return Scaffold(
key: _scaffoldKey,
resizeToAvoidBottomPadding: false,
appBar: AppBar(
title: Text(widget.title, style: TextStyle(fontSize: 11)),
centerTitle: true,
),
body:
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Form(
key: _formKey,
child: Padding(
first problem is, my map isn't showing up anymore. I can print coordinates... So, I don't get why the error is still showing and everything is crashing after I press plot. Anybody?
Edit 2:
This may have something to do with my error:
The relevant error-causing widget was: MyHomePage
file:///C:/Users/xxx/Desktop/xxxxx/xxxx-xxx/lib/main.dart:92:13 When
the exception was thrown, this was the stack:
points to this widget:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'xx',
theme: ThemeData(
primarySwatch: colorCustom,
hintColor: Colors.white,
canvasColor: colorCustom,
backgroundColor: Colors.red,
),
home: MyHomePage(title: 'xx'),
);
}
}
I don't have enough experience with Dart to know what's wrong with having a homepage with a title separate from the rest of my widgets. Is this a bad thing?
I moved my widget elsewhere and applied the container. My app won't start with the lat == null || lng == null statement and throws the following error:
The following assertion was thrown building GoogleMap(state:
_GoogleMapState#81111): No Directionality widget found.
I think the problem is I have too much garbage loading in. I'm lost.
void main() => runApp(
RestartWidget(child: MyApp()),
);
class RestartWidget extends StatefulWidget {
RestartWidget({this.child});
final Widget child;
static void restartApp(BuildContext context) {
context.findAncestorStateOfType<_RestartWidgetState>().restartApp();
}
#override
_RestartWidgetState createState() => _RestartWidgetState();
}
class _RestartWidgetState extends State<RestartWidget> {
Key key = UniqueKey();
void restartApp() {
setState(() {
key = UniqueKey();
});
}
#override
Widget build(BuildContext context) {
return KeyedSubtree(
key: key,
child: widget.child,
);
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'xxxxxxx',
theme: ThemeData(
primarySwatch: colorCustom,
hintColor: Colors.white,
canvasColor: colorCustom,
backgroundColor: Colors.red,
),
home: MyHomePage(title: 'xxxxxxx'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final _formKey = GlobalKey<FormState>();
final _scaffoldKey = GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
etc... I'm so lost in all of these Widgets. Something is causing my location to be null. I don't know. Anybody?
When dealing with async data loading, you need to have a placeholder container until your data gets loaded. In this case that's lat and long parameters.
Widget methodReturningWidget() {
// In case your data is not ready, return empty container
if (lat == null || long == null) {
return Container();
}
// In case your data is present, return GoogleMap object
return Container(
height: 350,
child: GoogleMap(
markers: Set.from(markers),
initialCameraPosition: CameraPosition(
target: LatLng(lat, long),
zoom: 15.0),
myLocationEnabled: true,
compassEnabled: true,
myLocationButtonEnabled: true,
mapType: MapType.normal,
onMapCreated: (GoogleMapController controller) {
controller.setMapStyle(Utils.mapStyles);
}),
);
}
Part of the code that is crashing your app is this here LatLng(lat, long) that tries to create object, but it's parameters lat and long are null.
I don't have an approximate answer. However, I do believe I found the cause of this.
Map<double, double> m = (await sheet.values.map.column(3, fromRow: 2)).map((
key, value) =>
MapEntry(double.parse(key), double.parse(value)));
Map<double, double> m2 = (await sheet.values.map.column(4, fromRow: 2))
.map((key, value) =>
MapEntry(double.parse(key), double.parse(value)));
print(_markers);
setState(() {
Iterable _markers = Iterable.generate(10, (index) {
LatLng latLngMarker = LatLng(m["test$index"], m2["test$index"]);
return Marker(markerId: MarkerId("test$index"), position: latLngMarker);
});
markers = _markers;
In short, I'm calling the lat and long improperly somehow and it's throwing the error. I verified this by plotting a single point from my current location. Doing so worked without any issues. I'll have to research how to call my columns (coordinates) properly. If anyone has any insight, please let me know. Thanks
Edit: I think I found the problem.
my m and m2 are printing keys and values...!! The keys are messing everything up.

How to turn off the 'find me' FAB in Flutter Google maps plugin?

I am having zero luck turning off the floating action button which exists on the Google maps view in Flutter. I have tried restarting the app, hotreload, delete/reinstall, no luck.
Here's my code:
return Card(
child: GoogleMap(
compassEnabled: false,
mapToolbarEnabled: false,[![enter image description here][1]][1]
padding: EdgeInsets.all(1),
mapType: MapType.normal,
myLocationButtonEnabled: false,
myLocationEnabled: false,
initialCameraPosition: _curCamera,
markers: Set<Marker>.of(_markers),
polylines: Set<Polyline>.of(_polylines),
onMapCreated: (GoogleMapController controller) async {
staticMapController = controller;
positionList = await tracksDB.getListOfPositions(widget.trackID);
setState(() {
_polylines.add(Polyline(
polylineId: PolylineId('track1'),
visible: true,
points: positionList,
color: Colors.red,
));
});
},
),
);
Latest package version fixed the problem. google_maps_flutter: ^1.0.3

Flutter -> loading google maps in background

I need to load maps in background because when I hit the tab with maps.dart for the first time, map is loading. It is looking bad so I want to use FutureBuilder to show CircularProgressIndicator() but I have no idea how to do this.
I know how to do it with Lists but in this case...
This maps.dart code:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
class Maps extends StatefulWidget {
#override
_MapsState createState() => _MapsState();
}
class _MapsState extends State<Maps> with AutomaticKeepAliveClientMixin {
Completer<GoogleMapController> _controller = Completer();
#override
void initState() {
super.initState();
}
double zoomValue = 9.0;
#override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
body: Stack(
children: <Widget>[
_buildGoogleMap(context),
_zoomMinusFunction(),
_zoomPlusFunction(),
],
),
);
}
Widget _zoomMinusFunction() {
return Align(
alignment: Alignment.topLeft,
child: IconButton(
icon: Icon(FontAwesomeIcons.searchMinus, color: Color(0xff6200ee)),
onPressed: () {
zoomValue--;
_minus(zoomValue);
}),
);
}
Widget _zoomPlusFunction() {
return Align(
alignment: Alignment.topRight,
child: IconButton(
icon: Icon(FontAwesomeIcons.searchPlus, color: Color(0xff6200ee)),
onPressed: () {
zoomValue++;
_plus(zoomValue);
}),
);
}
Future<void> _minus(double zoomValue) async {
final GoogleMapController controller = await _controller.future;
controller.animateCamera(CameraUpdate.newCameraPosition(
CameraPosition(target: LatLng(x, y), zoom: zoomValue)));
}
Future<void> _plus(double zoomValue) async {
final GoogleMapController controller = await _controller.future;
controller.animateCamera(CameraUpdate.newCameraPosition(
CameraPosition(target: LatLng(x, y), zoom: zoomValue)));
}
Widget _buildGoogleMap(BuildContext context) {
return Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: GoogleMap(
mapType: MapType.normal,
initialCameraPosition:
CameraPosition(target: LatLng(x, y), zoom: 9),
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller);
},
markers: {
aaa,
bbb,
},
),
);
}
#override
bool get wantKeepAlive => true;
}
Marker aaa = Marker(
markerId: MarkerId('aaa'),
position: LatLng(x, y),
infoWindow: InfoWindow(title: 'aaa', snippet: 'aaa'),
icon: BitmapDescriptor.defaultMarkerWithHue(
BitmapDescriptor.hueViolet,
),
);
Marker bbb = Marker(
markerId: MarkerId('bbb'),
position: LatLng(x, y),
infoWindow:
InfoWindow(title: 'bbb', snippet: 'bbb'),
icon: BitmapDescriptor.defaultMarkerWithHue(
BitmapDescriptor.hueViolet,
),
);
Sorry, I'm new to Flutter.
I know this is a little late, but if it still helps, this is what I have done.
_userLocation == null // If user location has not been found
? Center(
// Display Progress Indicator
child: CircularProgressIndicator(
backgroundColor: UniColors.primaryColour[500],
),
)
: GoogleMap(
// Show Campus Map
onMapCreated: _onMapCreated,
initialCameraPosition: // required parameter that sets the starting camera position. Camera position describes which part of the world you want the map to point at.
CameraPosition(
target: _userLocation,
zoom: defaultZoom,
tilt: 0.0),
scrollGesturesEnabled: true,
tiltGesturesEnabled: true,
trafficEnabled: false,
indoorViewEnabled: true,
compassEnabled: true,
rotateGesturesEnabled: true,
myLocationEnabled: true,
mapType: _currentMapType,
zoomGesturesEnabled: true,
cameraTargetBounds: new CameraTargetBounds(
new LatLngBounds(
northeast: uniCampusNE,
southwest: uniCampusSW,
),
),
minMaxZoomPreference:
new MinMaxZoomPreference(minZoom, maxZoom),
),

GoogleMap's onCameraIdle stops firing when in a SingleChildScrollView

I have a design where a SingleChildScrollView has a GoogleMap on the first 50% on the screen, and a listing of items in the lower 50%. The user can scroll the entire view up to look at all of the listings. However, at times the Map stops firing the onCameraIdle if the users scrolls the page, and it just won't start firing it again.
onCameraMove and onTap works just fine. It is just onCameraIdle that won't fire.
SingleChildScrollView(
child: Column(
children: <Widget>[
Stack(
children: <Widget>[
Container(
height: screenSize.height / 2,
child: GoogleMap(
key: Key("GMap"),
mapType: MapType.normal,
markers: Set<Marker>.of(markers.values),
gestureRecognizers: Set()
..add(Factory<PanGestureRecognizer>(
() => PanGestureRecognizer()))
..add(
Factory<VerticalDragGestureRecognizer>(
() => VerticalDragGestureRecognizer()),
)
..add(
Factory<HorizontalDragGestureRecognizer>(
() => HorizontalDragGestureRecognizer()),
)
..add(
Factory<ScaleGestureRecognizer>(
() => ScaleGestureRecognizer()),
),
initialCameraPosition: CameraPosition(
target: LatLng(14.551620, 121.053329), zoom: 14.5),
onMapCreated: (GoogleMapController controller) {
if (!_controller.isCompleted) {
_controller.complete(controller);
_lastCameraPosition = CameraPosition(
target: LatLng(14.551620, 121.053329), zoom: 14.5);
}
},
myLocationEnabled: true,
myLocationButtonEnabled: true,
onCameraIdle: () {
print("547: onCameraIdle");
_fetchOffers();
},
onCameraMove: (value) {
print("552: onCameraMove");
_lastCameraPosition = value;
},
onTap: (value) {
// Load items for current view if deselecting a marker
print('556: Tapped outside');
},
),
),
Positioned(
top: 50,
right: 20,
child: Container(
height: 30,
decoration: BoxDecoration(
border: Border.all(
color: _userBalance > 0
? globals.themeColor4
: globals.themeColor2,
width: 2),
boxShadow: [
BoxShadow(
blurRadius: 10.0,
color: Colors.black.withOpacity(.5),
offset: Offset(3.0, 4.0),
),
],
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(10.0))),
child: Center(
child: Padding(
padding: EdgeInsets.fromLTRB(10, 5, 10, 5),
child: Text(
"Balance: \u{20B1} ${_userBalance.toStringAsFixed(0)}",
style: TextStyle(
color: Colors.black,
fontSize: 14,
),
),
),
),
),
),
],
),
AnimatedContainer(
color: Colors.white,
// Use the properties stored in the State class.
width: double.infinity,
height: _loaderHeight,
// Define how long the animation should take.
duration: Duration(seconds: 1),
// Provide an optional curve to make the animation feel smoother.
curve: Curves.fastOutSlowIn,
child: Center(
child: Text(
"Loading, please wait",
style: TextStyle(color: Colors.grey),
),
),
),
Container(
color: Colors.white,
child: _offers == null
? Container(
child: Padding(
padding: EdgeInsets.all(30),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Icon(MdiIcons.foodAppleOutline,
size: 60, color: globals.themeColor4),
Padding(padding: EdgeInsets.only(right: 20)),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("Fetching offers",
style: TextStyle(
color: globals.themeColor4,
fontWeight: FontWeight.bold)),
Padding(padding: EdgeInsets.only(top: 5)),
Text(
"We are fetching offers for you, please hold on...",
style: TextStyle(color: Colors.grey)),
],
),
),
],
),
),
)
: Column(children: _offers),
),
],
),
),
Has anyone encountered this before and have a solution to it?
I replicated a simple version of your sample, but I wasn’t able to reproduce an issue with onCameraIdle not firing.
Now, based off of my sample, there were some behaviors that you could have misinterpreted as not working, but is actually the scrollview’s behavior taking over (since this is all inside a scrollview):
Sometimes a downward gesture on the map would pull on the scrollview instead of the map.
And an upward gesture would scroll the scrollview instead of interacting with the map.
But without any further details into the rest of your code, or a mcve that can easily reproduce your issue, it’s hard to say what’s really going on.
However, as Patrick Kelly mentioned, it’s also possible that the lack of a KeepAlive might have eventually led to the temporary disposing of your maps widget. Which is why ListView was suggested because this feature is built into it.
On the other hand, you can also implement AutomaticKeepAliveClientMixin for a similar effect, as seen over at https://stackoverflow.com/a/51738269/6668797 (but beware of the warning for the widget disposing).
Anyways, here’s my sample, and I had to make an educated guess on what your _fetchOffers() is:
class _MyHomePageState extends State<MyHomePage> {
// testing
int fetchCount = 0;
List<Widget> _offers;
_fetchOffers() {
fetchCount++;
// simulate varying data
var rng = new Random();
int start = rng.nextInt(10);
int end = start + 3 + rng.nextInt(30);
// build sample list
List<Widget> list = new List();
for (int i = start; i < end; i++) {
list.add(Text('offer$i', style: new TextStyle(fontSize: 30.0)));
}
// assuming you are using setState()
setState(() {
_offers = list;
});
}
// from google maps sample
Completer<GoogleMapController> _controller = Completer();
static final CameraPosition _kGooglePlex = CameraPosition(
target: LatLng(37.42796133580664, -122.085749655962),
zoom: 14.4746,
);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: SingleChildScrollView(
child: Column(
children: <Widget>[
Stack(
children: <Widget>[
Container(
height: MediaQuery.of(context).size.height / 2,
child: GoogleMap(
mapType: MapType.normal,
initialCameraPosition: _kGooglePlex,
gestureRecognizers: Set()
..add(Factory<PanGestureRecognizer>(() => PanGestureRecognizer()))
..add(Factory<VerticalDragGestureRecognizer>(() => VerticalDragGestureRecognizer()))
..add(Factory<HorizontalDragGestureRecognizer>(() => HorizontalDragGestureRecognizer()))
..add(Factory<ScaleGestureRecognizer>(() => ScaleGestureRecognizer())),
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller);
},
onCameraIdle: () {
_fetchOffers();
},
),
),
]
),
Container(
child: _offers == null
? Container(child: Text("Fetching offers"))
: Column(children: _offers)
),
],
),
),
floatingActionButton: FloatingActionButton(
// for logging _fetchOffers activity
child: Text(fetchCount.toString())
),
);
}
}
onCameraIdle fired every time for me, and I could visually confirm it with the changing offers data as well as the fetchCount log.
You could use a ListView or a CustomScrollView with KeepAlive
This prevents Widgets from being thrown out when scrolled out of view.
I would alternatively recommend digging into the ScrollController class

Flutter google_maps_flutter package blocking alertDialog

I am currently using the google_maps_flutter package, and I am placing it within a stack and overlaying it with a button. When the button is pressed, an alert should pop up.
The problem I am currently having is that it works upon first load, but if I exit the app, putting it in the background, and I reenter the app, the alertDialog is no longer showing up. It exists on the screen because I am unable to move the map, and I have to click the area where the button would normally be, but it is not visible.
Any ideas on what's going on?
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new Stack(
children: <Widget>[
new Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: new Opacity(
opacity: opacity,
child: new GoogleMap(
onMapCreated: initializeMap,
options: GoogleMapOptions(
//trackCameraPosition: true,
compassEnabled: true,
myLocationEnabled: true,
),
),
),
),
new Align(
alignment: new Alignment(0, 1),
child: new GestureDetector(
onTap: () {
alert();
},
child: new Image.asset(
'assets/test.png',
height: 150.0,
fit: BoxFit.cover,
),
),
),
],
),
),
),
}
alert() {
return showCupertinoDialog(
context: context,
builder: (BuildContext context) {
return new CupertinoAlertDialog(
title: new Text("hello"),
content: new Text("hello"),
actions: <Widget>[
CupertinoDialogAction(
isDefaultAction: true,
child: Text("Ok"),
onPressed: () {
Navigator.pop(context);
}
),
CupertinoDialogAction(
child: Text("Cancel"),
onPressed: () {
Navigator.pop(context);
}
)
],
);
},
);
}
Keep in mind google_maps_flutter is a developer preview at version 0.0.3. Give it some time!