Dev-hwang / flutter_foreground_task

This plugin is used to implement a foreground service on the Android platform.
https://pub.dev/packages/flutter_foreground_task
MIT License
140 stars 105 forks source link

Discussion: Does `FlutterForegroundTask.receivePort` created new everytime we access it? #244

Closed Vinayak006 closed 1 month ago

Vinayak006 commented 1 month ago

In documentation, it is stated that, Register the receivePort before starting the service.

Below is my startService code:

Future<void> start(final ConnectParam param) async {
    recievePort = FlutterForegroundTask.receivePort;
    receivePortStreamSubscription = FlutterForegroundTask.receivePort?.listen(
      (final message) {},
      onDone: () {},
      onError: (final error) {},
    );
    await FlutterForegroundTask.startService(
      notificationTitle: "My Service",
      notificationText: "This is required to receive call in background",
      callback: startCallback,
    );
    FlutterForegroundTask.sendData({
      "connect": param.toJson(),
    });
  }

I have registered receivePort before starting the service as said, and it works fine.

But when the app is closed/terminated, and opened again, receivePort is not receiving any message. As stated in documentation, You can get the previous ReceivePort without restarting the service, I did tried getting FlutterForegroundTask.receivePort, but port isn't getting any message from sendPort.

Below is my initState code:

@override
  Future<void> onInit() async {
    super.onInit();
    FlutterForegroundTask.init();
    if (await FlutterForegroundTask.isRunningService) {
      recievePort = FlutterForegroundTask.receivePort;
      receivePortStreamSubscription = recievePort?.listen(
        (final message) {
          logger.d("Recived Message: $message");
         },
        onDone: () {},
        onError: (final error) {
          logger.e(error);
        },
      );
      FlutterForegroundTask.sendData("getRegisterState");
    }
  }

Now, my question is that wheather receive port created new everytime?

Dev-hwang commented 1 month ago

@Vinayak006

The document is incorrect.

It is correct that calling the receivePort getter function creates a new ReceivePort.

Currently, when the app starts, a ReceivePort must be created and managed as a global instance.

I plan to improve this issue. Thank you for reporting.

Vinayak006 commented 1 month ago

@Dev-hwang, Thank you for your clarification.

Is there any workaround that we can use in the meantime to handle this more efficiently?

Vinayak006 commented 1 month ago

Nope, Didn't helped. Tried but same problem. receivePort not receiving new messages sent by sentPort once app is killed and re-opened. @Dev-hwang

Dev-hwang commented 1 month ago

Can you share the project?

Vinayak0-0 commented 1 month ago

I can't share the project.

Let me summarize what I have done so far.

main.dart

ReceivePort? receivePort;

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  if (GetPlatform.isAndroid) {
    receivePort = FlutterForegroundTask.receivePort;
    Get.put<ForegroundService>(ForegroundService());
  }
  runApp(const SipPhone());
}

As you can see, I made receivePort as globally accessible.

ForegroundService.dart

@pragma("vm:entry-point")
Future<void> startCallback() async {
  FlutterForegroundTask.setTaskHandler(MyTaskHandler());
}

class ForegroundService extends GetxController {
  StreamSubscription? receivePortStreamSubscription;

  Rxn<RegisterState> registerState = Rxn<RegisterState>();
  Rxn<ConnectionState> connectionState = Rxn<ConnectionState>();

  Future<void> start(final ConnectParam param) async {
    await FlutterForegroundTask.startService(
      notificationTitle: "My service",
      notificationText: "This is required to receive call in background",
      callback: startCallback,
    );
    FlutterForegroundTask.sendData({
      "connect": param.toJson(),
    });
  }

  Future<void> stop() async {
    FlutterForegroundTask.sendData("disconnect");
    await receivePortStreamSubscription?.cancel();
    await FlutterForegroundTask.stopService();
  }

  @override
  Future<void> onInit() async {
    super.onInit();
    FlutterForegroundTask.init(
      androidNotificationOptions: AndroidNotificationOptions(
        channelId: "sip",
        channelName: "SIP",
      ),
      iosNotificationOptions: const IOSNotificationOptions(),
      foregroundTaskOptions: const ForegroundTaskOptions(
        isOnceEvent: true,
      ),
    );
  }

  @override
  Future<void> onClose() async {
    super.onClose();
    await receivePortStreamSubscription?.cancel();
  }
}

My task handler

class MyTaskHandler extends TaskHandler implements SipUaHelperListener {
  final SIPUAHelper sipuaHelper = SIPUAHelper();

