As the title said Is there any way to continuously check geoLocator.isLocationServiceEnabled()?
I want to check for GPS status at all times and show an image if the GPS is turned on/off by the user.
need to continuously run await geoLocator.isLocationServiceEnabled();
void _getLocation() {
Geolocator geoLocator = Geolocator();
try {
geoLocator.checkGeolocationPermissionStatus().then((granted) async {
if (granted != null) {
noGPS = !await geoLocator.isLocationServiceEnabled();
bool firstRun = true;
geoLocator.getPositionStream(LocationOptions(
distanceFilter: 20,
accuracy: LocationAccuracy.best,
timeInterval: 10000),GeolocationPermission.locationWhenInUse)
.listen((position) {
print(position.longitude);
longitude = position.longitude;
latitude= position.latitude;
if(firstRun){
mapController.animateCamera(CameraUpdate.newCameraPosition(CameraPosition(
target: LatLng(latitude, longitude),
zoom: 15,
)));
}
firstRun=false;
_addGeoPoint();
}
);
}else {
noGPS=true;
}
});
} on Exception {
}
}
I ended up doing it this way
FutureBuilder<bool>(
future: geoLocator.isLocationServiceEnabled(),
initialData: false,
builder:
(BuildContext context, AsyncSnapshot<bool> snapshot) {
if (!snapshot.data) {
return FadeTransition(
opacity: _animationController,
child: IconButton(
icon: Icon(Icons.gps_fixed),
onPressed: () => null,
color: Colors.red,
));
Related
I am working on flutter app where its all start with the user login to app,then in the home screen app check if if location is enabled,and if is not enabled the app ask for permission, i granted it but it does not show the widget as i expected it only stack on widget where location is null but the second time when i hot restart it enter as i expect so what is going on when location is off and when i granted for the first time,Also as user move i expect the location to be updated on the map to current location but both of these scenario they are not occuring,I really really need help.
Here are my code in homepage for listening location change
final initialPosition = LatLng(-6.77146, 39.23958);
_getCurrentLocation() async {
try {
bool serviceEnabled;
LocationPermission permission;
serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text("Location Service Disabled"),
content: Text("Please enable location service"),
actions: <Widget>[
TextButton(
child: CustomText(text: "OK", color: black, size: 20),
onPressed: () {
Navigator.of(context).pop();
},
),
],
),
);
}
permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied ||
permission == LocationPermission.deniedForever) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
return;
}
}
Position position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.best,
);
setState(() {
currentPosition = position;
});
_updateCameraPosition(position);
_locationSubscription = await Geolocator.getPositionStream(
locationSettings: LocationSettings(
accuracy: LocationAccuracy.best,
distanceFilter: 5,
),
).listen((position) {
setState(() {
currentPosition = position;
print("###################################:${currentPosition}");
});
_updateCameraPosition(position);
});
} catch (e) {
print("**********************************:${e}");
}
}
Here is my code in build method
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: currentPosition != null
? Stack(children: [
GoogleMap(
padding: EdgeInsets.only(bottom: bottomPaddingOfMap),
mapType: MapType.normal,
myLocationButtonEnabled: true,
zoomGesturesEnabled: false,
zoomControlsEnabled: true,
myLocationEnabled: true,
markers: {
Marker(
markerId: const MarkerId('currentLocation'),
position: LatLng(currentPosition!.latitude,
currentPosition!.longitude))
},
// polygons: _polygons,
initialCameraPosition: CameraPosition(
target: LatLng(currentPosition!.latitude,
currentPosition!.longitude),
zoom: 18,
),
onMapCreated: (controller) {
_GoogleMapcontroller = controller;
},
),
//othe widgets here
])
: Stack(children: [
GoogleMap(
padding: EdgeInsets.only(bottom: bottomPaddingOfMap),
mapType: MapType.normal,
zoomGesturesEnabled: false,
zoomControlsEnabled: true,
initialCameraPosition: CameraPosition(
target: LatLng(initialPosition.latitude,
initialPosition.longitude),
zoom: 18,
),
),
Here is update camera position
void _updateCameraPosition(Position position) {
if (_GoogleMapcontroller != null) {
_GoogleMapcontroller!.animateCamera(
CameraUpdate.newCameraPosition(
CameraPosition(
target: LatLng(position.latitude, position.longitude),
zoom: 18,
),
),
);
}
}
Here is init state method
#override
void initState() {
super.initState();
_getCurrentLocation();
}
I implemented the FutureBuilder with the code below in order to get the distance from the user and the item that he wants buy but I get a weird result between Android and iOS.
On Android works well and I get the distance for each item.
But on iOS I don't have the distance for each item and infact some item has the distance and some items get null value.
class ProductHorizontalListItem extends StatelessWidget {
const ProductHorizontalListItem({
Key? key,
required this.product,
required this.coreTagKey,
this.onTap,
}) : super(key: key);
final Product product;
final Function? onTap;
final String coreTagKey;
#override
Widget build(BuildContext context) {
final PsValueHolder valueHolder =
Provider.of<PsValueHolder>(context, listen: false);
Future<double> getCurrentLocation() async {
Position position = await Geolocator.getCurrentPosition();
double lat = position.latitude;
double long = position.longitude;
final double distanceInMeters = Geolocator.distanceBetween(
double.parse(position.latitude.toString()),
double.parse(position.longitude.toString()),
double.parse(product.itemLocation!.lat.toString()),
double.parse(product.itemLocation!.lng.toString()),
);
return Future.value(distanceInMeters);
}
return FutureBuilder<double>(
future: getCurrentLocation(),
builder: (BuildContext context, AsyncSnapshot<double> snapshot) {
return InkWell(
onTap: onTap as void Function()?,
child: Container(
margin: const EdgeInsets.only(
left: PsDimens.space4, right: PsDimens.space4,
bottom: PsDimens.space12),
child: Text(
'${snapshot.data}',
textAlign: TextAlign.start,
style: Theme.of(context).textTheme.caption!.copyWith(
color: PsColors.textColor3
)))
);});
}
}
I also tried to handle the states of FutureBuilder in each way but nothing.
iOS works bad, why?
I'm on Flutter 3.0.5 with Android Studio Chipmunk.
UPDATE CODE WITH STATE MANAGEMENT
class ProductHorizontalListItem extends StatelessWidget {
const ProductHorizontalListItem({
Key? key,
required this.product,
required this.coreTagKey,
this.onTap,
}) : super(key: key);
final Product product;
final Function? onTap;
final String coreTagKey;
#override
Widget build(BuildContext context) {
final PsValueHolder valueHolder =
Provider.of<PsValueHolder>(context, listen: false);
Future<double> getCurrentLocation() async {
Position position = await Geolocator.getCurrentPosition();
double lat = position.latitude;
double long = position.longitude;
final double distanceInMeters = Geolocator.distanceBetween(
double.parse(position.latitude.toString()),
double.parse(position.longitude.toString()),
double.parse(product.itemLocation!.lat.toString()),
double.parse(product.itemLocation!.lng.toString()),
);
return Future.value(distanceInMeters);
}
return FutureBuilder<double>(
future: getCurrentLocation(),
builder: (BuildContext context, AsyncSnapshot<double> snapshot) {
print(snapshot);
if (snapshot.connectionState==ConnectionState.waiting) {
return const Text('Loading...');
}
if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) {
return InkWell(
onTap: onTap as void Function()?,
child: Container(
margin: const EdgeInsets.only(
left: PsDimens.space4, right: PsDimens.space4,
bottom: PsDimens.space12),
child: Text(
'${snapshot.data}',
textAlign: TextAlign.start,
style: Theme.of(context).textTheme.caption!.copyWith(
color: PsColors.textColor3
)))
);}
if (snapshot.hasError) {
return const Text('Error');
}
return Container();
}
});
}
}
With state management, instead of null some item are stuck on "Loading..."
First, you should check the snapshot state before accessing its data. Otherwise you can get a null value, since the treatment has not been finished yet. Check snapshot.connectionState and snapshot.hasData before accessing snapshot.data.
Then, there is no need to convert latitudes and longitudes to String, then back to double.
Eventually, you can replace the definition of final Function? onTap; by a VoidCallback, to avoid parsing it in the Inkwell button.
Try this out:
#override
Widget build(BuildContext context) {
Future<double> getCurrentLocation() async {
Position position = await Geolocator.getCurrentPosition();
final double distanceInMeters = Geolocator.distanceBetween(
position.latitude,
position.longitude,
product.itemLocation!.latitude,
product.itemLocation!.longitude,
);
return Future.value(distanceInMeters);
}
return FutureBuilder<double>(
future: getCurrentLocation(),
builder: (BuildContext context, AsyncSnapshot<double> snapshot) {
if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) {
return InkWell(
onTap: onTap,
child: Container(
margin: const EdgeInsets.only(
left: PsDimens.space4,
right: PsDimens.space4,
bottom: PsDimens.space12,
),
child: Text(
'${snapshot.data}',
textAlign: TextAlign.start,
style: Theme.of(context).textTheme.caption!.copyWith(
color: PsColors.textColor3,
),
),
),
);
} else if (!snapshot.hasData) {
// Handle error case
return const Text('error');
} else {
// Display a loader or whatever to wait for the Future to complete
return const Center(child: CircularProgressIndicator());
}
},
);
}
I tried the code by replacing position and product.itemLocation by location of actual cities, and the distanceInMeters is correct.
I am learning Fllutter to build a map based App. I am using the location pub.dev plugin for managing location permissions.
I have created a Location() object Location location = Location()
and when I call
await location.hasPermission()
and I don't have granted permission , it actually requests the permission , without me calling
await location.requestPermission()
This causes many problems , such as asking two permissions at once , something not permitted by Android , so the app crashes , or by removing await location.requestPermission(), the app ask for the user's permission but it does not wait for the result.
I tested it on my Pixel 5 via adb , running Android 12
What is going on? I have not found another reference of this issue.
Here is the full Code Sample:
class Gmap extends StatefulWidget {
const Gmap({ Key? key }) : super(key: key);
#override
State<Gmap> createState() => _GmapState();
}
class _GmapState extends State<Gmap> {
String _mapStyle = "";
late GoogleMapController mapController;
late Future<LatLng> ull ;
Location location = Location();
final LatLng _center = const LatLng(37.983810, 23.727539);
LatLng _userLocation = const LatLng(37.983810, 23.727539);
Future<LatLng> userLocation() async{
PermissionStatus _permissionGranted;
bool _serviceEnabled;
_serviceEnabled = await location.serviceEnabled();
if (!_serviceEnabled) {
_serviceEnabled = await location.requestService();
if (!_serviceEnabled) {
return _center;
}
}
_permissionGranted = await location.hasPermission(); //it requests permission here
if (_permissionGranted == PermissionStatus.denied) {
_permissionGranted = await location.requestPermission()//it requests here again;
if (_permissionGranted != PermissionStatus.granted) {
return _center;
}
}
LocationData l = await location.getLocation();
setState((){
_userLocation = LatLng(l.latitude!, l.longitude!);
});
return _userLocation;
}
void centerLocation(){
CameraPosition userCamera = CameraPosition(
target: _userLocation,
zoom: 14.0,
);
CameraUpdate moveTo = CameraUpdate.newCameraPosition(userCamera);
mapController.animateCamera(moveTo);
}
void _onMapCreated(GoogleMapController controller) async{
mapController = controller;
mapController.setMapStyle(_mapStyle);
centerLocation();
}
#override
void initState() {
super.initState();
rootBundle.loadString('assets/style.txt').then((string) {
_mapStyle = string;
});
ull = userLocation();
}
Widget googleMap(userLocation){
return GoogleMap(
onMapCreated: _onMapCreated,
myLocationEnabled:true,
initialCameraPosition: CameraPosition(
target: userLocation,
zoom: 6.0,
),
zoomControlsEnabled: false, //dont show zoom buttons
compassEnabled: false,
myLocationButtonEnabled: false,
);
}
#override
Widget build(BuildContext context){
return Scaffold(
body: FutureBuilder<LatLng>(
future: ull,
builder: ( context , AsyncSnapshot<LatLng> snapshot){
Widget g;
if(snapshot.hasData){
g = googleMap(_userLocation);
}else if(snapshot.hasError){
g = googleMap(_center);
}else{
g = googleMap(_center);
}
return g;
}
),
floatingActionButton: Column(mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[
ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
primary: ourGreen,
shape: const CircleBorder(),
padding: const EdgeInsets.all(15),),
child: const Icon(Icons.filter_alt_rounded, size: 27, color: ourDark),),
const SizedBox(height: 9),
ElevatedButton(
onPressed: () {centerLocation(); },
style: ElevatedButton.styleFrom(
primary: const Color(0xFF1A202C),
shape: const CircleBorder(),
padding: const EdgeInsets.all(15)),
child: const Icon(Icons.location_on,
size: 27, color: Colors.greenAccent)),
]),
);
}
}
You can use the permission_handler package and request the permission status like this:
var status = await Permission.camera.status;
if (status.isDenied) {
// We didn't ask for permission yet or the permission has been denied before but not permanently.
}
// You can can also directly ask the permission about its status.
if (await Permission.location.isRestricted) {
// The OS restricts access, for example because of parental controls.
}
See the documentation: https://pub.dev/packages/permission_handler
Update: I never got this to work for some reason following the documentation or the other comments. Here is what worked for me:
Future<LatLng> askPermissionAndGetLocation() async {
if (await Permission.locationWhenInUse.serviceStatus.isEnabled) { //this here checks
//if the permission is granted and if not , it requests it.
if (await Permission.location.request().isGranted) {
LocationData l = await Location().getLocation();
setState(() {
_userLocation = LatLng(l.latitude!, l.longitude!);
});
return _userLocation;
}
}
return Future.error("Location services disabled or restricted");
}
This is the only way I got it to work and I do not see a reason why. If anyone has any insight please leave a comment
I just started learning flutter and I am trying to build a mobile app using google maps.
I am following a tutorial which is bulding an app that track my position all the time:
It is working pretty good, the problem is that when ever I try to zoom in/out it take me back to my position with the default zoom even if I am not mooving.
I am trying to be able to zoom in/out even if i am moving and tak me back my position only when i click on button.
here is the source code:
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:location/location.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Maps',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Map Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
StreamSubscription _locationSubscription;
Location _locationTracker = Location();
Marker marker;
Circle circle;
GoogleMapController _controller;
static final CameraPosition initialLocation = CameraPosition(
target: LatLng(37.42796133580664, -122.085749655962),
zoom: 14.4746,
);
Future<Uint8List> getMarker() async {
ByteData byteData = await DefaultAssetBundle.of(context).load("assets/car_icon.png");
return byteData.buffer.asUint8List();
}
void updateMarkerAndCircle(LocationData newLocalData, Uint8List imageData) {
LatLng latlng = LatLng(newLocalData.latitude, newLocalData.longitude);
this.setState(() {
marker = Marker(
markerId: MarkerId("home"),
position: latlng,
rotation: newLocalData.heading,
draggable: false,
zIndex: 2,
flat: true,
anchor: Offset(0.5, 0.5),
icon: BitmapDescriptor.fromBytes(imageData));
circle = Circle(
circleId: CircleId("car"),
radius: newLocalData.accuracy,
zIndex: 1,
strokeColor: Colors.blue,
center: latlng,
fillColor: Colors.blue.withAlpha(70));
});
}
void getCurrentLocation() async {
try {
Uint8List imageData = await getMarker();
var location = await _locationTracker.getLocation();
updateMarkerAndCircle(location, imageData);
if (_locationSubscription != null) {
_locationSubscription.cancel();
}
_locationSubscription = _locationTracker.onLocationChanged().listen((newLocalData) {
if (_controller != null) {
_controller.animateCamera(CameraUpdate.newCameraPosition(new CameraPosition(
bearing: 192.8334901395799,
target: LatLng(newLocalData.latitude, newLocalData.longitude),
tilt: 0,
zoom: 18.00)));
updateMarkerAndCircle(newLocalData, imageData);
}
});
} on PlatformException catch (e) {
if (e.code == 'PERMISSION_DENIED') {
debugPrint("Permission Denied");
}
}
}
#override
void dispose() {
if (_locationSubscription != null) {
_locationSubscription.cancel();
}
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: GoogleMap(
mapType: MapType.hybrid,
initialCameraPosition: initialLocation,
markers: Set.of((marker != null) ? [marker] : []),
circles: Set.of((circle != null) ? [circle] : []),
onMapCreated: (GoogleMapController controller) {
_controller = controller;
},
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.location_searching),
onPressed: () {
getCurrentLocation();
}),
);
}
}
Please help me guys!
Thanks.
You can try this
Declare a variable that hold you position as your location changes
Map _position;
The in your location change listener
_locationSubscription = _locationTracker.onLocationChanged().listen((newLocalData) {
setState(() {
_position = {
"lat": newLocalData.latitude,
"lng": newLocalData.longitude,
"heading": newLocalData.heading,
};
});
if (_controller != null) updateMarkerAndCircle(newLocalData, imageData);
});
Finally, call this method each time you want to center the map to your current position
void _gotoCurrentPosition() {
if (null != _controller && null != _position) {
_controller.animateCamera(CameraUpdate.newCameraPosition(new CameraPosition(
bearing: _position["heading"],
target: LatLng(_position["lat"], _position["lng"]),
tilt: 0,
zoom: 18.00),
),);
}
}
The idea is your application is focusing on your location and when you try to override an action it will continue to focus on the first taste which focusing your location. you have to separate your methods so when your application focusing on your location, you can still cancel the focus by override the first method onTap or onCameraMove.
_locationSubscription = _locationTracker.onLocationChanged().listen((newLocalData) {
if (_controller != null) {
_controller.animateCamera(CameraUpdate.newCameraPosition(new CameraPosition(
bearing: 192.8334901395799,
target: LatLng(newLocalData.latitude, newLocalData.longitude),
tilt: 0,
zoom: 18.00)));
updateMarkerAndCircle(newLocalData, imageData);
}
});
This is the reason why the maps take your back to your location when you are trying to move it, because the camera always animated to your location position
I am trying to write a program to check if the time selected by the user already exists in the firebase firestore or not. If it does then I navigate back to the page where they select time again.
But as of now, I am succeeded in sending the date and time to firebase and but not the latter part.
DateTime _eventDate;
bool processing;
String _time;
bool conditionsStatisfied ;
#override
void initState() {
super.initState();
_eventDate = DateTime.now();
processing = false ;
}
inside showDatePicker()
setState(() {
print('inside the setState of listTile');
_eventDate = picked ;
});
inside the button (SAVE):
onPressed: () async {
if (_eventDate != null) {
final QuerySnapshot result = await FirebaseFirestore
.instance
.collection('events')
.where('event_date', isEqualTo: this._eventDate)
.where('selected_time', isEqualTo: this._time)
.get();
final List <DocumentSnapshot> document = result.docs;
if (document.length > 0) {
setState(() {
print('inside the method matching conditions');
showAlertDialogue(context);
});
}else{
final data = {
// "title": _title.text,
'selected_time ': this._time,
"event_date": this._eventDate
};
if (widget.note != null) {
await eventDBS.updateData(widget.note.id, data);
} else {
await eventDBS.create(data);
}
Navigator.pop(context);
setState(() {
processing = false;
});
}
};
some guidance needed on how do I resolve this issue!
Also, because of the else statement now the program won't write the date into firestore.
After Alot of research, I came to realize that if you send the data from calendar in DateTime format then, because of the timestamp at the end of the Date it becomes impossible to match to dates. Hence I formatted the DateTime value into (DD/MM/YYYY).
Here is the rest of the code for reference:
class _AddEventPageState extends State<AddEventPage> {
String _eventDate;
bool processing;
String _time;
#override
void initState() {
super.initState();
// _eventDate = DateTime.now();
processing = false ;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Please select a date'),),
body: Column(
children: [
hourMinute30Interval(),
Text('$_time'),
ListView(
scrollDirection: Axis.vertical,
shrinkWrap: true,
children: <Widget>[
ListTile(
title: Text(
'$_eventDate'),
onTap: () async {
DateTime picked = await showDatePicker(context: context,
initialDate: DateTime.now(),
firstDate: DateTime(DateTime.now().year - 1),
lastDate: DateTime(DateTime.now().year + 10),);
if (picked != null) {
setState(() {
print('inside the setState of listTile');
_eventDate = DateFormat('dd/MM/yyyy').format(picked) ;
});
}
},
),
SizedBox(height: 10.0),
ListTile(
title: Center(
child: Text('Select time for appointment!', style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20,
),
),
),
),
processing
? Center(child: CircularProgressIndicator())
: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Material(
elevation: 5.0,
borderRadius: BorderRadius.circular(30.0),
color: Theme
.of(context)
.primaryColor,
child:MaterialButton(
child: Text('SAVE', style: TextStyle(
fontSize: 20,
color: Colors.white,
fontWeight: FontWeight.bold,
)),
onPressed: () async {
if (_eventDate != null) {
AddingEventsUsingRajeshMethod().getAvailableSlots(
_eventDate, _time).then((QuerySnapshot docs) async {
if (docs.docs.length == 1) {
showAlertDialogue(context);
}
else{
final data = {
// "title": _title.text,
'selected_time': this._time,
"event_date": _eventDate,
};
if (widget.note != null) {
await eventDBS.updateData(widget.note.id, data);
} else {
await eventDBS.create(data);
}
Navigator.pop(context);
setState(() {
processing = false;
});
}
});
}
}
),
),
),
],
),
],
),
);
}
showAlertDialogue method :
showAlertDialogue(BuildContext context) {
Widget okButton = FlatButton(onPressed: (){
Timer(Duration(milliseconds: 500), () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => datePicker()),
);
});
}, child: Text(' OK! '));
AlertDialog alert = AlertDialog(
title: Text('Slot unavailable'),
content: Text('This slot is already booked please select another slot'),
actions: [
okButton,
],
);
showDialog(context: context ,
builder: (BuildContext context){
return alert ;
}
);
}
The hourMinute30Interval() is nothing but a Widget that returns a timePickerSpinner which is a custom Widget. Tap here for that.
The Query that is run after passing the _eventDate and _time is in another class, and it goes as follows :
class AddingEventsUsingRajeshMethod {
getAvailableSlots(String _eventDate , String _time){
return FirebaseFirestore.instance
.collection('events')
.where('event_date', isEqualTo: _eventDate )
.where('selected_time', isEqualTo: _time)
.get();
}
}
You can name it something prettier ;)