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

[Help]: flutter_blue_plus with flutter_foreground_task combination failes, when app get backgrounded #924

Closed
1 task done
explodus opened this issue Jul 3, 2024 · 10 comments
Labels
help Questions, help, observations, or possible bugs

Comments

@explodus
Copy link

explodus commented Jul 3, 2024

Requirements

  • I've looked at the README 'Common Problems' section

Have you checked this problem on the example app?

No

FlutterBluePlus Version

1.32.8

Flutter Version

3.22.0

What OS?

Android

OS Version

Android 14

Bluetooth Module

unspecific

What is your problem?

I am trying to combine flutter_foreground_tasks with BLE scanning, whether the app is running visibly or in the background, everything works perfectly. But as soon as I turn off the screen and the log message goes through. I/FA (24762): Application backgrounded at: timestamp_millis: ... nothing works anymore. No matter how I try it. With continuous scanning, or every 6 seconds in onRepeatEvent (so that startScan is not called more than 5 times in 30s), it no longer delivers any results.

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"/>
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
<uses-permission android:name="android.permission.USE_EXACT_ALARM"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

I have turned on all possible permissions

<service android:name="com.pravera.flutter_foreground_task.service.ForegroundService"
android:foregroundServiceType="dataSync|remoteMessaging|connectedDevice" android:exported="false" android:stopWithTask="false" />

and also tried several combinations in ForegroundService.

Does anyone else have an idea what else I could try to wake the phone up again when I find a very specific BLE device?

Logs

D/[FBP-Android](29081): [FBP] onMethodCall: startScan
D/BluetoothAdapter(29081): isLeEnabled(): ON
D/BluetoothLeScanner(29081): onScannerRegistered() - status=0 scannerId=19 mScannerId=0
[log] startScan
[log] scanResults.listen(0)
@explodus explodus added the help Questions, help, observations, or possible bugs label Jul 3, 2024
@chipweinberger
Copy link
Owner

never tried this before

@explodus
Copy link
Author

explodus commented Jul 4, 2024

did you think i could use something like this?

https://github.com/flutter/samples/blob/main/background_isolate_channels/lib/simple_database.dart

to get the plugin/startScan useable from a background isolate?
or is the better way to get a connection to the ble device? normally i don't need a connection, every status information is already in the advertising packet, so a simple scan and with service uid is sufficient to get all informations and everything is connectionless.

@chipweinberger
Copy link
Owner

Im unfamiliar. good luck & report back.

@chipweinberger
Copy link
Owner

@explodus
Copy link
Author

explodus commented Jul 4, 2024

i tried this also

@pragma('vm:entry-point') // Mandatory if the App is obfuscated or using Flutter 3.1+
void workManagerCallback() {
  rootIsolateToken = RootIsolateToken.instance;
  Workmanager().executeTask((task, inputData) async {
    print(task);
    BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken!);
    if (task == 'backgroundedScan') {
      UniversalBle.onScanResult = (bleDevice) {
        print('UniversalBle.onScanResult: ${bleDevice.deviceId}');
        FlutterForegroundTask.wakeUpScreen();
        // e.g. Use BleDevice ID to connect
      };

      // Or optionally add a scan filter
      UniversalBle.startScan();
      print('UniversalBle.startScan()');

      for (var i = 0; i < 90; i++) {
        print('UniversalBle.Future.delayed');
        await Future.delayed(const Duration(seconds: 10));
      }

      // Stop scanning
      UniversalBle.stopScan();
      print('UniversalBle.stopScan()');
    }
    return true;
  });
}

also with different ble libs, it doesn't scan.

The problem is that I see the log print from FlutterForegroundTask.onRepeatEvent at intervals of 6s. So it would work if you could scan every 6s, but that's exactly where my problem lies. As soon as you start the scan, nothing happens, scanResult is empty. But I don't see why the scan is prevented. As I said, I've already set up and tried lowPower and similar variants.

@chipweinberger
Copy link
Owner

I'd try making it work with a native app next

so you can understand how it is supposed to work

@chipweinberger
Copy link
Owner

chipweinberger commented Jul 4, 2024

some docs


Scanning BLE in the background on mobile platforms can be challenging due to the operating system's restrictions on background activities to conserve battery life. However, both Android and iOS provide ways to perform BLE scans in the background with some limitations.

Android

On Android, you can use the JobScheduler or WorkManager APIs to schedule background tasks, including BLE scanning. Here's a simplified example using WorkManager:

  1. Add permissions and dependencies:

    Make sure you have the necessary permissions in your AndroidManifest.xml:

    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

    Add WorkManager dependency in your build.gradle:

    implementation "androidx.work:work-runtime-ktx:2.7.1"
  2. Create a Worker for BLE scanning:

    import android.content.Context
    import android.bluetooth.BluetoothAdapter
    import android.bluetooth.BluetoothManager
    import androidx.work.Worker
    import androidx.work.WorkerParameters
    
    class BLEScanWorker(appContext: Context, workerParams: WorkerParameters):
        Worker(appContext, workerParams) {
    
        override fun doWork(): Result {
            val bluetoothManager = applicationContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
            val bluetoothAdapter = bluetoothManager.adapter
    
            if (bluetoothAdapter != null && bluetoothAdapter.isEnabled) {
                val scanner = bluetoothAdapter.bluetoothLeScanner
                scanner.startScan { callbackType, result ->
                    // Process scan results here
                }
            }
    
            // Indicate whether the work finished successfully with the Result
            return Result.success()
        }
    }
  3. Schedule the Worker:

    import androidx.work.OneTimeWorkRequestBuilder
    import androidx.work.WorkManager
    import java.util.concurrent.TimeUnit
    
    val scanRequest = OneTimeWorkRequestBuilder<BLEScanWorker>()
        .setInitialDelay(15, TimeUnit.MINUTES) // Set delay as per requirement
        .build()
    
    WorkManager.getInstance(context).enqueue(scanRequest)

iOS

On iOS, you can perform limited BLE scanning in the background by enabling the appropriate background modes in your app. However, the scanning interval and duration are restricted by the OS.

  1. Enable Background Modes:

    In your Xcode project, go to the "Capabilities" tab and enable "Background Modes". Check "Uses Bluetooth LE accessories" and "Acts as a Bluetooth LE accessory".

  2. Configure BLE Scanning in App:

    import CoreBluetooth
    
    class BLEManager: NSObject, CBCentralManagerDelegate {
        var centralManager: CBCentralManager?
    
        override init() {
            super.init()
            centralManager = CBCentralManager(delegate: self, queue: nil, options: [CBCentralManagerOptionRestoreIdentifierKey: "myCentralManagerIdentifier"])
        }
    
        func centralManagerDidUpdateState(_ central: CBCentralManager) {
            if central.state == .poweredOn {
                centralManager?.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey: NSNumber(value: true)])
            }
        }
    
        func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
            // Process discovered peripherals
        }
    
        func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
            // Restore state if needed
        }
    }
  3. Handle Background Tasks:

    You can use UIApplication background tasks to ensure that the app continues scanning for a while when it goes to the background.

    func applicationDidEnterBackground(_ application: UIApplication) {
        var bgTask: UIBackgroundTaskIdentifier = .invalid
        bgTask = application.beginBackgroundTask(withName: "BLEScan") {
            application.endBackgroundTask(bgTask)
            bgTask = .invalid
        }
    }

These are simplified examples to get you started. You may need to handle more scenarios and edge cases depending on your app's requirements. Additionally, always test thoroughly on actual devices to ensure compliance with the latest platform guidelines and restrictions.

@explodus
Copy link
Author

explodus commented Jul 4, 2024

I'd try making it work with a native app next

so you can understand how it is supposed to work

thanks for your help. i tried it already to understand with a fork of this a repository and added much more debug logging output. as i can say at the moment, somethings stops calling the getScanCallback() as soon as the flutter app gets backgrounded.
but the app especially the foregroundtask isolate is alive. so it could/should scan.

@fabiototh
Copy link

some docs

Scanning BLE in the background on mobile platforms can be challenging due to the operating system's restrictions on background activities to conserve battery life. However, both Android and iOS provide ways to perform BLE scans in the background with some limitations.

Android

On Android, you can use the JobScheduler or WorkManager APIs to schedule background tasks, including BLE scanning. Here's a simplified example using WorkManager:

  1. Add permissions and dependencies:
    Make sure you have the necessary permissions in your AndroidManifest.xml:

    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    

    Add WorkManager dependency in your build.gradle:

    implementation "androidx.work:work-runtime-ktx:2.7.1"
    
  2. Create a Worker for BLE scanning:

    import android.content.Context
    import android.bluetooth.BluetoothAdapter
    import android.bluetooth.BluetoothManager
    import androidx.work.Worker
    import androidx.work.WorkerParameters
    
    class BLEScanWorker(appContext: Context, workerParams: WorkerParameters):
        Worker(appContext, workerParams) {
    
        override fun doWork(): Result {
            val bluetoothManager = applicationContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
            val bluetoothAdapter = bluetoothManager.adapter
    
            if (bluetoothAdapter != null && bluetoothAdapter.isEnabled) {
                val scanner = bluetoothAdapter.bluetoothLeScanner
                scanner.startScan { callbackType, result ->
                    // Process scan results here
                }
            }
    
            // Indicate whether the work finished successfully with the Result
            return Result.success()
        }
    }
  3. Schedule the Worker:

    import androidx.work.OneTimeWorkRequestBuilder
    import androidx.work.WorkManager
    import java.util.concurrent.TimeUnit
    
    val scanRequest = OneTimeWorkRequestBuilder<BLEScanWorker>()
        .setInitialDelay(15, TimeUnit.MINUTES) // Set delay as per requirement
        .build()
    
    WorkManager.getInstance(context).enqueue(scanRequest)

iOS

On iOS, you can perform limited BLE scanning in the background by enabling the appropriate background modes in your app. However, the scanning interval and duration are restricted by the OS.

  1. Enable Background Modes:
    In your Xcode project, go to the "Capabilities" tab and enable "Background Modes". Check "Uses Bluetooth LE accessories" and "Acts as a Bluetooth LE accessory".
  2. Configure BLE Scanning in App:
    import CoreBluetooth
    
    class BLEManager: NSObject, CBCentralManagerDelegate {
        var centralManager: CBCentralManager?
    
        override init() {
            super.init()
            centralManager = CBCentralManager(delegate: self, queue: nil, options: [CBCentralManagerOptionRestoreIdentifierKey: "myCentralManagerIdentifier"])
        }
    
        func centralManagerDidUpdateState(_ central: CBCentralManager) {
            if central.state == .poweredOn {
                centralManager?.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey: NSNumber(value: true)])
            }
        }
    
        func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
            // Process discovered peripherals
        }
    
        func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) {
            // Restore state if needed
        }
    }
  3. Handle Background Tasks:
    You can use UIApplication background tasks to ensure that the app continues scanning for a while when it goes to the background.
    func applicationDidEnterBackground(_ application: UIApplication) {
        var bgTask: UIBackgroundTaskIdentifier = .invalid
        bgTask = application.beginBackgroundTask(withName: "BLEScan") {
            application.endBackgroundTask(bgTask)
            bgTask = .invalid
        }
    }

These are simplified examples to get you started. You may need to handle more scenarios and edge cases depending on your app's requirements. Additionally, always test thoroughly on actual devices to ensure compliance with the latest platform guidelines and restrictions.

I'm using a Service, and the app is working perfectly with Bluetooth. I can synchronize data periodically. Currently, I'm facing a similar issue. When the Bluetooth device goes out of range and comes back into range after a few minutes, using flutter_blue, I can start a scan by passing the device ID, but it always returns an empty list. Note: the app is minimized, and the screen is locked.

When I unlock the screen, this periodic service, when executed, can then find the device. But with the screen locked, it always returns an empty list of devices.

@chipweinberger
Copy link
Owner

closing in favor of new issue

#930

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help Questions, help, observations, or possible bugs
Projects
None yet
Development

No branches or pull requests

3 participants