-
Notifications
You must be signed in to change notification settings - Fork 272
Open
Description
Required Reading
- Confirmed
Plugin Version
flutter_background_geolocation: ^4.18.2
Mobile operating-system(s)
- iOS
- Android
Device Manufacturer(s) and Model(s)
iphone 11
Device operating-systems(s)
26.1
What do you require assistance about?
Hello, We have been testing flutter_background_geolocation for the last 2 days, but we’re facing issues with continuous location updates, in all app states, especially in background and terminated (killed) app states.
Before proceeding with purchasing the license, we’d like to clearly understand whether our implementation is correct and how this plugin is expected to work with our use case.
issues We Are Facing
- No continuous location updates
- Background & Terminated state is speacial feature of our app is not providing location updates
- Without Geofence will it works or not, and will it then how?
[Optional] Plugin Code and/or Config
@pragma('vm:entry-point')
void backgroundGeolocationHeadlessTask(bg.HeadlessEvent event) async {
switch (event.name) {
case bg.Event.LOCATION:
bg.Location location = await bg.BackgroundGeolocation.getCurrentPosition(
samples: 1,
persist: true,
extras: {"event": "terminate", "headless": true},
);
ProviderLocationTrackingHelper().onBGLocation(location);
break;
case bg.Event.TERMINATE:
bg.Location location = await bg.BackgroundGeolocation.getCurrentPosition(
samples: 1,
persist: true,
extras: {"event": "terminate", "headless": true},
);
ProviderLocationTrackingHelper().onBGLocation(location);
break;
case bg.Event.HEARTBEAT:
bg.Location location = await bg.BackgroundGeolocation.getCurrentPosition(
samples: 2,
timeout: 10,
extras: {"event": "heartbeat", "headless": true},
);
ProviderLocationTrackingHelper().onBGLocation(location);
break;
case bg.Event.MOTIONCHANGE:
ProviderLocationTrackingHelper().onMotionChange(event.event);
break;
}
}
@pragma('vm:entry-point')
void backgroundFetchHeadlessTask(HeadlessTask task) async {
String taskId = task.taskId;
if (task.timeout) {
print("[BackgroundFetch] HeadlessTask TIMEOUT: $taskId");
BackgroundFetch.finish(taskId);
return;
}
print("[BackgroundFetch] HeadlessTask: $taskId");
var location = await bg.BackgroundGeolocation.getCurrentPosition(
samples: 2,
extras: {"event": "background-fetch", "headless": true},
);
ProviderLocationTrackingHelper().onBGLocation(location);
BackgroundFetch.finish(taskId);
}
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp(
name: Platform.isAndroid ? AppStrings.quinable : null,
options: DefaultFirebaseOptions.currentPlatform,
);
var content = message.data["message"] ?? "";
var title = message.data["title"] ?? "";
int unreadNotificationCount =
int.tryParse(message.data["unread_notification_count"].toString()) ?? 0;
NotificationHelper().showSimpleNotification(
title: title,
body: content,
payload: message.data.toString(),
);
AppBadgePlus.updateBadge(unreadNotificationCount);
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
bg.BackgroundGeolocation.registerHeadlessTask(
backgroundGeolocationHeadlessTask,
);
await Firebase.initializeApp(
name: Platform.isAndroid ? AppStrings.quinable : null,
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
FlutterError.onError = (errorDetails) {
FirebaseCrashlytics.instance.recordFlutterFatalError(errorDetails);
};
PlatformDispatcher.instance.onError = (error, stack) {
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
return true;
};
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
if (Platform.isAndroid) {
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.dark,
),
);
} else if (Platform.isIOS) {
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarBrightness: Brightness.light,
),
);
}
ShareUtil.setDefaultSharePositionOrigin(Rect.fromLTWH(100, 100, 1, 1));
AppBadgePlus.updateBadge(0);
await Future.wait([
ScreenUtil.ensureScreenSize(),
DeviceInfoManager().initDeviceInfo(),
preference.load(),
]);
runApp(buildProviders(initialRoute: AppRoutes.startupScreen));
}
buildProviders({required String initialRoute}) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(value: AuthProvider.initialize()),
ChangeNotifierProvider.value(value: TabbarProvider()),
ChangeNotifierProvider.value(value: AppProvider()),
ChangeNotifierProvider.value(value: DashboardProvider()),
ChangeNotifierProvider.value(value: TimeClockProvider()),
ChangeNotifierProvider.value(value: ScheduleProvider()),
ChangeNotifierProvider.value(value: TimerProvider()),
ChangeNotifierProvider.value(value: ProfileProvider()),
ChangeNotifierProvider.value(value: DrawerProvider()),
ChangeNotifierProvider.value(value: CountDownProvider()),
ChangeNotifierProvider.value(value: InvitationsProvider()),
ChangeNotifierProvider.value(value: LocationPermissionProvider()),
ChangeNotifierProvider.value(value: CalendarFilterProvider()),
ChangeNotifierProvider.value(value: ShiftSchedulePreferencesProvider()),
ChangeNotifierProvider.value(value: RoutesProvider()),
],
child: MyApp(initialRoute: initialRoute),
);
}
class MyApp extends StatefulWidget {
final String initialRoute;
const MyApp({Key? key, required this.initialRoute}) : super(key: key);
static GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
static GlobalKey<ScaffoldMessengerState> scaffoldMessengerKey =
GlobalKey<ScaffoldMessengerState>();
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
static FirebaseAnalytics analytics = FirebaseAnalytics.instance;
@override
void initState() {
super.initState();
_initProviderTracking();
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
var content = message.data["message"] ?? "";
var title = message.data["title"] ?? "";
int unreadNotificationCount =
int.tryParse(message.data["unread_notification_count"].toString()) ??
0;
NotificationHelper().showSimpleNotification(
title: title,
body: content,
payload: message.data.toString(),
);
AppBadgePlus.updateBadge(unreadNotificationCount);
});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
Navigator.pushNamed(context, AppRoutes.notificationScreen);
});
initializeNotificationOnAppKill();
}
Future _initProviderTracking() async {
bg.BackgroundGeolocation.onLocation(
ProviderLocationTrackingHelper().onBGLocation,
);
bg.BackgroundGeolocation.ready(
bg.Config(
logLevel: bg.Config.LOG_LEVEL_VERBOSE,
desiredAccuracy: bg.Config.DESIRED_ACCURACY_HIGH,
stopOnTerminate: false,
startOnBoot: true,
pausesLocationUpdatesAutomatically: false,
isMoving: true,
useSignificantChangesOnly: true,
enableHeadless: true,
heartbeatInterval: 60,
preventSuspend: true,
showsBackgroundLocationIndicator: true,
distanceFilter: 0.0,
disableElasticity: true,
disableLocationAuthorizationAlert: true,
foregroundService: true,
locationUpdateInterval: 30000,
activityRecognitionInterval: 30,
allowIdenticalLocations: true,
),
);
}
initializeNotificationOnAppKill() async {
await NotificationOnKillApp.stopNotificationOnKillAppService();
NotificationOnKillApp.startNotificationOnKillAppService();
}
void handleMessage(RemoteMessage message) {
Navigator.push(
MyApp.navigatorKey.currentContext!,
CupertinoPageRoute(builder: (context) => NotificationScreen()),
);
}
setupInteractionMessage() async {
RemoteMessage? initialMessage = await FirebaseMessaging.instance
.getInitialMessage();
if (initialMessage != null) {
handleMessage(initialMessage);
}
FirebaseMessaging.onMessageOpenedApp.listen(handleMessage);
}
@override
Widget build(BuildContext context) {
return ScreenUtilInit(
designSize: const Size(392.72727272727275, 803.6363636363636),
minTextAdapt: true,
splitScreenMode: true,
builder: (_, child) {
return Consumer<AppProvider>(
builder: (context, provider, child) {
return ToastificationWrapper(
child: MaterialApp(
title: AppStrings.appName,
debugShowCheckedModeBanner: false,
builder: EasyLoading.init(),
navigatorKey: MyApp.navigatorKey,
scaffoldMessengerKey: MyApp.scaffoldMessengerKey,
theme: ThemeData(
fontFamily: AppFonts.poppins,
scaffoldBackgroundColor: AppColors.white,
splashColor: AppColors.transparent,
useMaterial3: false,
highlightColor: AppColors.transparent,
popupMenuTheme: PopupMenuThemeData(color: AppColors.white),
),
scrollBehavior: const MaterialScrollBehavior().copyWith(
dragDevices: {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,
PointerDeviceKind.stylus,
PointerDeviceKind.unknown,
},
physics: const BouncingScrollPhysics(),
scrollbars: true,
),
initialRoute: widget.initialRoute,
routes: AppRoutes.routes,
onGenerateRoute: (settings) =>
AppRoutes.onGenerateRoute(settings),
navigatorObservers: [
FirebaseAnalyticsObserver(analytics: analytics),
],
),
);
},
);
},
);
}
}
//Tracking helper class
enum TrackingState { NotStarted, BeforeShift, Active, Inactive }
class ProviderLocationTrackingHelper {
static final ProviderLocationTrackingHelper _instance =
ProviderLocationTrackingHelper._internal();
factory ProviderLocationTrackingHelper() {
return _instance;
}
ProviderLocationTrackingHelper._internal();
DateTime? _lastUpdateTime;
double? _lastLatitude;
double? _lastLongitude;
bool _insideOutsideStatus = false;
TrackingState _trackingState = TrackingState.NotStarted;
TrackingState get trackingState => _trackingState;
bool get insideOutsideStatus => _insideOutsideStatus;
WebSocket? _webSocket;
bool _isWebsocketConnected = false;
TimeClockModel? currentTimeClockModel;
MetaDataModel? currentMetaModel;
UserModel? currentUserModel;
void updateTimeClock(TimeClockModel? model) {
currentTimeClockModel = model;
}
void updateMetaData(MetaDataModel? model) {
currentMetaModel = model;
}
void updateCurrentUser(UserModel? model) {
currentUserModel = model;
}
Future<void> startStopTracking({
bool reEvaluateTrackingState = true,
TimeClockModel? newTimeClockModel,
MetaDataModel? newMetaDataModel,
UserModel? newUserModel,
}) async {
if (reEvaluateTrackingState) {
updateTimeClock(newTimeClockModel);
updateMetaData(newMetaDataModel);
updateCurrentUser(newUserModel);
}
if (currentTimeClockModel == null) {
await stopTracking();
return;
}
if (getBidCategory(currentTimeClockModel!.bidCategory.toString()) ==
BidCategory.bid) {
await _processShiftTracking(
currentTimeClockModel!,
currentMetaModel,
reEvaluateTrackingState: reEvaluateTrackingState,
);
} else {
var visit = RouteHelper.getCurrentRunningVisit(
currentTimeClockModel!.genericRouteModel?.visits ?? [],
currentTimeClockModel!.skippedVisitsList ?? [],
);
if (visit != null) {
await _processVisitTracking(
visit,
currentMetaModel,
reEvaluateTrackingState: reEvaluateTrackingState,
);
} else {
await stopTracking();
}
}
}
Future<void> _processShiftTracking(
TimeClockModel timeClockModel,
MetaDataModel? metaDataModel, {
required bool reEvaluateTrackingState,
}) async {
ShiftTimesheetModel? timesheet = timeClockModel.timesheet;
DateTime now = DateTime.now();
DateTime shiftStartTime = _parseShiftStartTime(timeClockModel);
DateTime shiftEndTime = _parseShiftEndTime(timeClockModel);
if (timesheet?.localCheckoutTime != null) {
try {
DateTime co = DateTime.parse(timesheet!.localCheckoutTime!.toString());
if (co.isAfter(shiftEndTime)) shiftEndTime = co;
} catch (_) {}
}
DateTime oneHourAfterShiftEnd = shiftEndTime.add(Duration(hours: 1));
_updateTrackingState(
now,
shiftStartTime,
shiftEndTime,
oneHourAfterShiftEnd,
timesheet?.localCheckinTime?.toIso8601String(),
timesheet?.localCheckoutTime?.toIso8601String(),
);
if (reEvaluateTrackingState) {
await _handleTrackingActions(
metaDataModel,
shiftId: timeClockModel.shift?.id,
);
}
}
Future<void> _handleTrackingActions(
MetaDataModel? metaDataModel, {
int? shiftId,
int? visitId,
}) async {
if (_trackingState == TrackingState.BeforeShift ||
_trackingState == TrackingState.Active) {
await _ensureWebsocketConnected();
await startTracking(shiftId: shiftId, visitId: visitId);
} else {
await stopTracking();
}
}
Future<void> _processVisitTracking(
Visit visit,
MetaDataModel? metaDataModel, {
required bool reEvaluateTrackingState,
}) async {
VisitTimesheetModel? timesheet = visit.timesheetModel;
DateTime utcNow = DateHelper.getUtcCurrectTime();
DateTime visitUtcStartTime = DateTime.parse(visit.utcStartDatetime!);
DateTime visitUtcEndTime = DateTime.parse(visit.utcEndDatetime!);
if (timesheet?.utcClockoutDatetime != null) {
try {
DateTime co = DateTime.parse(timesheet!.utcClockoutDatetime!);
if (co.isAfter(visitUtcEndTime)) visitUtcEndTime = co;
} catch (_) {}
}
DateTime oneHourAfterVisitEnd = visitUtcEndTime.add(Duration(hours: 1));
_updateTrackingState(
utcNow,
visitUtcStartTime,
visitUtcEndTime,
oneHourAfterVisitEnd,
timesheet?.utcClockinDatetime,
timesheet?.utcClockoutDatetime,
);
if (reEvaluateTrackingState) {
await _handleTrackingActions(metaDataModel, visitId: visit.id);
}
}
DateTime _parseShiftStartTime(TimeClockModel timeClockModel) {
DateTime now = DateTime.now();
DateTime localStartTime = DateFormat(
"HH:mm:ss",
).parse(timeClockModel.shift!.localStartTime!);
return DateTime(
now.year,
now.month,
now.day,
localStartTime.hour,
localStartTime.minute,
localStartTime.second,
);
}
DateTime _parseShiftEndTime(TimeClockModel timeClockModel) {
return DateTime.parse(
"${timeClockModel.shift?.localEndDate} ${timeClockModel.shift?.localEndTime}",
);
}
void _updateTrackingState(
DateTime now,
DateTime startTime,
DateTime endTime,
DateTime oneHourAfterEnd,
String? checkinTime,
String? checkoutTime,
) {
final hasCheckedIn = checkinTime != null && checkinTime.isNotEmpty;
if (!hasCheckedIn && now.isBefore(startTime)) {
setTrackingState(TrackingState.BeforeShift);
} else if (now.isBefore(oneHourAfterEnd)) {
setTrackingState(TrackingState.Active);
} else {
setTrackingState(TrackingState.Inactive);
}
}
void setTrackingState(TrackingState newState) {
_trackingState = newState;
}
changeGeofenceStatus({required bool status}) {
_insideOutsideStatus = status;
}
Future<void> _ensureWebsocketConnected() async {
if (_isWebsocketConnected && _webSocket != null) return;
try {
_webSocket = await WebSocket.connect(ApiConfig.wsUrl);
_isWebsocketConnected = true;
} catch (e) {
debugPrint("WebSocket connect error: $e");
_isWebsocketConnected = false;
_webSocket = null;
}
}
Future<void> _sendCurrentLocationOverWebSocket(
int providerUserId,
int companyPrimaryUserId, {
required double latitude,
required double longitude,
int? visitId,
int? shiftId,
}) async {
if (!_isWebsocketConnected || _webSocket == null) return;
final bool isSave = _trackingState == TrackingState.Active;
final payload = {
"action": "changeLocation",
"data": {
"provider_user_id": providerUserId,
"latitude": double.parse(latitude.toStringAsFixed(6)),
"longitude": double.parse(longitude.toStringAsFixed(6)),
"company_primary_user_id": companyPrimaryUserId,
"is_save": false,
if (visitId != null) "visit_id": visitId,
if (shiftId != null) "shift_id": shiftId,
},
};
final jsonStr = jsonEncode(payload);
_webSocket!.add(jsonStr);
}
Future<void> startTracking({int? shiftId, int? visitId}) async {
debugPrint("tracking started ---------- $_trackingState");
if (!(await bg.BackgroundGeolocation.state).enabled) {
await bg.BackgroundGeolocation.start();
}
}
void onBGLocation(bg.Location location) async {
double lat = location.coords.latitude;
double lng = location.coords.longitude;
_lastLatitude = lat;
_lastLongitude = lng;
Loc.LocationData currentLoc = Loc.LocationData.fromMap({
"latitude": lat,
"longitude": lng,
});
debugPrint(
"tracking event: ${DateTime.now()} → ${currentLoc.latitude}, ${currentLoc.longitude}",
);
await updateGeofenceStatus(currentLoc);
}
void onMotionChange(bg.Location location) async {
double lat = location.coords.latitude;
double lng = location.coords.longitude;
Loc.LocationData currentLoc = Loc.LocationData.fromMap({
"latitude": lat,
"longitude": lng,
});
debugPrint(
"_onMotionChange event: ${DateTime.now()} → ${currentLoc.latitude}, ${currentLoc.longitude}",
);
}
void onProviderChange(bg.ProviderChangeEvent providerChangeEvent) async {
debugPrint(
"_onProviderChange event: ${DateTime.now()} → ${providerChangeEvent.network}, ${providerChangeEvent.gps}",
);
}
Future<bool> shouldProcessLocation(Loc.LocationData currentLocation) async {
int interval = getIntervalValue();
if (interval == 0) return false;
return _lastUpdateTime == null ||
DateTime.now().difference(_lastUpdateTime!).inSeconds >= interval;
}
Future<double> calculateDistance(
Loc.LocationData? last,
Loc.LocationData current,
double distanceLimit,
) async {
if (last == null || last.latitude == null || last.longitude == null)
return Future.value(distanceLimit);
return await LocationManager().calculateDistance(
last.latitude!,
last.longitude!,
currentLoc: current,
);
}
Future<void> updateGeofenceStatus(Loc.LocationData currentLocation) async {
if (currentTimeClockModel == null) return;
if (getBidCategory(currentTimeClockModel!.bidCategory.toString()) ==
BidCategory.bid) {
Facility? facility = currentTimeClockModel?.shift?.facility;
int? shiftId = currentTimeClockModel?.shiftId;
var facilityGeoFenceRadiusInFeet = facility?.geoFenceRadius;
await LocationManager().locationJudgeDistance(
facility?.latitude,
facility?.longitude,
(locationData, distance, inside) async {
if (inside != _insideOutsideStatus) {
_insideOutsideStatus = inside;
if (!inside) {
updateLastInsideDateTime(DateTime.now());
await saveLastInsideTime(
shiftId: shiftId,
dateTime: DateTime.now(),
);
}
}
bool check = await shouldProcessLocation(currentLocation);
debugPrint("check proceed: ${check}, current state: $trackingState");
if (!check) return;
_lastUpdateTime = DateTime.now();
_lastLatitude = currentLocation.latitude;
_lastLongitude = currentLocation.longitude;
_sendCurrentLocationOverWebSocket(
shiftId: currentTimeClockModel?.shiftId,
currentUserModel?.id ?? 0,
currentTimeClockModel?.shift?.companyPrimaryUserId ?? 0,
latitude: currentLocation.latitude ?? _lastLatitude ?? 0,
longitude: currentLocation.longitude ?? _lastLongitude ?? 0,
);
startStopTracking(reEvaluateTrackingState: false);
},
currentLoc: currentLocation,
geoFenceRadiusInFeet: facilityGeoFenceRadiusInFeet,
);
} else {
var visit = RouteHelper.getCurrentRunningVisit(
currentTimeClockModel?.genericRouteModel?.visits ?? [],
currentTimeClockModel?.skippedVisitsList ?? [],
);
var routeGeoFenceRadiusInFeet =
currentTimeClockModel?.genericRouteModel?.agency?.geoFenceRadius;
if (visit != null &&
visit.patientAddress?.latitude != null &&
visit.patientAddress?.longitude != null) {
await LocationManager().locationJudgeDistance(
double.parse(visit.patientAddress!.latitude!),
double.parse(visit.patientAddress!.longitude!),
(locationData, distance, inside) async {
if (inside != _insideOutsideStatus) {
_insideOutsideStatus = inside;
if (!inside) {
updateLastInsideDateTime(DateTime.now());
await saveLastInsideTime(
visitId: visit.id,
dateTime: DateTime.now(),
);
}
}
bool check = await shouldProcessLocation(currentLocation);
debugPrint(
"check proceed: ${check}, current state: $trackingState",
);
if (!check) return;
_lastUpdateTime = DateTime.now();
_lastLatitude = currentLocation.latitude;
_lastLongitude = currentLocation.longitude;
_sendCurrentLocationOverWebSocket(
visitId: visit.id,
latitude: currentLocation.latitude ?? _lastLatitude ?? 0,
longitude: currentLocation.longitude ?? _lastLongitude ?? 0,
currentUserModel?.id ?? 0,
currentTimeClockModel?.genericRouteModel?.companyPrimaryUserId ??
0,
);
startStopTracking(reEvaluateTrackingState: false);
},
currentLoc: currentLocation,
geoFenceRadiusInFeet: routeGeoFenceRadiusInFeet,
);
}
}
}
Future<void> uploadLocation(Loc.LocationData locationData) async {
if (locationData.latitude == null || locationData.longitude == null) return;
Response response = await Dio().post(
"${ApiConfig.apiUrl + ApiConfig.appendLiveLocationApi}",
data: jsonEncode({
'latitude': locationData.latitude,
'longitude': locationData.longitude,
'local_datetime': DateTime.now().toIso8601String(),
if (currentTimeClockModel?.shiftId != null)
'shift_id': currentTimeClockModel?.shiftId,
}),
options: Options(
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ${currentUserModel?.accessToken ?? ""}',
},
),
);
print("Location uploaded: ${response.data}");
}
DateTime? _lastInsideDateTime;
DateTime? get lastInsideDateTime => _lastInsideDateTime;
updateLastInsideDateTime(DateTime? dateTime) {
_lastInsideDateTime = dateTime;
}
Future<void> saveLastInsideTime({
int? visitId,
int? shiftId,
required DateTime dateTime,
}) async {
final prefs = Preference();
await prefs.load();
final key = visitId != null
? "${PreferenceKeys.lastInsideTimeVisit}$visitId"
: "${PreferenceKeys.lastInsideTimeVisit}$shiftId";
await prefs.setString(key, dateTime.toIso8601String());
}
Future<DateTime?> getLastInsideTime({int? visitId, int? shiftId}) async {
final prefs = Preference();
await prefs.load();
final key = visitId != null
? "${PreferenceKeys.lastInsideTimeVisit}$visitId"
: "${PreferenceKeys.lastInsideTimeShift}$shiftId";
final storedValue = await prefs.getData(key);
if (storedValue == null || storedValue.isEmpty) {
return null;
}
return DateTime.tryParse(storedValue);
}
Future<void> clearLastInsideTime() async {
final prefs = Preference();
await prefs.load();
final shiftKey = PreferenceKeys.lastInsideTimeShift;
final visitKey = PreferenceKeys.lastInsideTimeVisit;
prefs.removeString(shiftKey);
prefs.removeString(visitKey);
}
Future<void> fetchAndUpdateLastInsideTime({
int? visitId,
int? timesheetId,
}) async {
var lastInsideTimeStr = await getLastInsideTime(
visitId: visitId,
shiftId: timesheetId,
);
DateTime? lastInsideTime = DateTime.tryParse(lastInsideTimeStr.toString());
if (lastInsideTime != null) {
updateLastInsideDateTime(lastInsideTime);
}
}
int getIntervalValue() {
if (_trackingState == TrackingState.BeforeShift) {
return currentMetaModel?.intervalBeforeShiftOrVisit ?? 0;
} else if (_trackingState == TrackingState.Active) {
return currentMetaModel?.intervalDuringShiftOrVisit ?? 120;
}
return 0;
}
Future<void> stopTracking() async {
debugPrint("location stopped");
_trackingState = TrackingState.NotStarted;
await bg.BackgroundGeolocation.stop();
try {
await _webSocket?.close();
} catch (e) {
debugPrint("Error closing websocket: $e");
} finally {
_webSocket = null;
_isWebsocketConnected = false;
}
await clearLastInsideTime();
Loc.Location().getLocation().then((value) {
updateGeofenceStatus(value);
});
}
}[Optional] Relevant log output
Metadata
Metadata
Assignees
Labels
No labels