Skip to content

MissingPluginException thrown when starting the foreground service #77

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
Benkhouds opened this issue May 11, 2022 · 7 comments
Closed

Comments

@Benkhouds
Copy link

Benkhouds commented May 11, 2022

I followed this example:

 @override
  Future<void> onStart(DateTime timestamp, SendPort? sendPort) async {
     final positionStream =
        Geolocator.getPositionStream();
    _streamSubscription = positionStream.listen((Position event) {
      FlutterForegroundTask.updateService(
        notificationTitle: 'Current Position',
        notificationText: '${event.latitude}, ${event.longitude}',
      );
      sendPort?.send(event);
}

Got this exception :

The following MissingPluginException was thrown while activating platform stream on channel
flutter.baseflow.com/geolocator_updates:
MissingPluginException(No implementation found for method listen on channel
flutter.baseflow.com/geolocator_updates)

When the exception was thrown, this was the stack:
#0 MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:175:7)

#1 EventChannel.receiveBroadcastStream.
(package:flutter/src/services/platform_channel.dart:516:9)

@Dev-hwang
Copy link
Owner

The Geolocator plugin doesn't seem to have good compatibility with this plugin. How about using the fl_location plugin which does the same thing?

Here's an example code:

import 'dart:async';
import 'dart:isolate';

import 'package:fl_location/fl_location.dart';
import 'package:flutter/material.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 {
  StreamSubscription<Location>? _streamSubscription;

  @override
  Future<void> onStart(DateTime timestamp, SendPort? sendPort) async {
    _streamSubscription = FlLocation.getLocationStream().listen((event) {
      FlutterForegroundTask.updateService(
        notificationTitle: 'My Location',
        notificationText: '${event.latitude}, ${event.longitude}',
      );

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

  @override
  Future<void> onEvent(DateTime timestamp, SendPort? sendPort) async {

  }

  @override
  Future<void> onDestroy(DateTime timestamp, SendPort? sendPort) async {
    await _streamSubscription?.cancel();
  }
}

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: ExamplePage(),
    );
  }
}

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

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

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

  Future<bool> _checkAndRequestPermission({bool? background}) async {
    if (!await FlLocation.isLocationServicesEnabled) {
      // Location services are disabled.
      return false;
    }

    var locationPermission = await FlLocation.checkLocationPermission();
    if (locationPermission == LocationPermission.deniedForever) {
      // Cannot request runtime permission because location permission is denied forever.
      return false;
    } else if (locationPermission == LocationPermission.denied) {
      // Ask the user for location permission.
      locationPermission = await FlLocation.requestLocationPermission();
      if (locationPermission == LocationPermission.denied ||
          locationPermission == LocationPermission.deniedForever) return false;
    }

    // Location permission must always be allowed (LocationPermission.always)
    // to collect location data in the background.
    if (background == true &&
        locationPermission == LocationPermission.whileInUse) return false;

    // Location services has been enabled and permission have been granted.
    return true;
  }

  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,
      ),
      foregroundTaskOptions: const ForegroundTaskOptions(
        autoRunOnBoot: true,
        allowWifiLock: true,
      ),
      printDevLog: true,
    );
  }

  Future<bool> _startForegroundTask() async {
    if (await _checkAndRequestPermission(background: true) == false) {
      print('Location permission denied!!');
      return false;
    }

    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 Location) {
          print('location: ${message.toJson()}');
        }
      });

      return true;
    }

    return false;
  }

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

  @override
  void initState() {
    super.initState();
    _initForegroundTask();
    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),
        ],
      ),
    );
  }
}

@Benkhouds
Copy link
Author

I had to update the onRequestPermissionsResult in the LocationPermission.kt file to match its signature but other than that everything works perfectly (still didn't test it on ios though). Thank you for the support !! <3

@irjayjay
Copy link

Hi, I have this same issue with Geolocator.
Why doesn't it have good compatiblity? What's the technical reason?

I have a bunch of other plugins that also throw MissingPluginExceptions in the foreground service and I'm trying to understand why.

@irjayjay
Copy link

Hi, any news on this?
I'm trawling the internet and a lot of posts are mentioning to ensure you run the following in an isolate to ensure other plugins can be called(I'm no expert on isolates though):
DartPluginRegistrant.ensureInitialized()

Reference link.

@Benkhouds
Copy link
Author

@irjayjay Yes this plugin doesn't support Geolocator, it worked for me with fl_location, but i had to open to kotlin files and change the signature of some methods. To be honest if you're looking for a production grade plugin the only one available is flutter_background_geolocator (and it has its drawbacks) and it required a licence on android (300$) but it doesn't require one for debug build. If you really need access to background geolocation and want to trigger callbacks (like saving in local db or alerting the user when he reaches his destination) then this is the only solution currently available in flutter. Hope i answered your questions

@gembancud
Copy link

I had to update the onRequestPermissionsResult in the LocationPermission.kt file to match its signature but other than that everything works perfectly (still didn't test it on ios though). Thank you for the support !! <3

I'd like to use geolocation on foreground/background services as well. Though manually changing cached kotlin files can get cumbersome. Could you perhaps raise it as an issue in their following repo?

@irjayjay
Copy link

irjayjay commented Aug 3, 2022

Better context for future reference:
Flutter 2 has an issue with plugin registration in isolates that only got solved in Flutter 3. Some plugins work fine, others don't. This is why you're getting this issue.

WARNING: Before attempting this fix: our team has found Flutter 3 highly unstable, especially when using any map plugins such as mapbox/gmaps/etc. Various crashes have forced us to remain on an earlier version of Flutter.

Solution is to upgrade to Flutter 3 and run:
DartPluginRegistrant.ensureInitialized()
before accessing any plugins.

The issue is better solved by both upgrading to Flutter 3 and upgrading flutter_foreground_task to version 3.8.1, then you don't have to run ensureInitialized().

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

4 participants