i am implementing a foreground service so that when there is no internet , the data that i directly send to my server should be reside in hive sequentially and when internet comes, the foreground service get them one by one and then send them to the server.
Issue: The service works most of the time but some times it stuck and dont send data to the server - i don't know if its a deadlock of proceses or what but i need to force start the app from android studio, so that this service start again . if i dont do that the screen freeeze if i close and open the app from my phone .the splash screen don't come even.
Is there anything i am doing wrong in my code..
main:
@pragma('vm:entry-point')
void startCallback() {
FlutterForegroundTask.setTaskHandler(UploadTaskHandler());
}
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
// 1️⃣ STOP ZOMBIE SERVICE FIRST (CRITICAL)
// We do this before opening Hive to ensure the database file lock is released.
final isRunning = await FlutterForegroundTask.isRunningService;
if (isRunning) {
print("⚠️ Zombie service detected. Stopping before Hive init...");
await FlutterForegroundTask.stopService();
// Give the Android OS half a second to fully close the background process
await Future.delayed(const Duration(milliseconds: 500));
}
// 2️⃣ Initialize Hive SECOND
// Now that the service is stopped, Hive is guaranteed to open without hanging.
await Hive.initFlutter();
await HiveStorage.init();
// 3️⃣ Init communication port
FlutterForegroundTask.initCommunicationPort();
// 4️⃣ Init Foreground Service config
FlutterForegroundTask.init(
androidNotificationOptions: AndroidNotificationOptions(
channelId: 'consultation_sync_channel',
channelName: 'Consultation Sync',
channelDescription: 'Handles background upload of medical records',
channelImportance: NotificationChannelImportance.DEFAULT,
priority: NotificationPriority.DEFAULT,
),
iosNotificationOptions: const IOSNotificationOptions(showNotification: true),
foregroundTaskOptions: ForegroundTaskOptions(
eventAction: ForegroundTaskEventAction.repeat(5000),
autoRunOnBoot: true,
allowWakeLock: true,
allowWifiLock: true,
),
);
// 5️⃣ Request notification permissions (Essential for Android 13+)
final notificationPermission = await FlutterForegroundTask.checkNotificationPermission();
if (notificationPermission != NotificationPermission.granted) {
await FlutterForegroundTask.requestNotificationPermission();
}
// 6️⃣ Initialize Coordinator LAST
// This will check Hive and restart the service if there is pending work.
BackgroundQueueCoordinator.init();
runApp(const MyApp());
}
background queu coordinator:
class BackgroundQueueCoordinator {
static bool _isProcessing = false;
static Timer? _handshakeTimer;
static StreamSubscription>? _connectivitySubscription;
static Timer? _debounceTimer;
/// Initialize background queue coordinator
static void init() {
// Add callback for receiving messages from background service
FlutterForegroundTask.addTaskDataCallback(_onReceiveTaskData);
// Listen to connectivity changes to retry queue processing
_connectivitySubscription =
Connectivity().onConnectivityChanged.listen((results) {
if (results.any((r) => r != ConnectivityResult.none)) {
_debounceTimer?.cancel();
_debounceTimer = Timer(const Duration(seconds: 1), () {
print("🌐 Internet Stable: Re-triggering sync...");
checkQueueAndStartService();
});
}
});
// Initial delayed check after app startup
Future.delayed(const Duration(seconds: 2), () {
checkQueueAndStartService();
});
}
/// Main entry to check queue and start/resume service
static Future checkQueueAndStartService() async {
try {
// 1️⃣ Ensure Hive is already open
if (HiveStorage.getOutBoxCount() == 0) {
// If queue is empty but service is somehow running, stop safely
if (await FlutterForegroundTask.isRunningService) {
await FlutterForegroundTask.stopService();
}
return;
}
final isRunning = await FlutterForegroundTask.isRunningService;
if (!isRunning) {
print("🚀 Cold Start: Service not running.");
await _startServiceInternal();
} else {
print("🔄 Handshake: Service is alive, checking background isolate...");
// Reset lock to allow handshake to work even if stuck
_isProcessing = false;
// 2️⃣ Watchdog timer: force restart if service is unresponsive
_handshakeTimer?.cancel();
_handshakeTimer = Timer(const Duration(seconds: 3), () async {
print("⚠️ DEADLOCK / ZOMBIE SERVICE DETECTED. Force Killing...");
await FlutterForegroundTask.stopService(); // release Hive lock
await Future.delayed(const Duration(seconds: 1)); // OS cleanup
await _startServiceInternal(); // fresh start
});
// Send handshake ping to background isolate
FlutterForegroundTask.sendDataToTask({'action': 'check_status'});
}
} catch (e) {
print("❌ Coordinator Exception: $e");
}
}
/// Helper: start the service cleanly
static Future _startServiceInternal() async {
await FlutterForegroundTask.startService(
notificationTitle: 'NourDoc Sync',
notificationText: 'Preparing consultations...',
callback: startCallback,
);
}
/// Receive messages from background isolate
static void _onReceiveTaskData(Object data) async {
if (data is! Map) return;
final status = data['status'];
// ✅ Cancel watchdog timer when service responds
_handshakeTimer?.cancel();
if (status == 'ready_for_task' || status == 'idle') {
_isProcessing = false;
_sendNextTask();
} else if (status == 'success') {
final dynamic hiveKey = data['hiveKey'];
await _handleSuccess(hiveKey);
} else if (status == 'error') {
_isProcessing = false;
}
}
/// Dispose listeners
static void dispose() {
_connectivitySubscription?.cancel();
_debounceTimer?.cancel();
}
/// Send next consultation to foreground service
static Future _sendNextTask() async {
if (_isProcessing) return;
// Respect server batch limit
if (HiveStorage.getServerBatchCount() >= HiveStorage.batchLimit) {
print("⛔ Batch limit reached.");
_isProcessing = false;
return;
}
final keys = HiveStorage.getOutboxKeys();
if (keys.isEmpty) {
_isProcessing = false;
if (await FlutterForegroundTask.isRunningService) {
await FlutterForegroundTask.stopService();
print("🛑 Queue empty. Service stopped.");
}
return;
}
final firstKey = keys.first;
final consultation = HiveStorage.getConsultationByKey(firstKey);
if (consultation == null) {
_isProcessing = false;
return;
}
_isProcessing = true;
final taskMap = consultation.toJson(firstKey);
FlutterForegroundTask.sendDataToTask(taskMap);
}
/// Handle successful sync
static Future _handleSuccess(dynamic hiveKey) async {
try {
// Remove from Hive first
await HiveStorage.removeFromOutbox(hiveKey);
// Increment batch counter
await HiveStorage.incrementServerBatch();
_isProcessing = false;
// Continue with next task
_sendNextTask();
} catch (e) {
print("❌ Error handling success: $e");
_isProcessing = false;
}
}
upload service:
class UploadTaskHandler extends TaskHandler {
bool _isBusy = false;
@override
Future onStart(DateTime timestamp, TaskStarter starter) async {
print("🚀 Background Service: Isolate started. Signaling Main App...");
FlutterForegroundTask.sendDataToMain({'status': 'ready_for_task'});
}
@override
void onReceiveData(Object data) async {
if (data is! Map) return;
// 1️⃣ Always answer ping immediately (prevents "Preparing" stuck)
if (data['action'] == 'check_status') {
FlutterForegroundTask.sendDataToMain({
'status': _isBusy ? 'busy_working' : 'idle',
});
return;
}
// 2️⃣ Prevent double-processing
if (_isBusy) return;
try {
_isBusy = true;
// 3️⃣ Safety check: ensure task data has required keys
if (!data.containsKey('patientName') || !data.containsKey('audioPath')) {
print("⚠️ Received malformed task data, skipping...");
_isBusy = false;
return;
}
final task = PendingConsultation.fromJson(data);
final dynamic hiveKey = data['hiveKey'];
// 4️⃣ Immediately update notification
FlutterForegroundTask.updateService(
notificationTitle: 'Syncing Consultation',
notificationText: 'Preparing: ${task.patientName}',
);
print("📦 Background Service: Received task for ${task.patientName} (Key: $hiveKey)");
await _processUpload(task, hiveKey);
} catch (e) {
print("❌ Background parsing/upload error: $e");
FlutterForegroundTask.sendDataToMain({'status': 'error', 'error': e.toString()});
} finally {
_isBusy = false;
// ✅ Always signal ready again for queue continuation
FlutterForegroundTask.sendDataToMain({'status': 'ready_for_task'});
}
}
@override
void onRepeatEvent(DateTime timestamp) {}
Future _processUpload(PendingConsultation data, dynamic hiveKey) async {
double lastSentProgress = -0.02;
try {
// Validate file exists
final file = File(data.audioPath);
if (!await file.exists()) {
throw Exception("Audio file not found at ${data.audioPath}");
}
final request = MultipartRequestWithProgress(
'POST',
Uri.parse(AppUrls.interference),
onProgress: (bytes, total) {
if (total <= 0) return;
double progress = bytes / total;
if ((progress - lastSentProgress) >= 0.02 || progress >= 0.99) {
lastSentProgress = progress;
FlutterForegroundTask.sendDataToMain({
'status': 'progress',
'value': progress,
});
FlutterForegroundTask.updateService(
notificationTitle: 'Syncing Consultation',
notificationText: '${(progress * 100).toInt()}% uploaded: ${data.patientName}',
);
}
},
);
request.files.add(await http.MultipartFile.fromPath('file', data.audioPath));
request.fields.addAll({
'name_patient': data.patientName,
'gender': data.gender,
'Age': data.age,
'dob': data.dob,
'visit_type': data.visitType,
'start_time': data.startTime.toIso8601String(),
'end_time': data.endTime.toIso8601String(),
'temp': data.temp,
'bp': data.bp,
'sugar': data.sugar,
'pulse': data.pulse,
'resp_rate': data.resp,
});
if (data.patientId.isNotEmpty) {
request.fields['patient_id'] = data.patientId;
}
request.headers['Authorization'] = 'Bearer ${data.token}';
final response = await request.send().timeout(const Duration(minutes: 5));
if (response.statusCode == 200) {
print("🏁 Background Service: Success for ${data.patientName}");
FlutterForegroundTask.sendDataToMain({
'status': 'success',
'hiveKey': hiveKey,
'patient': data.patientName,
});
} else {
print("❌ Server error: ${response.statusCode}");
FlutterForegroundTask.sendDataToMain({
'status': 'error',
'error': 'Server error: ${response.statusCode}',
});
}
} on TimeoutException {
print("⏳ Upload timed out");
FlutterForegroundTask.sendDataToMain({'status': 'error', 'error': 'Upload timeout'});
} catch (e) {
print("❌ Upload exception: $e");
FlutterForegroundTask.sendDataToMain({'status': 'error', 'error': e.toString()});
}
}
@override
Future onDestroy(DateTime timestamp, bool isTimeout) async {
print("👋 Background Service: Destroyed");
}
}
/// Helper for Upload Progress
class MultipartRequestWithProgress extends http.MultipartRequest {
final Function(int bytes, int totalBytes) onProgress;
MultipartRequestWithProgress(
String method,
Uri url, {
required this.onProgress,
}) : super(method, url);
@override
http.ByteStream finalize() {
final byteStream = super.finalize();
final totalBytes = contentLength;
int bytesSent = 0;
return http.ByteStream(
byteStream.map((data) {
bytesSent += data.length;
onProgress(bytesSent, totalBytes);
return data;
}),
);
}
}