  SendPort? _sendPort;
  @override
  void onDestroy(final DateTime timestamp, final SendPort? sendPort) {
    _sendPort = sendPort;
  }

  @override
  Future<void> onReceiveData(final Object data) async {
    super.onReceiveData(data);
    if (data is Map) {
      if (data["connect"] != null) {
        // Handle connect logic
      }
    }
    if (data is String) {
      if (data == "disconnect") {
          //handle disconnect logic
      }
    }
  }

  @override
  void onRepeatEvent(final DateTime timestamp, final SendPort? sendPort) {}

  @override
  void onStart(final DateTime timestamp, final SendPort? sendPort) {
    logger.i("onStart called");
    sipuaHelper.addSipUaHelperListener(this);
    _sendPort = sendPort;
  }

  @override
  void callStateChanged(final Call call, final CallState state) {
    _sendPort?.send("CALL ${state.state}");
  }

  @override
  void onNewMessage(final SIPMessageRequest msg) {}

  @override
  void onNewNotify(final Notify ntf) {}

  @override
  void registrationStateChanged(final RegistrationState state) {
    logger.i("registrationStateChanged called");
   _sendPort?.send("RegistrationState Changed");
  }

  @override
  void transportStateChanged(final TransportState state) {
     _sendPort?.send("RegistrationState Changed");
  }
}

And in home controller,

class HomeController extends GetxController {
  @override
  void onInit() {
    super.onInit();
    if (GetPlatform.isAndroid) {
      // ignore: always_specify_types
      receivePort?.listen((final message) {
        logger.i("MESSAGE: $message");
        Get.snackbar("Message", "$message");
      });
    }
  }
}

when starting the service, i can get the log and snackbar message. But once app opened from terminated state, receivePort isn't receive message.

Dev-hwang commented 1 month ago

@Vinayak006

When you call the FlutterForegroundTask.receivePort getter function, SendPort is also new created.

ReceivePort and SendPort are pairs.

You can temporarily use the IsolateNameServer.lookupPortByName function.

import 'dart:isolate';
import 'dart:ui';

class MyTaskHandler extends TaskHandler implements SipUaHelperListener {
  final SIPUAHelper sipuaHelper = SIPUAHelper();

  // SendPort? _sendPort;
  @override
  void onDestroy(final DateTime timestamp, final SendPort? sendPort) {
    // _sendPort = sendPort;
  }

  @override
  Future<void> onReceiveData(final Object data) async {
    super.onReceiveData(data);
    if (data is Map) {
      if (data["connect"] != null) {
        // Handle connect logic
      }
    }
    if (data is String) {
      if (data == "disconnect") {
        //handle disconnect logic
      }
    }
  }

  @override
  void onRepeatEvent(final DateTime timestamp, final SendPort? sendPort) {}

  @override
  void onStart(final DateTime timestamp, final SendPort? sendPort) {
    logger.i("onStart called");
    sipuaHelper.addSipUaHelperListener(this);
    // _sendPort = sendPort;
  }

  @override
  void callStateChanged(final Call call, final CallState state) {
    final SendPort? sendPort =
        IsolateNameServer.lookupPortByName('flutter_foreground_task/isolateComPort');
    sendPort?.send("CALL ${state.state}");
  }

  @override
  void onNewMessage(final SIPMessageRequest msg) {}

  @override
  void onNewNotify(final Notify ntf) {}

  @override
  void registrationStateChanged(final RegistrationState state) {
    final SendPort? sendPort =
        IsolateNameServer.lookupPortByName('flutter_foreground_task/isolateComPort');
    logger.i("registrationStateChanged called");
    sendPort?.send("RegistrationState Changed");
  }

  @override
  void transportStateChanged(final TransportState state) {
    final SendPort? sendPort =
        IsolateNameServer.lookupPortByName('flutter_foreground_task/isolateComPort');
    sendPort?.send("RegistrationState Changed");
  }
}
Vinayak006 commented 1 month ago

Yeah, it is working now.

Thanks for the assistance :-) @Dev-hwang

Dev-hwang commented 1 month ago

@Vinayak006

I discovered a big problem through this discussion. thank you

Dev-hwang commented 1 month ago

The new version makes it easier to troubleshoot.

dependencies:
  flutter_foreground_task: ^8.0.0

Migration Guide https://github.com/Dev-hwang/flutter_foreground_task#migration-ver-800

Vinayak006 commented 1 month ago

@Dev-hwang

That's a quick fix, and I appreciate it. I'll update to the new version using the migration guide.