flutter-geolocator icon indicating copy to clipboard operation
flutter-geolocator copied to clipboard

[Bug]: Ios cached Location

Open Raasith09 opened this issue 10 months ago • 1 comments

Please check the following before submitting a new issue.

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]
import 'dart:async'; import 'dart:math'; import 'package:aqattendance/core/helpers/dialog_helper.dart'; import 'package:aqattendance/core/helpers/sized_box.dart'; import 'package:aqattendance/core/providers/network_provider.dart'; import 'package:aqattendance/core/themes/color_palette.dart'; import 'package:aqattendance/core/widgets/custom_appbar.dart'; import 'package:aqattendance/core/widgets/main_button.dart'; import 'package:aqattendance/feature/history/viewmodel/attendance_services.dart'; import 'package:aqattendance/feature/home/model/home_model.dart'; import 'package:aqattendance/feature/home/viewmodel/home_viewmodel.dart'; import 'package:aqattendance/feature/tracker/model/location_model.dart'; import 'package:aqattendance/feature/tracker/viewmodel/nearlocation_viewmodel.dart'; import 'package:aqattendance/feature/tracker/viewmodel/tracker_viewmodel.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:aqattendance/l10n/gen_l10n/app_localizations.dart'; import 'package:flutter/services.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:geolocator/geolocator.dart'; import 'package:local_auth/local_auth.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart';

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 _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: <Widget>[ 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<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 _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]

Raasith09 avatar Dec 16 '24 08:12 Raasith09