implementing a foreground service with hive -> sequentially sending data to the server
04:52 13 Feb 2026

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;
      }),
    );
  }
}
flutter dart hive notifications foreground