Required Reading
Plugin Version
flutter_background_geolocation: "4.16.9", background_fetch: "1.5.0"
Mobile operating-system(s)
Device Manufacturer(s) and Model(s)
Nokia T20 Tab - TA-1397
Device operating-systems(s)
Android 13
What do you require assistance about?
I am using flutter_background_geolocation for continuous tracking and I have a business requirement:
At ~11:30 PM, I need to:
Call a “End Day” API
Stop background tracking (BackgroundGeolocation.stop())
Problem:
I am unable to reliably execute this logic at the scheduled time.
However:
onHeartbeat is not firing consistently (sometimes not at all)
Timers are not reliable in background (expected due to Android limitations)
Observation:
On some devices (e.g. Xiaomi), location updates continue overnight
But onHeartbeat does not fire, so my logic is never executed
On other devices (Nokia/Motorola), background tracking itself stops after some time
So currently I don’t have a reliable trigger to:
run code at a specific time
and then stop the service
Questions:
- Is there any reliable way within this plugin to execute a task at a specific time (e.g. 11:30 PM)?
- What is the recommended approach to:
run a one-time task at night
and then stop tracking reliably?
Goal:
I need a reliable way to ensure:
API is called once at night
tracking is stopped afterward
even under Android background restrictions.
Environment:
Flutter version: [3.41.6]
Plugin version: flutter_background_geolocation: "4.16.9", background_fetch: "1.5.0"
Android versions tested:11, 12, 13
Devices: Xiaomi, Nokia, Motorola, Samsung
[Optional] Plugin Code and/or Config
**Current Setup:**
// Receive events from BackgroundGeolocation in Headless state.ksdnsknd
@pragma('vm:entry-point')
void backgroundGeolocationHeadlessTask(bg.HeadlessEvent headlessEvent) async {
switch (headlessEvent.name) {
case bg.Event.BOOT:
final now = DateTime.now();
if (now.hour >= 23 && now.minute >= 30) {
// Device rebooted at night, don't let services start
await bg.BackgroundGeolocation.stop();
await bf.BackgroundFetch.stop();
}
bg.State state = await bg.BackgroundGeolocation.state;
customPrint('📬 didDeviceReboot: ${state.didDeviceReboot}');
break;
case bg.Event.TERMINATE:
try {
bg.Location location = await bg.BackgroundGeolocation.getCurrentPosition(samples: 1, extras: {'event': 'terminate', 'headless': true});
customPrint('[getCurrentPosition] Headless: $location');
} catch (e) {
return;
}
break;
// TODO: RECHECK
case bg.Event.HEARTBEAT:
final now = DateTime.now();
if (now.hour == 23 && now.minute >= 30) {
// await forceStopAllServices();
// try {
// onSyncStart.syncAllDataFinal();
// } catch (e) {}
await finishYourDayInBg();
await forceStopAllServices();
}
break;
case bg.Event.LOCATION:
bg.Location location = headlessEvent.event;
customPrint(location);
break;
case bg.Event.MOTIONCHANGE:
bg.Location location = headlessEvent.event;
customPrint(location);
break;
case bg.Event.GEOFENCE:
bg.GeofenceEvent geofenceEvent = headlessEvent.event;
customPrint(geofenceEvent);
break;
case bg.Event.GEOFENCESCHANGE:
bg.GeofencesChangeEvent event = headlessEvent.event;
customPrint(event);
break;
case bg.Event.SCHEDULE:
bg.State state = headlessEvent.event;
customPrint(state);
break;
case bg.Event.ACTIVITYCHANGE:
bg.ActivityChangeEvent event = headlessEvent.event;
customPrint(event);
break;
case bg.Event.HTTP:
bg.HttpEvent event = headlessEvent.event;
customPrint(event);
break;
case bg.Event.POWERSAVECHANGE:
bool enabled = headlessEvent.event;
customPrint(enabled);
break;
case bg.Event.CONNECTIVITYCHANGE:
bg.ConnectivityChangeEvent event = headlessEvent.event;
customPrint(event);
break;
case bg.Event.ENABLEDCHANGE:
bool enabled = headlessEvent.event;
customPrint(enabled);
break;
// TODO: RECHECK
case bg.Event.AUTHORIZATION:
bg.AuthorizationEvent event = headlessEvent.event;
customPrint(event);
bg.BackgroundGeolocation.setConfig(bg.Config(url: '${baseUrl2}AddbackgroundLocation', headers: {'Authorization': "Bearer ${prefs!.getString('token')}"}));
break;
}
}
int _delayUntil(TimeOfDay time) {
final now = DateTime.now();
DateTime target = DateTime(now.year, now.month, now.day, time.hour, time.minute);
if (target.isBefore(now)) {
target = target.add(const Duration(days: 1));
}
return target.difference(now).inMilliseconds;
}
Future<void> scheduleDailyStop() async {
await bf.BackgroundFetch.scheduleTask(
bf.TaskConfig(
taskId: "night_stop_tracking",
delay: _delayUntil(const TimeOfDay(hour: 23, minute: 30)),
periodic: false,
stopOnTerminate: false,
enableHeadless: true,
startOnBoot: true,
forceAlarmManager: true,
),
);
}
void onFetch(String taskId) async {
if (taskId == "night_stop_tracking") {
await bg.BackgroundGeolocation.stop();
await forceStopAllServices();
// schedule tomorrow's stop
await scheduleDailyStop();
}
bf.BackgroundFetch.finish(taskId);
}
/// Receive events from BackgroundFetch in Headless state.
@pragma('vm:entry-point')
void backgroundFetchHeadlessTask(bf.HeadlessTask task) async {
String taskId = task.taskId;
if (task.timeout) {
bf.BackgroundFetch.finish(taskId);
return;
}
if (taskId == "night_stop_tracking") {
await finishYourDayInBg(); // ← now does exactly what you want
await scheduleDailyStop(); // for tomorrow
bf.BackgroundFetch.finish(taskId);
return;
}
// Normal periodic task
log('Background Fetch: normal connectivity trigger');
await storeConnectivityInfoLocally();
bf.BackgroundFetch.finish(taskId);
}
API REQUEST - TO END DAY [ I executed this manually, it's working - no issue here ]
Future<void> finishYourDayInBg() async {
final prefs = await SharedPreferences.getInstance();
await prefs.reload(); // ensure latest values
// ←←← IDEMPOTENCY GUARD (prevents double execution)
if (prefs.getString('dayType') == Constants.finishDay) {
customPrint('🌙 Night finish already done → skipping');
await forceStopAllServices();
return;
}
// final bool isLocService = await Permission.locationWhenInUse.serviceStatus.isEnabled;
bool internetAvailable = false;
try {
internetAvailable = await checkIfInternetWorking();
} catch (_) {}
customPrint('🌙 NIGHT FINISH TRIGGERED | Internet: $internetAvailable | Loc: ');
// ====================== DATA FIRST (your requirement) ======================
if (internetAvailable) {
customPrint('📤 Internet ON → Full Isar + connectivity sync');
// 2. Final connectivity log + sync
try {
await storeConnectivityInfoLocally();
customPrint('✅ Connectivity synced');
} catch (e) {
customPrint('⚠️ Connectivity sync failed: $e');
}
// 1. Most important: Isar sync
try {
await onSyncStart.syncAllDataFinal();
customPrint('✅ Isar sync completed');
} catch (e) {
customPrint('⚠️ Isar sync failed (continuing): $e');
}
// 3. Finishday API
// if (isLocService) {
try {
final value = await bg.BackgroundGeolocation.getCurrentPosition(samples: 1, timeout: 30);
String finalAddress = '-';
try {
final placemarks = await placemarkFromCoordinates(value.coords.latitude, value.coords.longitude);
finalAddress =
'${placemarks[0].name}, ${placemarks[0].subLocality}, '
'${placemarks[0].thoroughfare}, ${placemarks[0].locality}, '
'${placemarks[0].postalCode}, ${placemarks[0].administrativeArea}, '
'${placemarks[0].country}';
} catch (_) {}
final pp = {
'loginid': prefs.getString('loginID') ?? '',
'latitude': value.coords.latitude,
'longitude': value.coords.longitude,
'location': finalAddress,
'batterylevel': (value.battery.level * 100).toInt(),
'endtime': DateTime.now().toIso8601String(),
'endgpsenbled': true,
'endlocationaccuracy': value.coords.accuracy.toPrecision(2),
'endmocklocationenabled': value.mock,
};
final temp = await fRetryOptions.retry(
() => http.post(
Uri.parse('${baseUrl}Finishday'),
body: jsonEncode(pp),
headers: {'Accept': 'application/json', 'Content-Type': 'application/json', 'Authorization': 'Bearer ${prefs.getString('token')}'},
),
retryIf: (e) => e is SocketException || e is TimeoutException || e is HttpException || e is http.ClientException || e is FormatException,
);
customPrint('✅ Finishday API success: ${temp.statusCode}');
} catch (e) {
customPrint('⚠️ Finishday API failed (data already synced): $e');
}
// }
} else {
customPrint('📴 No internet → Skipping sync & API, only local cleanup');
}
// ====================== ALWAYS CLEAN UP ======================
try {
await prefs.setString('dayType', Constants.finishDay);
await prefs.setBool('isDayFinished', true);
await prefs.remove('beatID');
await prefs.remove('distributorID');
await forceStopAllServices();
customPrint('🛑 All background services stopped (battery saved)');
} catch (e) {
customPrint('Cleanup error: $e');
}
}
Future<void> initPlatformState() async {
// Configure BackgroundFetch.
await bf.BackgroundFetch.configure(
bf.BackgroundFetchConfig(
forceAlarmManager: true,
minimumFetchInterval: 15,
stopOnTerminate: false,
enableHeadless: true,
startOnBoot: true,
requiresBatteryNotLow: false,
requiresCharging: false,
requiresStorageNotLow: false,
requiresDeviceIdle: false,
requiredNetworkType: bf.NetworkType.NONE,
),
(String taskId) async {
bg.BackgroundGeolocation.getCurrentPosition(extras: {'event': 'terminate', 'headless': true});
customPrint('[BackgroundFetch] Event received $taskId');
bf.BackgroundFetch.finish(taskId);
},
(String taskId) async {
bf.BackgroundFetch.finish('taskId');
},
);
if (!mounted) return;
}
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);;
bg.BackgroundGeolocation.onConnectivityChange((bg.ConnectivityChangeEvent event) async {
if (event.connected == true) {
await onSyncStart.syncAllDataFinal();
}
});
// 2. Configure the plugin
if (prefs!.getString('dayType') != Constants.noStartDayRequired) {
bg.BackgroundGeolocation.ready(
bg.Config(
enableHeadless: true,
heartbeatInterval: 60,
locationAuthorizationRequest: 'Always',
url: 'https://example.com/AddbackgroundLocation',
headers: {'Authorization': "Bearer ${prefs!.getString('token')}"},
desiredAccuracy: bg.Config.DESIRED_ACCURACY_HIGH,
distanceFilter: 50,
// scheduleUseAlarmManager: true,
// schedule: ['1-7 21:19-21:20'],
stopOnTerminate: false,
disableElasticity: false,
elasticityMultiplier: 1,
desiredOdometerAccuracy: 100,
// stationaryRadius: ,
stationaryRadius: 25,
maxDaysToPersist: 3,
locationUpdateInterval: 8000,
stopTimeout: 5,
disableMotionActivityUpdates: false,
// useSignificantChangesOnly: true,
disableStopDetection: false,
motionTriggerDelay: 0,
autoSync: true,
disableAutoSyncOnCellular: false,
persistMode: 1,
preventSuspend: true,
notificationPriority: 1,
startOnBoot: true,
debug: false,
logLevel: bg.Config.LOG_LEVEL_VERBOSE,
backgroundPermissionRationale: bg.PermissionRationale(
title: "Allow {applicationName} to access this device's location even when the app is closed or not in use.",
message: 'This app collects location data to enable recording your trips to work and calculate distance-travelled.',
positiveAction: 'Change to "{backgroundPermissionOptionLabel}"',
negativeAction: 'Cancel',
),
notificationLargeIcon: 'drawable/ic_stat_Expamplelogo',
notificationSmallIcon: 'drawable/ic_stat_Expamplelogo',
foregroundService: true,
notification: bg.Notification(
title: 'Day Started - Expample',
text: 'Example started',
priority: bg.Config.NOTIFICATION_PRIORITY_HIGH,
sticky: true,
smallIcon: 'drawable/ic_stat_Expamplelogo', // <-- defaults to app icon
largeIcon: 'drawable/ic_stat_Expamplelogo',
channelId: 'my_channel_id',
actions: [],
),
),
);
}
initPlatformState();
}
[Optional] Relevant log output
Required Reading
Plugin Version
flutter_background_geolocation: "4.16.9", background_fetch: "1.5.0"
Mobile operating-system(s)
Device Manufacturer(s) and Model(s)
Nokia T20 Tab - TA-1397
Device operating-systems(s)
Android 13
What do you require assistance about?
I am using flutter_background_geolocation for continuous tracking and I have a business requirement:
At ~11:30 PM, I need to:
Call a “End Day” API
Stop background tracking (BackgroundGeolocation.stop())
Problem:
I am unable to reliably execute this logic at the scheduled time.
However:
onHeartbeat is not firing consistently (sometimes not at all)
Timers are not reliable in background (expected due to Android limitations)
Observation:
On some devices (e.g. Xiaomi), location updates continue overnight
But onHeartbeat does not fire, so my logic is never executed
On other devices (Nokia/Motorola), background tracking itself stops after some time
So currently I don’t have a reliable trigger to:
run code at a specific time
and then stop the service
Questions:
run a one-time task at night
and then stop tracking reliably?
Goal:
I need a reliable way to ensure:
API is called once at night
tracking is stopped afterward
even under Android background restrictions.
Environment:
Flutter version: [3.41.6]
Plugin version: flutter_background_geolocation: "4.16.9", background_fetch: "1.5.0"
Android versions tested:11, 12, 13
Devices: Xiaomi, Nokia, Motorola, Samsung
[Optional] Plugin Code and/or Config
[Optional] Relevant log output