Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: Ios cached Location #1619

Open
3 of 8 tasks
Raasith09 opened this issue Dec 16, 2024 · 0 comments
Open
3 of 8 tasks

[Bug]: Ios cached Location #1619

Raasith09 opened this issue Dec 16, 2024 · 0 comments

Comments

@Raasith09
Copy link

Please check the following before submitting a new issue.

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]
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 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]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant