Description
Please check the following before submitting a new issue.
- I have searched the existing issues.
- I have carefully read the documentation and verified I have added the required platform specific configuration.
Please select affected platform(s)
- Android
- 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 createState() => _MapCioState();
}
class _MapCioState extends State with WidgetsBindingObserver {
bool _hasShownPermissionDeniedAlert = false;
StreamSubscription? _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 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(context, listen: false).getUserData();
Future.delayed(const Duration(seconds: 2));
locationProvider = Provider.of(context, listen: false);
if (Provider.of(context, listen: false).userModel ==
null) {
Provider.of(context, listen: false).getUserData();
dbProvider =
Provider.of(context, listen: false).userModel!;
} else {
dbProvider =
Provider.of(context, listen: false).userModel!;
}
_checkLocationPermission();
}
}
}
Future _checkLocationPermission() async {
if (_isDisposed) return;
LocationPermission permission = await Geolocator.checkPermission();
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 _getInitialLocation() async {
try {
Position position = await Geolocator.getCurrentPosition(
locationSettings: const LocationSettings(
accuracy: LocationAccuracy.bestForNavigation,
distanceFilter: 10));
setState(() {
userLocation = LatLng(position.latitude, position.longitude);
_isLoading = false;
});
} catch (e) {
debugPrint("Error fetching initial location: $e");
setState(() => _isLoading = false);
}
}
Future _showPermanentDenialDialog() async {
return showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: const Text("Permission required"),
content: Text(AppLocalizations.of(context)!.deniedloc),
actions: [
TextButton(
child: const Text("Ok"),
onPressed: () {
openAppSettings();
Navigator.of(context).pop();
},
),
],
);
},
);
}
Future _getStream() async {
if (_isDisposed || _isMockDetected) return;
try {
await _fetchData();
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 _fetchData() async {
final latestRecord =
await AttendanceServices().getLastCheckInRecordForToday();
if (latestRecord != null) {
setState(() {
_buttonLabel = latestRecord.checkIn && !latestRecord.checkOut
? AppLocalizations.of(context)!.checkout
: AppLocalizations.of(context)!.checkin;
});
} else {
setState(() {
_buttonLabel = AppLocalizations.of(context)!.checkin;
});
}
}
@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(
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 _handleBiometricAuth() async {
if (!dbProvider.enforceBiometric) return true;
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 _handleLocationCheck(
TrackerServices attendanceServiceProvider) async {
if (userLocation == null) return;
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 _isWithinLocationRadius(LocationModel location) async {
LatLng locationLatLng = LatLng(location.locationLat, location.locationLong);
double distance = _calculateDistance(userLocation!, locationLatLng);
return distance <= location.radius!.toDouble();
}
// Perform the actual check-in/out operation
Future _performCheckInOut(
TrackerServices attendanceServiceProvider) async {
if (!context.mounted) return;
// 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]