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
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.
To start, I have gone through more than 20 different questions and solutions here on Stack Overflow about this topic (most of them are related to the web version), I have also tried twitter, and even the FlutterDev Discord server and cannot seem to find this issue.
I am using firebase for mobile authentication for my app, and no matter what I try, I cannot seem to get the persistent auth state to work on iOS or Android.
Here is my main:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(
MultiProvider(
...
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
final const ColorScheme colorScheme = ColorScheme(
...
);
#override
Widget build(BuildContext context) {
bool isDebug = false;
if (Constants.DEBUG_BANNER == 'true') {
isDebug = true;
}
return MaterialApp(
theme: ThemeData(
...
),
routes: {
// This is a general layout of how all my routes are in case this is the issue
Screen.route: (BuildContext context) => const Screen(),
},
home: const HomeScreen(),
debugShowCheckModeBanner: isDebug,
);
}
}
the ... is just code that I think is unrelated to my question and so I am hiding it for brevity. Mostly themes, and private data
Let's just start with my google-sign-in-button and if necessary I can share others if it is important. We are using Facebook, Google, and Apple for iOS.
class GoogleSignInButton extends StatefulWidget {
const GoogleSignInButton({Key? key}) : super(key: key);
#override
_GoogleSignInButtonState createState() => _GoogleSignInButtonState();
}
class _GoogleSignInButtonState extends State<GoogleSignInButton> {
bool _isSigningIn = false;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: _isSigningIn
? CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(MRRM.colorScheme.primary),
)
: OutlinedButton(
key: const Key('google_sign_in_button'),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.white),
shape: MaterialStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(40),
),
),
),
onPressed: () async {
setState(() {
_isSigningIn = true;
});
context.read<Member>().signInWithGoogle(context: context).then<void>((void user) {
setState(() {
_isSigningIn = false;
});
Navigator.pushReplacementNamed(context, UserInfoScreen.route);
});
},
child: Padding(
padding: const EdgeInsets.fromLTRB(0, 10, 0, 10),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Image(
image: AssetImage('assets/images/png/google_logo.png'),
height: 35.0,
),
Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
'Sign in with Google',
style: TextStyle(
fontSize: 20,
color: MRRM.colorScheme.secondary,
fontWeight: FontWeight.w600,
),
))
],
),
),
),
);
}
}
I am using the provider pub, which is what context.read<Object?>() is from.
Here is the signInWithGoogle function;
Future<String> signInWithGoogle({required BuildContext context}) async {
final FirebaseAuth _auth = FirebaseAuth.instance;
final GoogleSignIn googleSignIn = GoogleSignIn();
final GoogleSignInAccount? googleSignInAccount =
await googleSignIn.signIn();
if (googleSignInAccount != null) {
final GoogleSignInAuthentication googleSignInAuthentication =
await googleSignInAccount.authentication;
final AuthCredential credential = GoogleAuthProvider.credential(
accessToken: googleSignInAuthentication.accessToken,
idToken: googleSignInAuthentication.idToken,
);
try {
final UserCredential userCredential =
await _auth.signInWithCredential(credential);
_firebaseUser = userCredential.user!;
_authType = AuthType.Google;
_uuId = _firebaseUser.uid;
notifyListeners();
} on FirebaseAuthException catch (e) {
if (e.code == 'account-exists-with-different-credential') {
ScaffoldMessenger.of(context).showSnackBar(
customSnackBar(
content: 'The account already exists with different credentials.',
),
);
} else if (e.code == 'invalid-credential') {
ScaffoldMessenger.of(context).showSnackBar(
customSnackBar(
content: 'Error occurred while accessing credentials. Try again.',
),
);
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
customSnackBar(
content: 'Error occurred using Google Sign-In. Try again.',
),
);
}
}
return getMemberLogin();
}
This is contained in my Member object, which just stores all of the Auth data as well as the Member specific data that comes from one of our internal API's, and the member data is stored as an App State object in provider, which is linked in the main.dart file
The getMemberLogin() function is just taking the UUID from the auth and sending it to an API and getting internal member data, I would hope that a simple post request isn't what is causing this. but if you think it might let me know and I will try to post it while obfuscating any NDA related data.
This is the home/splash Screen that handles the initial routing and goes to the loadingScreen that is supposed to be checking if there is a persisted login and going to the UserInfo screen instead of the Auth Screen.
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
static const String route = '/home';
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
key: const Key('Home'),
children: <Widget>[
Expanded(
child: Image.asset('assets/images/png/Retail_Rebel_Primary.png'),
),
BlinkingTextButton(
key: const Key('blinking_text_button'),
textButton: TextButton(
child: Text(
'Tap to continue',
style: TextStyle(
color: MRRM.colorScheme.primary,
fontSize: 16.0,
),
),
onPressed: () {
Navigator.of(context).pushReplacementNamed(LoadingScreen.route);
},
),
),
Container(
height: 8.0,
),
],
),
);
}
}
And lastly, this is the LoadingScreen that the HomeScreen navigates to:
class LoadingScreen extends StatelessWidget {
const LoadingScreen({Key? key}) : super(key: key);
static const String route = '/loadingScreen';
#override
Widget build(BuildContext context) {
return StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (BuildContext context, AsyncSnapshot<User?> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
if (snapshot.hasData) {
print('user is logged in');
SchedulerBinding.instance!.addPostFrameCallback((_) {
Navigator.of(context).pushReplacementNamed(UserInfoScreen.route);
});
return const Text('');
} else {
print('no user is logged in');
SchedulerBinding.instance!.addPostFrameCallback((_) {
Navigator.of(context).pushReplacementNamed(AuthScreen.route);
});
return const Text('');
}
}
return const SplashScreen();
},
);
}
}
Not sure if possibly the way that I am handing routing may be the issue, but it is very common for me to use Navigator.of(context).pushReplacementNamed(); unless popping is necessary then I will typically just use Navigator.of(context).pop();. I usually only use .pop() for modals/alertDialogs, and for things like QR scanners to return to previous screen.
Sorry if this is too much info, or I forgot a ton of stuff. I have been working on trying to get this fixed for a little over a week now and am kind of getting frustrated.
Thank you for any and all responses.
Just because I think it is important to see what I have looked at already, here is a list of a couple of other questions I have looked through that did not help.
This one I believe is dated as of August 2020, especially considering that onAuthStateChanges has been changed to a stream authStateChanges().
I have also tried just implementing auth in the exact way described in the docs here but same issue.
I also tried just using:
FirebaseAuth.instance.authStateChanges().then((User? user) {
if (user != null) {
Navigator.of(context).pushReplacementNamed(UserInfoScreen.route);
} else {
Navigator.of(context).pushReplacementNamed(AuthScreen.route);
}
Which didn't work. I have also attempted to just simply check if there is a current user with:
User user = FirebaseAuth.instance.currentUser;
if (user != null && user.uid != null) {
Navigator.of(context).pushReplacementNamed(UserInfoScreen.route);
} else {
Navigator.of(context).pushReplacementNamed(AuthScreen.route);
}
which still always went to AuthScreen I have also tried all of these methods as asynchronous tasks to see if maybe it is just taking a second to load, and same issue. The weirdest one is with the current method if I take out the if(snapshot.connectionState == ConnectionState.waiting) from the LoadingScreen it will print out no user is logged in immediately followed by user is logged in and then no user is logged in again and then it will navigate to AuthScreen
If you follow what I have done up above, and make a single change, it will work with persisted logins.
change:
class LoadingScreen extends StatelessWidget {
const LoadingScreen({Key? key}) : super(key: key);
static const String route = '/loadingScreen';
#override
Widget build(BuildContext context) {
return StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (BuildContext context, AsyncSnapshot<User?> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
if (snapshot.hasData) {
print('user is logged in');
SchedulerBinding.instance!.addPostFrameCallback((_) {
Navigator.of(context).pushReplacementNamed(UserInfoScreen.route);
});
return const Text('');
} else {
print('no user is logged in');
SchedulerBinding.instance!.addPostFrameCallback((_) {
Navigator.of(context).pushReplacementNamed(AuthScreen.route);
});
return const Text('');
}
}
return const SplashScreen();
},
);
}
}
to
class LoadingScreen extends StatelessWidget {
const LoadingScreen({Key? key}) : super(key: key);
static const String route = '/loadingScreen';
#override
Widget build(BuildContext context) {
return StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (BuildContext context, AsyncSnapshot<User?> snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
if (snapshot.hasData) {
print('user is logged in');
SchedulerBinding.instance!.addPostFrameCallback((_) {
Navigator.of(context).pushReplacementNamed(UserInfoScreen.route);
});
return const Text('');
} else {
print('no user is logged in');
SchedulerBinding.instance!.addPostFrameCallback((_) {
Navigator.of(context).pushReplacementNamed(AuthScreen.route);
});
return const Text('');
}
}
return const SplashScreen();
},
);
}
}
I'm using mapbox_gl, which is a Flutter plugin for access to Mapbox services. Application needs to display the initial camera position fed from user's current location acquired via geolocator plugin. On the Android emulator, setting the current location to coordinates for Ljubljana, Slovenia (around 46N, 13E) displays map of Republic of the Congo, which is obviously incorrect.
The MapboxMap widget is built the following way:
class HomeScreen extends StatelessWidget {
final String mapboxToken = 'XXX-TOKEN-XXX';
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: _acquireCurrentPosition(),
builder: (BuildContext context, AsyncSnapshot snapshot) =>
snapshot.hasData
? MapboxMap(
accessToken: mapboxToken,
minMaxZoomPreference: MinMaxZoomPreference(6.0, 15.0),
compassEnabled: false,
initialCameraPosition: CameraPosition(
target: snapshot.data,
),
)
: Center(
child: CircularProgressIndicator(),
),
),
);
}
Future<LatLng> _acquireCurrentPosition() async {
Position position = await getCurrentPosition(
desiredAccuracy: LocationAccuracy.high,
);
return LatLng(position.latitude, position.longitude);
}
}
Method _acquireCurrentPosition() correctly acquires latitude/longitude combination, and was tested on Android emulator, iOS Simulator and even physical Android device (Xiaomi Redmi Note 8 Pro). Error with the initial camera position persists even when using a different location.
Any sort of help is greatly appreciated.
After some workarounds, I managed to identify and fix the problem using the following steps:
First, drop the geolocator package and replace it with location package, and explicitly declare location permissions on Android and iOS, via mentioning them in AndroidManifest.xml
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
and Info.plist respectively
<key>NSLocationWhenInUseUsageDescription</key>
<string>Shows your location on the map and helps improve the map</string>
Next, instead of setting the initial camera location via initialCameraPosition parameter, you can use the onMapCreated callback:
// This is how you'd build the MapboxMap widget
MapboxMap(
accessToken: mapboxToken,
onMapCreated: (controller) {
_acquireCurrentLocation().then((LatLong location) {
if (location != null) {
controller.animateCamera(
CameraUpdate.newCameraPosition(
CameraPosition(
target: location,
),
),
);
}
}).catchError((error) => print(error));
},
minMaxZoomPreference: MinMaxZoomPreference(6.0, 15.0),
initialCameraPosition: CameraPosition(
target: LatLng(45.45, 45.45),
),
);
// Method that uses location plugin
Future<LatLng> _acquireCurrentLocation() async {
Location location = new Location();
bool serviceEnabled;
PermissionStatus permissionGranted;
LocationData locationData;
serviceEnabled = await location.serviceEnabled();
if (!serviceEnabled) {
serviceEnabled = await location.requestService();
if (!serviceEnabled) {
return null;
}
}
permissionGranted = await location.hasPermission();
if (permissionGranted == PermissionStatus.denied) {
permissionGranted = await location.requestPermission();
if (permissionGranted != PermissionStatus.granted) {
return null;
}
}
locationData = await location.getLocation();
return LatLng(locationData.latitude, locationData.longitude);
}
My personal recommendation is to change the minimum SDK version for Android to 23.
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,
));