flutter-geolocator
flutter-geolocator copied to clipboard
[Bug]: Ios cached Location
Please check the following before submitting a new issue.
- [X] I have searched the existing issues.
- [X] I have carefully read the documentation and verified I have added the required platform specific configuration.
Please select affected platform(s)
- [ ] Android
- [X] iOS
- [ ] Linux
- [ ] macOS
- [ ] Web
- [ ] Windows
Steps to reproduce
In my case first of all i get user current location and start streaming for my application purpose.
Expected results
I want accurate current location and streaming
Actual results
I got accurate current location but in rare case some time even though I killed the app and enter into that it show my last cached location after few minutes or sometime few around 20 seconds it update.
Code sample
Code sample
[Paste your code here]
class MapCio extends StatefulWidget { const MapCio({super.key});
@override State<MapCio> createState() => _MapCioState(); }
class _MapCioState extends State<MapCio> with WidgetsBindingObserver { bool _hasShownPermissionDeniedAlert = false; StreamSubscription<Position>? _positionStreamSubscription; final LocalAuthentication _auth = LocalAuthentication(); LatLng? userLocation; bool _isLoading = true; bool _isDisposed = false; bool _isMockDetected = false; GoogleMapController? mapController; late LocationService locationProvider; late UserModel dbProvider; List<LatLng> designatedLocations = []; CameraPosition? currentCameraPosition; String _buttonLabel = ""; String? lastCheckInRecordId; Future _cancelPositionStream() async { await _positionStreamSubscription?.cancel(); _positionStreamSubscription = null; }
@override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); _initializeAttendance(); }
@override void dispose() { _isDisposed = true; WidgetsBinding.instance.removeObserver(this); _cancelPositionStream(); mapController?.dispose(); super.dispose(); }
void setStateIfMounted(VoidCallback fn) { if (mounted && !_isDisposed) { setState(fn); } }
void _initializeAttendance() async { if (await NetworkProvider().checkInternetConnection()) { if (mounted && !_isDisposed) { Provider.of<UserServices>(context, listen: false).getUserData(); Future.delayed(const Duration(seconds: 2)); locationProvider = Provider.of<LocationService>(context, listen: false); if (Provider.of<UserServices>(context, listen: false).userModel == null) { Provider.of<UserServices>(context, listen: false).getUserData(); dbProvider = Provider.of<UserServices>(context, listen: false).userModel!; } else { dbProvider = Provider.of<UserServices>(context, listen: false).userModel!; }
_checkLocationPermission();
}
}
}
Future
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
}
switch (permission) {
case LocationPermission.whileInUse:
case LocationPermission.always:
await _getInitialLocation();
_getStream();
break;
case LocationPermission.deniedForever:
// Check if the alert has already been shown
if (!_hasShownPermissionDeniedAlert) {
_hasShownPermissionDeniedAlert = true; // Set the flag
// Show the alert dialog
if (mounted) {
await _showPermanentDenialDialog();
}
}
break;
case LocationPermission.denied:
// User explicitly denied permission
if (mounted) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.deniedForever) {
openAppSettings();
}
// _showPermissionDeniedSnackbar();
}
break;
default:
break;
}
}
Future
Future
Future
final locations =
await locationProvider.getLocationData(dbProvider.allowedLocation);
locationProvider.locationModelsList = locations;
designatedLocations = locations
.map(
(location) => LatLng(location.locationLat, location.locationLong))
.toList();
// Cancel any existing position stream subscription
await _cancelPositionStream();
// Define platform-specific location settings
LocationSettings locationSettings = _getPlatformSpecificSettings();
if (_isDisposed) return;
// Start listening to position stream with the specified settings
_positionStreamSubscription = Geolocator.getPositionStream(
locationSettings: locationSettings,
).listen(
(Position position) {
_onPositionUpdate(position);
},
onError: (error) => _handleLocationError(error),
cancelOnError: false,
);
} catch (e) {
debugPrint("Error getting location: $e");
}
}
void _handleLocationError(dynamic error) { debugPrint("Location stream error: $error"); _cancelPositionStream(); Utils.showSnackBar("Location error: ${error.message}", context); }
LocationSettings _getPlatformSpecificSettings() { if (defaultTargetPlatform == TargetPlatform.android) { return AndroidSettings( accuracy: LocationAccuracy.high, distanceFilter: 15, intervalDuration: const Duration(seconds: 15), ); } else if (defaultTargetPlatform == TargetPlatform.iOS) { return AppleSettings( accuracy: LocationAccuracy.high, // Most precise accuracy distanceFilter: 5, // Reduce distance filter activityType: ActivityType.fitness, showBackgroundLocationIndicator: true, ); } return const LocationSettings( accuracy: LocationAccuracy.high, distanceFilter: 10); }
void _handleMockLocation() async { if (_isDisposed) return;
_isMockDetected = true; // Set flag to prevent stream restart
await _cancelPositionStream(); // Cancel the stream
if (mounted && !_isDisposed) {
Utils.show(
context,
title: AppLocalizations.of(context)!.titlealert,
content: AppLocalizations.of(context)!.mockloc,
confirmText: "Confirm",
);
}
}
void _onPositionUpdate(Position position) async { if (_isDisposed) { _cancelPositionStream(); return; }
if (position.isMocked) {
_handleMockLocation();
return;
}
setStateIfMounted(() {
userLocation = LatLng(position.latitude, position.longitude);
_isLoading = false;
mapController?.animateCamera(CameraUpdate.newLatLng(userLocation!));
});
}
Future
@override void didChangeAppLifecycleState(AppLifecycleState state) { if (_isDisposed) return;
if (state == AppLifecycleState.paused) {
_positionStreamSubscription?.pause();
} else if (state == AppLifecycleState.resumed) {
// setStateIfMounted(() {
// userLocation = null;
// });
_positionStreamSubscription?.resume(); // Resume listening for updates
_initializeAttendance(); // Re
}
}
@override Widget build(BuildContext context) { return Scaffold( backgroundColor: Theme.of(context).scaffoldBackgroundColor, appBar: CustomAppBar(title: buttonLabel, from: "page"), body: isLoading ? const Center(child: CircularProgressIndicator()) : Padding( padding: const EdgeInsets.all(16.0), child: SingleChildScrollView( child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(15)), height: 500, child: GoogleMap( myLocationButtonEnabled: true, mapType: MapType.normal, onMapCreated: (controller) { mapController = controller; }, onCameraMove: (cameraPosition) { currentCameraPosition = cameraPosition; }, initialCameraPosition: userLocation != null ? CameraPosition(target: userLocation!, zoom: 17) : const CameraPosition( target: LatLng(0, 0), zoom: 1), markers: { if (userLocation != null) Marker( markerId: const MarkerId("user"), icon: BitmapDescriptor.defaultMarkerWithHue( BitmapDescriptor.hueBlue), position: userLocation!, ), ...designatedLocations.map((location) => Marker( markerId: MarkerId( 'designated_location${location.latitude}${location.longitude}'), position: location, infoWindow: const InfoWindow( title: "Designated Location"), )), }, circles: { ...locationProvider.locationModelsList .map((model) => Circle( circleId: CircleId( 'circle_${model.locationLat}_${model.locationLong}'), center: LatLng( model.locationLat, model.locationLong), radius: model.radius!.toDouble(), fillColor: Colors.lightBlue.withOpacity(0.4), strokeColor: Colors.lightBlue, strokeWidth: 2, )), }, ), ), 15.kH, Text( AppLocalizations.of(context)!.note, style: Theme.of(context).textTheme.labelLarge!.copyWith( color: AppThemeColors.grey500, fontWeight: FontWeight.w400), ), 15.kH, Consumer<TrackerServices>( builder: (context, trackerService, child) { return trackerService.isLoading ? const Center(child: CircularProgressIndicator()) : GestureDetector( onTap: () async { if (userLocation != null) { if (!await _handleBiometricAuth()) return; await _handleLocationCheck(trackerService); } }, child: MainButton( text: Text( _buttonLabel, style: Theme.of(context) .textTheme .bodyLarge! .copyWith( color: AppThemeColors.white500), ), ), ); }, ), ], ), ), ), ); }
double _calculateDistance(LatLng start, LatLng end) { const double earthRadius = 6371000; // meters double dLat = _degreeToRadian(end.latitude - start.latitude); double dLon = _degreeToRadian(end.longitude - start.longitude);
double a = sin(dLat / 2) * sin(dLat / 2) +
cos(_degreeToRadian(start.latitude)) *
cos(_degreeToRadian(end.latitude)) *
sin(dLon / 2) *
sin(dLon / 2);
double c = 2 * atan2(sqrt(a), sqrt(1 - a));
return earthRadius * c;
}
double _degreeToRadian(double degree) { return degree * pi / 180; }
Future
try {
// First check if device is capable of biometric authentication
final bool canAuthWithBiometrics = await _auth.canCheckBiometrics;
final bool canAuth =
canAuthWithBiometrics || await _auth.isDeviceSupported();
if (!canAuth) {
if (mounted) {
Utils.showSnackBar(
AppLocalizations.of(context)!.devwithbiocap,
context,
);
}
return false;
}
// Get available biometrics
final List<BiometricType> availableBiometrics =
await _auth.getAvailableBiometrics();
// Check if there are any biometrics enrolled
if (availableBiometrics.isEmpty) {
if (mounted) {
Utils.showSnackBar(
AppLocalizations.of(context)!.bfreattempttochckIn,
context,
);
}
return false;
}
// Attempt authentication
if (mounted) {
final bool didAuthenticate = await _auth.authenticate(
localizedReason: AppLocalizations.of(context)!.bioisreqttend,
options: const AuthenticationOptions(
biometricOnly:
true, // Force biometric only, no PIN/pattern fallback
stickyAuth: true,
sensitiveTransaction: true,
),
);
if (!didAuthenticate) {
if (mounted) {
Utils.showSnackBar(
AppLocalizations.of(context)!.authfailed,
context,
);
}
return false;
}
}
return true;
} catch (e) {
// Handle specific platform exceptions
if (e is PlatformException && mounted) {
String message = "";
switch (e.code) {
case 'NotAvailable':
case 'NotEnrolled':
message = AppLocalizations.of(context)!.biomandatory;
break;
case 'LockedOut':
case 'PermanentlyLockedOut':
message = AppLocalizations.of(context)!.manyfailedattemp;
break;
default:
message = AppLocalizations.of(context)!.autherror;
}
if (mounted) {
Utils.showSnackBar(
message,
context,
);
}
return false;
}
// For any other unexpected errors
debugPrint("Biometric error: $e");
if (mounted) {
Utils.showSnackBar(
AppLocalizations.of(context)!.ensurebiomet,
context,
);
}
return false;
}
}
// Handle location check and check-in/out process
Future
bool isWithinAnyRadius = false;
// Sales mode allows check-in/out from anywhere
if (dbProvider.isSales) {
isWithinAnyRadius = true;
// Set a default "Sales" location model for tracking
locationProvider.setNearestLocationModel(
LocationModel(
id: 1,
customerName: "Sales",
locationName: "Sales",
locationLat: userLocation!.latitude,
locationLong: userLocation!.longitude,
radius: 0, // No radius restriction for sales
),
);
} else {
// Regular mode: Check if within any designated location radius
if (designatedLocations.isEmpty) {
if (context.mounted) {
Utils.showSnackBar(
AppLocalizations.of(context)!.nodesignatedloc,
context,
);
}
return;
}
for (LocationModel location in locationProvider.locationModelsList) {
if (await _isWithinLocationRadius(location)) {
isWithinAnyRadius = true;
locationProvider.setNearestLocationModel(location);
break;
}
}
}
if (isWithinAnyRadius) {
await _performCheckInOut(attendanceServiceProvider);
} else {
if (mounted) {
Utils.showSnackBar(
AppLocalizations.of(context)!.outside,
context,
);
}
}
}
// Check if user is within radius of a specific location
Future
// Perform the actual check-in/out operation
Future
// Show loading indicator
showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context) {
return const Center(child: CircularProgressIndicator());
},
);
Map<String, dynamic> loc = {
"lat": userLocation!.latitude,
"lon": userLocation!.longitude,
};
if (context.mounted) {
await attendanceServiceProvider.checkInOrOut(
context,
loc,
_positionStreamSubscription,
locationProvider.nearestLocationModel?.customerName ?? "Sales",
locationProvider.nearestLocationModel?.id ?? 1,
locationProvider.nearestLocationModel?.locationName ?? "Sales",
);
}
} }
Screenshots or video
Screenshots or video demonstration
[Upload media here]
Version
geolocator: ^13.0.2
Flutter Doctor output
Doctor output
mohamedraasith@Mohameds-Laptop aqattendance_ios % flutter doctor Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel stable, 3.27.0, on macOS 15.2 24C101 darwin-arm64, locale en-US) [✓] Android toolchain - develop for Android devices (Android SDK version 35.0.0) [✓] Xcode - develop for iOS and macOS (Xcode 16.2) [✓] Chrome - develop for the web [✓] Android Studio (version 2024.1) [✓] VS Code (version 1.96.0) [✓] Connected device (3 available) ! Error: Browsing on the local area network for Rasi09. Ensure the device is unlocked and attached with a cable or associated with the same local area network as this Mac. The device must be opted into Developer Mode to connect wirelessly. (code -27) [✓] Network resources• No issues found!
[Paste your output here]