[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]
@Raasith09,
Thanks for your issue, It is possible that it takes some time to determine your location. It is also possible that in the meantime you receive "the last known" location. In order to speed up fetching your current location try to go outside and try to play with the LocationSettings.
Kind regards,
Without additional information, we are unfortunately not able to resolve this issue. Therefore, we reluctantly closed this issue for now. If you run into this issue later, feel free to file a new issue with a reference to this issue. Add a description of detailed steps to reproduce, expected and current behaviour, logs and the output of 'flutter doctor -v'. Thanks for your contribution.