Skip to content

Using flutter_foreground_task together with flutter_blue #84

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

Closed
leeflix opened this issue May 21, 2022 · 13 comments
Closed

Using flutter_foreground_task together with flutter_blue #84

leeflix opened this issue May 21, 2022 · 13 comments

Comments

@leeflix
Copy link

leeflix commented May 21, 2022

I am trying to make an app that uses flutter_foreground_task to scan for bluetooth devices while it is in the background with the help of flutter_blue. Is this possible? When I am taking the example of flutter_foreground_task and insert the example of flutter_blue into it I get the following error:

I/flutter (16681): Error starting scan.
E/flutter (16681): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: MissingPluginException(No implementation found for method startScan on channel plugins.pauldemarco.com/flutter_blue/methods)
E/flutter (16681): #0      FlutterBlue.scan (package:flutter_blue/src/flutter_blue.dart:123:7)
E/flutter (16681): <asynchronous suspension>
E/flutter (16681): 

When I insert the code in the initState method for example it works. It seems that we cannot use plugins that use platform channels in the background or is there a workaround or am I overseeing something?

Code:

import 'dart:isolate';

import 'package:flutter/material.dart';
import 'package:flutter_blue/flutter_blue.dart';
import 'package:flutter_foreground_task/flutter_foreground_task.dart';

void main() => runApp(const ExampleApp());

// The callback function should always be a top-level function.
void startCallback() {
  // The setTaskHandler function must be called to handle the task in the background.
  FlutterForegroundTask.setTaskHandler(MyTaskHandler());
}

class MyTaskHandler extends TaskHandler {
  SendPort? _sendPort;
  int _eventCount = 0;

  @override
  Future<void> onStart(DateTime timestamp, SendPort? sendPort) async {
    _sendPort = sendPort;

    // You can use the getData function to get the stored data.
    final customData =
        await FlutterForegroundTask.getData<String>(key: 'customData');
    print('customData: $customData');
  }

  @override
  Future<void> onEvent(DateTime timestamp, SendPort? sendPort) async {
    FlutterForegroundTask.updateService(
        notificationTitle: 'MyTaskHandler',
        notificationText: 'eventCount: $_eventCount');

    // Send data to the main isolate.
    sendPort?.send(_eventCount);

    _eventCount++;

    FlutterBlue flutterBlue = FlutterBlue.instance;
    // Start scanning
    flutterBlue.startScan(timeout: const Duration(seconds: 4));

    // Listen to scan results
    var subscription = flutterBlue.scanResults.listen((results) {
      // do something with scan results
      for (ScanResult r in results) {
        print('${r.device.name} found! rssi: ${r.rssi}');
      }
    });

    // // Stop scanning
    // flutterBlue.stopScan();
  }

  @override
  Future<void> onDestroy(DateTime timestamp, SendPort? sendPort) async {
    // You can use the clearAllData function to clear all the stored data.
    await FlutterForegroundTask.clearAllData();
  }

  @override
  void onButtonPressed(String id) {
    // Called when the notification button on the Android platform is pressed.
    print('onButtonPressed >> $id');
  }

  @override
  void onNotificationPressed() {
    // Called when the notification itself on the Android platform is pressed.
    //
    // "android.permission.SYSTEM_ALERT_WINDOW" permission must be granted for
    // this function to be called.

    // Note that the app will only route to "/resume-route" when it is exited so
    // it will usually be necessary to send a message through the send port to
    // signal it to restore state when the app is already started.
    FlutterForegroundTask.launchApp("/resume-route");
    _sendPort?.send('onNotificationPressed');
  }
}

class ExampleApp extends StatelessWidget {
  const ExampleApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: '/',
      routes: {
        '/': (context) => const ExamplePage(),
        '/resume-route': (context) => const ResumeRoutePage(),
      },
    );
  }
}

class ExamplePage extends StatefulWidget {
  const ExamplePage({Key? key}) : super(key: key);

  @override
  State<StatefulWidget> createState() => _ExamplePageState();
}

class _ExamplePageState extends State<ExamplePage> {
  ReceivePort? _receivePort;

  Future<void> _initForegroundTask() async {
    await FlutterForegroundTask.init(
      androidNotificationOptions: AndroidNotificationOptions(
        channelId: 'notification_channel_id',
        channelName: 'Foreground Notification',
        channelDescription:
            'This notification appears when the foreground service is running.',
        channelImportance: NotificationChannelImportance.LOW,
        priority: NotificationPriority.LOW,
        iconData: const NotificationIconData(
          resType: ResourceType.mipmap,
          resPrefix: ResourcePrefix.ic,
          name: 'launcher',
          backgroundColor: Colors.orange,
        ),
        buttons: [
          const NotificationButton(id: 'sendButton', text: 'Send'),
          const NotificationButton(id: 'testButton', text: 'Test'),
        ],
      ),
      iosNotificationOptions: const IOSNotificationOptions(
        showNotification: true,
        playSound: false,
      ),
      foregroundTaskOptions: const ForegroundTaskOptions(
        interval: 5000,
        autoRunOnBoot: true,
        allowWifiLock: true,
      ),
      printDevLog: true,
    );
  }

  Future<bool> _startForegroundTask() async {
    // "android.permission.SYSTEM_ALERT_WINDOW" permission must be granted for
    // onNotificationPressed function to be called.
    //
    // When the notification is pressed while permission is denied,
    // the onNotificationPressed function is not called and the app opens.
    //
    // If you do not use the onNotificationPressed or launchApp function,
    // you do not need to write this code.
    if (!await FlutterForegroundTask.canDrawOverlays) {
      final isGranted =
          await FlutterForegroundTask.openSystemAlertWindowSettings();
      if (!isGranted) {
        print('SYSTEM_ALERT_WINDOW permission denied!');
        return false;
      }
    }

    // You can save data using the saveData function.
    await FlutterForegroundTask.saveData(key: 'customData', value: 'hello');

    ReceivePort? receivePort;
    if (await FlutterForegroundTask.isRunningService) {
      receivePort = await FlutterForegroundTask.restartService();
    } else {
      receivePort = await FlutterForegroundTask.startService(
        notificationTitle: 'Foreground Service is running',
        notificationText: 'Tap to return to the app',
        callback: startCallback,
      );
    }

    return _registerReceivePort(receivePort);
  }

  Future<bool> _stopForegroundTask() async {
    return await FlutterForegroundTask.stopService();
  }

  bool _registerReceivePort(ReceivePort? receivePort) {
    _closeReceivePort();

    if (receivePort != null) {
      _receivePort = receivePort;
      _receivePort?.listen((message) {
        if (message is int) {
          print('eventCount: $message');
        } else if (message is String) {
          if (message == 'onNotificationPressed') {
            Navigator.of(context).pushNamed('/resume-route');
          }
        } else if (message is DateTime) {
          print('timestamp: ${message.toString()}');
        }
      });

      return true;
    }

    return false;
  }

  void _closeReceivePort() {
    _receivePort?.close();
    _receivePort = null;
  }

  T? _ambiguate<T>(T? value) => value;

  @override
  void initState() {
    super.initState();
    _initForegroundTask();
    _ambiguate(WidgetsBinding.instance)?.addPostFrameCallback((_) async {
      // You can get the previous ReceivePort without restarting the service.
      if (await FlutterForegroundTask.isRunningService) {
        final newReceivePort = await FlutterForegroundTask.receivePort;
        _registerReceivePort(newReceivePort);
      }
    });
  }

  @override
  void dispose() {
    _closeReceivePort();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    // A widget that prevents the app from closing when the foreground service is running.
    // This widget must be declared above the [Scaffold] widget.
    return WithForegroundTask(
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Flutter Foreground Task'),
          centerTitle: true,
        ),
        body: _buildContentView(),
      ),
    );
  }

  Widget _buildContentView() {
    buttonBuilder(String text, {VoidCallback? onPressed}) {
      return ElevatedButton(
        child: Text(text),
        onPressed: onPressed,
      );
    }

    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          buttonBuilder('start', onPressed: _startForegroundTask),
          buttonBuilder('stop', onPressed: _stopForegroundTask),
        ],
      ),
    );
  }
}

class ResumeRoutePage extends StatelessWidget {
  const ResumeRoutePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Resume Route'),
        centerTitle: true,
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            // Navigate back to first route when tapped.
            Navigator.of(context).pop();
          },
          child: const Text('Go back!'),
        ),
      ),
    );
  }
}
@Kimsangwon0509
Copy link

Same problem TT

@kumbulali
Copy link

I have the similar problem with the different plugin that is flutter_beacon. I can not initiate the plugin inside of task handlers onStart function.

@irjayjay
Copy link

Having similar issues here. Different plugins that have this issue with the isolate at different times.
I'm googling for MissingPluginException all over the internet, hoping to gain some insight. No workarounds yet.

@ksheremet
Copy link

@kumbulali , @irjayjay

I have a working proof of concept flutter_beacon and flutter_foregound_task. I've refactored flutter_beacon though.
https://github.com/futureware-tech/flutter_foreground_service

Please add your beacons in the lib/fs/beacon_example.dart file.

I hope it will be helpful.

@irjayjay
Copy link

irjayjay commented Aug 8, 2022

I found the issue with running plugins in isolates such as this library. Please see my reply on a similar issue in this repo:
#77 (comment)

Hope this helps. Check the plugin's repo issues to find how they solve MissingPluginException when using pre-Flutter 3.0.0, otherwise simply upgrade to Flutter 3.0.0 (I wouldn't recommend it, there are various bugs).

@typexy
Copy link

typexy commented Oct 27, 2022

@ksheremet many thanks for the poc. I compared your refactored flutter_beacon project and the original one and unfortunately didn't really spot a difference. Would it be possible for you to give me a hint what exactly you refactored so that I can be able to do it, too? Any help is highly appreciated @ksheremet Many thanks in advance

@ksheremet
Copy link

@typexy

Sure, I've done changes in the android folder of flutter_beacon.

Please have a look at changes here: alann-maulana/flutter_beacon@master...futureware-tech:flutter_beacon:background

@TheLastGimbus
Copy link

As of flutter 3.7 you can use plugins in any isolate 👀 maybe that will help you guys

https://medium.com/flutter/whats-new-in-flutter-3-7-38cbea71133c

@ali-almas
Copy link

ali-almas commented Feb 23, 2023

Just before starting the foreground service make sure all permissions are enabled for bluetooth

@KyungRyul
Copy link

KyungRyul commented Jun 12, 2023

I am also using flutter_foreground_task and flutter_blue_plus.
In my case, it works fine on Android, but when I run it on iOS, the following issues occur.

*** Terminating app due to uncaught exception of class 'FlutterError'
libc++abi: terminating with uncaught exception of type FlutterError

  • thread '#'1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
    frame #0: 0x00000001ff0d7160 libsystem_kernel.dylib__pthread_kill + 8 libsystem_kernel.dylib:
    -> 0x1ff0d7160 <+8>: b.lo 0x1ff0d7180 ; <+40>
    0x1ff0d7164 <+12>: pacibsp
    0x1ff0d7168 <+16>: stp x29, x30, [sp, #-0x10]!
    0x1ff0d716c <+20>: mov x29, sp
    Target 0: (Runner) stopped.

Advice please.

@rajan-nonstopio
Copy link

My use of flutter_foreground_task and flutter_blue_plus (version 1.29.11) is functioning as intended.

Note: Ensure that all necessary permissions are requested within the app itself. Please be aware that foreground service does not support permissions.

@ConnorDykes
Copy link

My use of flutter_foreground_task and flutter_blue_plus (version 1.29.11) is functioning as intended.

Note: Ensure that all necessary permissions are requested within the app itself. Please be aware that foreground service does not support permissions.

how are you doing this? I need to listen to a a Characteristic Stream and reform calculation with the value?

@Maqcel
Copy link

Maqcel commented Nov 25, 2024

Any example usages to get into?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests