transistorsoft / flutter_background_geolocation

Sophisticated, battery-conscious background-geolocation & geofencing with motion-detection
https://www.transistorsoft.com/shop/products/flutter-background-geolocation
Other
640 stars 237 forks source link

Do we need to annotate geolocationHeadlessTask with pragma(vm:entry-point) ??? #840

Closed yaroslav13 closed 1 year ago

yaroslav13 commented 1 year ago

@christocracy Do we need to annotate geolocationHeadlessTask with pragma("vm:entry-point") ???


bg.BackgroundGeolocation.registerHeadlessTask(geolocationHeadlessTask);

void geolocationHeadlessTask(bg.HeadlessEvent headlessEvent) {
  headlessGeolocationCallback(headlessEvent);
}
christocracy commented 1 year ago

pragma("vm:entry-point")

I’ve never heard of it. What is it???

yaroslav13 commented 1 year ago

@pragma("vm:entry-point") to mark a function (or other entities, as classes) to indicate to the compiler that it will be used from native code. Without this annotation, the dart compiler could strip out unused functions, inline them, shrink names, etc, and the native code would fail to call it.

A very good doc (written much more clearly than usual) about entry-point is https://github.com/dart-lang/sdk/blob/master/runtime/docs/compiler/aot/entry_point_pragma.md

christocracy commented 1 year ago

I’ve never heard of it. Use it if you wish.

yaroslav13 commented 1 year ago

So... I am asking because firebase messaging adds for his docs this point for [_firebaseMessagingBackgroundHandler]. Maybe it is somehow related to the new Flutter version... I don't know (

  1. It must be annotated with @pragma('vm:entry-point') right above the function declaration (otherwise it may be removed during tree shaking for release mode).

    
    @pragma('vm:entry-point')
    Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) 
jasonkaruza commented 1 year ago

Yes, I believe it's needed, but it didn't resolve the issue for me. When using the location service while my app is in the foreground, it works fine. Once swiped away, I get the following errors:

D/FlutterGeolocator(27763): Creating service.
D/FlutterGeolocator(27763): Binding to location service.
E/flutter (27763): [ERROR:flutter/shell/common/shell.cc(93)] Dart Error: Dart_LookupLibrary: library 'package:flutter_background_geolocation/flutter_background_geolocation.dart' not found.
E/flutter (27763): [ERROR:flutter/runtime/dart_isolate.cc(668)] Could not resolve main entrypoint function.
E/flutter (27763): [ERROR:flutter/runtime/dart_isolate.cc(167)] Could not run the run main Dart entrypoint.
E/flutter (27763): [ERROR:flutter/runtime/runtime_controller.cc(385)] Could not create root isolate.
E/flutter (27763): [ERROR:flutter/shell/common/shell.cc(604)] Could not launch engine with configuration.

I tried adding the decorator per the Dart guidance as such:

/// Receive events from BackgroundGeolocation in Headless state.
@pragma('vm:entry-point')
void backgroundGeolocationHeadlessTask(bg.HeadlessEvent headlessEvent) async {

and I am using the following version of Flutter

[√] Flutter (Channel stable, 3.3.3, on Microsoft Windows [Version 10.0.22621.521], locale en-US)
[√] Android toolchain - develop for Android devices (Android SDK version 33.0.0)

but it's still not working. I am using 4.3.4 version of the plugin.

christocracy commented 1 year ago

I suggest you attempt to reproduce this with the /example app in this repo.

christocracy commented 1 year ago

Also, the issue template asked you for a number of important pieces of information, including flutter doctor and plugin version.

jasonkaruza commented 1 year ago

I downgraded flutter to v3.0.5 in the meantime

flutter downgrade v3.0.5

christocracy commented 1 year ago
$ flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.3.4, on macOS 12.6 21G115 darwin-x64, locale en-CA)

I have no issue with headless on the /example app in this repo using latest flutter sdk (3.3.4). Have you tried the /example?

christocracy commented 1 year ago

Show me your entire lib/main.dart

jasonkaruza commented 1 year ago

Show me your entire lib/main.dart @christocracy I am not sure if that's directed at me, but here you go:

// DART IMPORTS
import 'dart:async';
import 'dart:io' show Platform;

// PACKAGE IMPORTS
import 'package:background_fetch/background_fetch.dart';
// import 'package:connectivity/connectivity.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_background_geolocation/flutter_background_geolocation.dart'
    as bg;
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:get_it/get_it.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:wakelock/wakelock.dart';

// LOCAL IMPORTS
import 'package:ive_arrived/constants.dart';
import 'package:ive_arrived/controllers/connectivity_controller.dart';
import 'package:ive_arrived/controllers/location_controller.dart';
// import 'package:ive_arrived/controllers/notification_controller.dart';
import 'package:ive_arrived/data/state_data.dart';
import 'package:ive_arrived/utilities/log.dart';
import 'package:ive_arrived/widgets/dialogs/dialog_service.dart';
import 'package:ive_arrived/widgets/home_screen.dart';
import 'package:ive_arrived/widgets/new_version_screen.dart';
import 'package:ive_arrived/widgets/onboarding_screen.dart';
import 'package:ive_arrived/widgets/opening_splash_screen.dart';

ConnectivityController connectivityController = ConnectivityController();
StateData stateData = StateData();
GetIt getIt = GetIt.instance;
const fetchBackgroundLocation = "fetchBackgroundLocation";
bool permissionsGranted = false;

void main() {
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize ads
  MobileAds.instance.initialize();

  // Set app mode (debug/release) into state data
  stateData.setMode(!bool.fromEnvironment('dart.vm.product'));

  // Get the logging enabled setting at build time to specify if we should
  // output logs or not.
  const bool loggingEnabled =
      bool.fromEnvironment("logging-enabled", defaultValue: false);
  bool isInDebugMode = stateData.isDebug();
  bool logsEnabled = isInDebugMode || loggingEnabled;
  print("Logs enabled? " + (logsEnabled ? "yes" : "no"));
  stateData.setLoggingEnabled(logsEnabled);

  // Set OS into state data
  stateData.setAndroid(Platform.isAndroid);

  stateData.toggleStateNotifications(isInDebugMode);

  // Set the callback functions to be performed when we lose or restore
  // connectivity for headless (will be overwritten in home screen later when
  // headed)
  connectivityController.setLostConnectionCallback(() async {
    Log.logMessage(
        "The app has lost connection to the internet! Main callback.");
    stateData.setConnected(false);
  });
  connectivityController.setRestoredConnectionCallback(() async {
    Log.logMessage("The app is connected to the internet! Main callback.");
    stateData.setConnected(true);
  });
  ConnectivityController().isConnected();

  // https://github.com/flutter/plugins/blob/master/packages/firebase_crashlytics/example/lib/main.dart
  // Pass all uncaught errors to Crashlytics.
  FlutterError.onError = (FlutterErrorDetails details) {
    if (isInDebugMode) {
      // In development mode, simply print to console.
      Log.logError(details.exception.toString(), details.stack);
    } else {
      // In production mode, report to the application zone to report to
      // Sentry.
      Zone.current.handleUncaughtError(details.exception, details.stack);
    }
  };

  getIt.registerLazySingleton(() => DialogService());

  FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
      FlutterLocalNotificationsPlugin();
  const AndroidInitializationSettings initializationSettingsAndroid =
      AndroidInitializationSettings(assetLogoNotificationIcon);
  final IOSInitializationSettings initializationSettingsIOS =
      IOSInitializationSettings(
    requestSoundPermission: false,
    requestBadgePermission: false,
    requestAlertPermission: false,
    onDidReceiveLocalNotification: onDidReceiveLocalNotification,
  );
  final MacOSInitializationSettings initializationSettingsMacOS =
      MacOSInitializationSettings(
          requestAlertPermission: false,
          requestBadgePermission: false,
          requestSoundPermission: false);
  final InitializationSettings initializationSettings = InitializationSettings(
      android: initializationSettingsAndroid,
      iOS: initializationSettingsIOS,
      macOS: initializationSettingsMacOS);

  // Initialize the tray notifications
  flutterLocalNotificationsPlugin.initialize(initializationSettings,
      onSelectNotification: selectNotification);

  /// Application selection:  Select the app to boot:
  /// - AdvancedApp
  /// - HelloWorldAp
  /// - HomeApp
  ///
  SharedPreferences.getInstance().then((SharedPreferences prefs) {
    // Sanitize old-style registration system that only required username.
    // If we find a valid username but null orgname, reverse them.
    String orgname = prefs.getString("orgname");
    String username = prefs.getString("username");

    if (orgname == null && username != null) {
      prefs.setString("orgname", username);
      prefs.remove("username");
    }
  });
  TransistorAuth.registerErrorHandler();

  /// Register BackgroundGeolocation headless-task.
  bg.BackgroundGeolocation.registerHeadlessTask(
      backgroundGeolocationHeadlessTask);

  runZonedGuarded<Future<void>>(() async {
    runApp(IveArrived());
  }, (dynamic exception, StackTrace stack, {dynamic context}) {
    Log.logError("Crashlytics exception: $exception", stack);
    //return Crashlytics.instance.recordError(exception, stack, context: context);
  });
}

/// Return the notification mode based on the product flavor, defined by the app
/// package name. https://stackoverflow.com/a/61674385
///
/// @return Future<int> notifMode* constant enum based on package name
Future<int> getNotifMode() async {
  // Flavor extraction
  PackageInfo packageInfo = await PackageInfo.fromPlatform();
  String packageName = packageInfo.packageName;
  switch (packageName) {
    case "$dynamicLinkPackageNameBase$productFlavorSms":
      return notifModeSms;
  }
  return notifModeTwilio;
}

/// Initialize the stateData object and global dynamicLinkPackageName variables
/// based on the product flavor.
Future<void> initNotifModeAndPackageName() async {
  int notifMode = await getNotifMode();
  switch (notifMode) {
    case notifModeSms:
      dynamicLinkPackageName += productFlavorSms;
      break;
    default:
      dynamicLinkPackageName += productFlavorTwilio;
  }
  stateData.setNotifMode(notifMode);
}

/// Receive events from BackgroundGeolocation in Headless state.
@pragma('vm:entry-point', true)
void backgroundGeolocationHeadlessTask(bg.HeadlessEvent headlessEvent) async {
  Log.logFunctionName(
      "main", "backgroundGeolocationHeadlessTask($headlessEvent)");

  // Set because we are headless
  stateData.setHeadless(true);

  // Init notification mode stateData info
  await initNotifModeAndPackageName();

  bg.Location location;

  switch (headlessEvent.name) {
    case bg.Event.TERMINATE:
      try {
        location =
            await bg.BackgroundGeolocation.getCurrentPosition(samples: 1);
      } catch (error) {
        Log.logError("[getCurrentPosition] Headless ERROR: $error");
      }
      break;
    case bg.Event.HEARTBEAT:
      try {
        location =
            await bg.BackgroundGeolocation.getCurrentPosition(samples: 1);
      } catch (error) {
        Log.logError("[getCurrentPosition] Headless ERROR: $error");
      }
      break;
    case bg.Event.LOCATION:
      location = headlessEvent.event;
      break;
    case bg.Event.MOTIONCHANGE:
      location = headlessEvent.event;
      break;
    case bg.Event.GEOFENCE:
      bg.GeofenceEvent geofenceEvent = headlessEvent.event;
      Log.logMessage(geofenceEvent);
      break;
    case bg.Event.GEOFENCESCHANGE:
      bg.GeofencesChangeEvent event = headlessEvent.event;
      Log.logMessage(event);
      break;
    case bg.Event.SCHEDULE:
      bg.State state = headlessEvent.event;
      Log.logMessage(state);
      break;
    case bg.Event.ACTIVITYCHANGE:
      bg.ActivityChangeEvent event = headlessEvent.event;
      Log.logMessage(event);
      break;
    case bg.Event.HTTP:
      bg.HttpEvent response = headlessEvent.event;
      Log.logMessage(response);
      break;
    case bg.Event.POWERSAVECHANGE:
      bool enabled = headlessEvent.event;
      Log.logMessage(enabled);
      break;
    case bg.Event.CONNECTIVITYCHANGE:
      bg.ConnectivityChangeEvent event = headlessEvent.event;
      Log.logMessage(event);
      break;
    case bg.Event.ENABLEDCHANGE:
      bool enabled = headlessEvent.event;
      Log.logMessage(enabled);
      break;
    case bg.Event.AUTHORIZATION:
      bg.AuthorizationEvent event = headlessEvent.event;
      Log.logMessage(event);
      // bg.BackgroundGeolocation.setConfig(
      //     bg.Config(url: "${ENV.TRACKER_HOST}/api/locations"));
      break;
  }

  // If we have a valid location that is moving, handle the event
  if (location != null) {
    Log.logMessage("Got a headless location: $location");
    LocationController.handleEvents(
        location.coords.latitude, location.coords.longitude);
  }
}

/// Receive events from BackgroundFetch in Headless state.
void backgroundFetchHeadlessTask(String taskId) async {
  Log.logFunctionName("main", "backgroundFetchHeadlessTask($taskId)");

  SharedPreferences prefs = await SharedPreferences.getInstance();
  int count = 0;
  if (prefs.get("fetch-count") != null) {
    count = prefs.getInt("fetch-count");
  }
  prefs.setInt("fetch-count", ++count);
  Log.logMessage("[BackgroundFetch] count: $count");

  BackgroundFetch.finish(taskId);
}

/// For flutter_background_geolocation
/// https://github.com/transistorsoft/flutter_background_geolocation/blob/master/example/lib/config/transistor_auth.dart
class TransistorAuth {
  static Future<SharedPreferences> _prefs = SharedPreferences.getInstance();

  static Future<bool> register() async {
    try {
      SharedPreferences prefs = await _prefs;
      // Request a JWT from tracker.transistorsoft.com
      String orgname = prefs.getString("orgname");
      String username = prefs.getString("username");
      if (orgname == null || username == null) {
        return false;
      }

      bg.TransistorAuthorizationToken jwt =
          await bg.TransistorAuthorizationToken.findOrCreate(
              orgname, username, ENV.TRACKER_HOST);

      await bg.BackgroundGeolocation.setConfig(
          bg.Config(transistorAuthorizationToken: jwt));
      return true;
    } catch (error) {
      Log.logMessage("[ERROR] $error");
      return false;
    }
  }

  /// For flutter_background_geolocation
  static Future<void> registerErrorHandler() async {
    bg.State state = await bg.BackgroundGeolocation.state;
    if ((state.params != null) && (state.params['device'] != null)) {
      _migrateConfig();
    }
  }

  static void _migrateConfig() async {
    Log.logFunctionName("TransistorAuth", "_migrateConfig()");
    await bg.TransistorAuthorizationToken.destroy(ENV.TRACKER_HOST);
    // https://github.com/transistorsoft/flutter_background_geolocation/blob/4d83901958e9e96ae3d31511c7ffcc0a247e3657/lib/models/background_geolocation.dart#L189
    bg.BackgroundGeolocation.reset(bg.Config(
        backgroundPermissionRationale: bg.PermissionRationale(
            title: textLocationAccessRequired,
            message: textLocationPermissionsNeeded,
            positiveAction: textAllowWhileInUse,
            negativeAction: textCancel),
        debug: false,
        enableHeadless: true,
        heartbeatInterval: configHeartbeatInterval,
        logLevel: bg.Config.LOG_LEVEL_VERBOSE,
        desiredAccuracy: bg.Config.DESIRED_ACCURACY_HIGH,
        distanceFilter: limitMinimumDistanceFilter,
        stopOnTerminate: false,
        startOnBoot: true,
        // url: "${ENV.TRACKER_HOST}/api/locations",
        params: {}));
  }
}

/// Callback method for when a tray notification is clicked on
Future<dynamic> onDidReceiveLocalNotification(
    int id, String title, String body, String payload) async {
  Log.logFunctionName("_IveArrived",
      "onDidReceiveLocalNotification($id, $title, $body, $payload)");
  // TODO: Route to event's tab
}

/// Callback method for selecting a notification?
Future selectNotification(String payload) async {
  Log.logFunctionName("_IveArried", "selectNotification($payload)");
  if (payload != null) {
    // TODO: Do something?
  }
}

/// Main jumping off point
class IveArrived extends StatefulWidget {
  @override
  _IveArrived createState() => _IveArrived();
}

class _IveArrived extends State<IveArrived>
    with SingleTickerProviderStateMixin {
  // final Connectivity _connectivity = Connectivity();
  // StreamSubscription<ConnectivityResult> _connectivitySubscription;
  StateData stateData = StateData();
  GlobalKey _keyMain = GlobalKey();
  Timer _timerLink;

  // Will set the main global key for sizing
  _afterLayout(_) {
    Log.logFunctionName("_IveArrived", "_afterLayout()");
    stateData.setMainKey(_keyMain);
  }

  /// Update the current location temporarily. This is done because location
  /// may be turned off, but we may stil want to get the coords when creating a
  /// new place.
  Future<void> updateCurrentLocation() async {
    bg.Location location = await bg.BackgroundGeolocation.getCurrentPosition(
        persist: true, // <-- do persist this location
        desiredAccuracy: 0, // <-- desire best possible accuracy
        timeout: 3, // <-- wait 30s before giving up.
        samples: 1 // <-- sample 3 location before selecting best.
        );
    if (location != null) {
      stateData.setLocation(
          location.coords.latitude, location.coords.longitude);
    }
  }

  @override
  void initState() {
    Log.logFunctionName("_IveArrived", "initState()");
    WidgetsBinding.instance.addPostFrameCallback(_afterLayout);
    super.initState();
    Wakelock.toggle(
        enable:
            stateData.isDebug()); // Keep screen on/active to avoid lock screen

    // Set the callback for updating the current location
    stateData.setUpdateCurrentLocationCallback(updateCurrentLocation);

    // flutter_background_geolocation
    _initPlatformState();
  }

  /// BackgroundGeolocation initstate
  /// https://github.com/transistorsoft/flutter_background_geolocation
  /// | [onLocation]           | Fired with each recorded [Location]     |
  /// | [onMotionChange]       | Fired when the plugin changes state between *moving* / *stationary* |
  /// | [onHttp]               | Fired with each HTTP response from your server.  (see [Config.url]). |
  /// | [onActivityChange]     | Fired with each change in device motion-activity.                    |
  /// | [onProviderChange]     | Fired after changes to device location-services configuration.       |
  /// | [onHeartbeat]          | Periodic timed events.  See [Config.heartbeatInterval].  iOS requires [Config.preventSuspend]. |
  /// | [onGeofence]           | Fired with each [Geofence] transition event (`ENTER, EXIT, DWELL`).  |
  /// | [onGeofencesChange]    | Fired when the list of actively-monitored geofences changed.  See [Config.geofenceProximityRadius]. |
  /// | [onSchedule]           | Fired for [Config.schedule] events.                                  |
  /// | [onConnectivityChange] | Fired when network-connectivity changes (connected / disconnected).  |
  /// | [onPowerSaveChange]    | Fired when state of operating-system's "power-saving" feature is enabled / disabled. |
  /// | [onEnabledChange]      | Fired when the plugin is enabled / disabled via its [start] / [stop] methods.        |
  /// | [onNotificationAction]
  Future<Null> _initPlatformState() async {
    Log.logFunctionName("_IveArrived", "_initPlatformState()");

    // Init notification mode stateData info
    await initNotifModeAndPackageName();

    bg.BackgroundGeolocation.onActivityChange((bg.ActivityChangeEvent event) {
      Log.logMessage("[onActivityChange] $event");
    });

    bg.BackgroundGeolocation.onHeartbeat((bg.HeartbeatEvent event) {
      Log.logMessage("*** heartbeat event received");
      bg.BackgroundGeolocation.getCurrentPosition(
              persist: true, // <-- do persist this location
              desiredAccuracy: 0, // <-- desire best possible accuracy
              timeout: 30000, // <-- wait 30s before giving up.
              samples: 3 // <-- sample 3 location before selecting best.
              )
          .then((bg.Location location) {
        Log.logMessage(
            "BackgroundGeolocation.getCurrentPosition() - $location");
        LocationController.handleEvents(
            location.coords.latitude, location.coords.longitude);
      }).catchError((error) {
        Log.logError(
            "BackgroundGeolocation.getCurrentPosition() ERROR: $error");
      });
    });

    // bg.BackgroundGeolocation.onHttp((bg.HttpEvent event) async {
    //   Log.logMessage("[${bg.Event.HTTP}] - $event");
    // });

    bg.BackgroundGeolocation.onLocation((bg.Location location) {
      Log.logMessage("In bg.BackgroundGeoLocation.onLocation(): $location");
      LocationController.handleEvents(
          location.coords.latitude, location.coords.longitude);
    }, _onLocationError);

    // Fired whenever the plugin changes motion-state (stationary->moving and vice-versa)
    bg.BackgroundGeolocation.onMotionChange((bg.Location location) {
      Log.logMessage("[motionchange] - $location");
      LocationController.handleEvents(
          location.coords.latitude, location.coords.longitude);
    });

    // Fired whenever the state of location-services changes.  Always fired at boot
    bg.BackgroundGeolocation.onProviderChange((bg.ProviderChangeEvent event) {
      Log.logMessage("[providerchange] - $event");
    });
  }

  void _onLocationError(bg.LocationError error) {
    Log.logMessage("[location] ERROR - $error");
  }

  @override
  void dispose() {
    // _connectivitySubscription.cancel();
    super.dispose();
    if (_timerLink != null) {
      _timerLink.cancel();
    }
  }

  @override
  Widget build(BuildContext context) {
    // Force portrait orientation
    SystemChrome.setPreferredOrientations([
      DeviceOrientation.portraitUp,
      DeviceOrientation.portraitDown,
    ]);

    // Hide bottom navbar and top bar ([]) for fullscreen mode
    // Otherwse (SystemUiOverlay.values)
    // SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);

    return MaterialApp(
      key: _keyMain,
      title: appTitle,
      theme: ThemeData(
        brightness: Brightness.light,
        cardColor:
            stateData.theme.backgroundColor, // For kebab menu background color
        primaryColor: Colors.blue,
        unselectedWidgetColor: Colors.red,
      ),
      darkTheme: ThemeData(
          brightness: Brightness.dark,
          cardColor: Colors.black, // For kebab menu background color
          unselectedWidgetColor: Colors.red),
      initialRoute: routeOpeningSplashScreen,
      routes: <String, WidgetBuilder>{
        routeHomeScreen: (BuildContext context) => HomeScreen(),
        routeNewVersionScreen: (BuildContext context) => NewVersionScreen(),
        routeOnboardingScreen: (BuildContext context) => OnboardingScreen(),
        routeOpeningSplashScreen: (BuildContext context) =>
            OpeningSplashScreen(),
      },
    );
  }
}

typedef MultiTapButtonCallback = void Function(bool correctNumberOfTouches);
christocracy commented 1 year ago

I suggest you move the definition of void backgroundGeolocationHeadlessTask(bg.HeadlessEvent headlessEvent) async { before main().

Have you tried experimenting with the plugin in a simple Hello World app?

jasonkaruza commented 1 year ago

I suggest you move the definition of void backgroundGeolocationHeadlessTask(bg.HeadlessEvent headlessEvent) async { before main().

Have you tried experimenting with the plugin in a simple Hello World app?

I made the update that you proposed, but haven't updated Flutter version yet. Also I haven't tried the Hello World plugin for a while (when I started developing it was the basis, but once I had gotten things working, there was no need for it). Of course, Flutter changes, Dart changes, your plugin changes... :-) Things sometimes stop working for unexpected reasons.

An unrelated question: When using a GPS mock location app such as this one, I am finding that GeoLocation plugin doesn't seem to detect the change and trigger location change events with the GPS changes alone. Is there some trick you'd suggest for "forcing" the plugin to operate on the GPS changes for testing (vs being more battery efficient using accelerometers, etc)?

christocracy commented 1 year ago

GeoLocation plugin doesn't seem to detect the change and trigger location change events with the GPS changes alone

That's correct. The plugin only turns on location-services and begins listening to location updates when the device is detected to be moving or exits an internal geofence around the last known position.

You have to manually engage the moving state by calling .changePace(true).

jasonkaruza commented 1 year ago

You have to manually engage the moving state by calling .changePace(true).

Yep! That does it! :-) Thank you.

christocracy commented 1 year ago

Is this issue solved?

jasonkaruza commented 1 year ago

Is this issue solved?

No, it's not. I upgraded flutter to 3.3.5 to test if the pragma addition and moving my handler above main() fixed running in the background, but it's still producing the same error as soon as the headless process kicks in:

D/FlutterGeolocator(30004): Creating service. D/FlutterGeolocator(30004): Binding to location service. E/flutter (30004): [ERROR:flutter/shell/common/shell.cc(93)] Dart Error: Dart_LookupLibrary: library 'package:flutter_background_geolocation/flutter_background_geolocation.dart' not found. E/flutter (30004): [ERROR:flutter/runtime/dart_isolate.cc(668)] Could not resolve main entrypoint function. E/flutter (30004): [ERROR:flutter/runtime/dart_isolate.cc(167)] Could not run the run main Dart entrypoint. E/flutter (30004): [ERROR:flutter/runtime/runtime_controller.cc(385)] Could not create root isolate. E/flutter (30004): [ERROR:flutter/shell/common/shell.cc(604)] Could not launch engine with configuration.

christocracy commented 1 year ago

I have no idea what could cause flutter to fail loading a dependency from pubspec.yaml

Noblesoft commented 1 year ago

The plugin also works in profile mode and does not work in release mode, i tried all the methods and it didn't work, but I found that someone talked about Remove --fast -start ,so what does that mean please? it's working with firebase withe same issue (Dart Error: Dart_LookupLibrary).

christocracy commented 1 year ago

The plugin is not responsible for being found by Dart_LookupLibrary. I have no idea what could possibly be wrong, but the issue is certainly within your own environment.

vidklopcic commented 1 year ago

Can confirm that headlessMode is indeed broken since Flutter v3.x.x. If I add @pragma('vm:entry-point') above the BackgroundGeolocation class inside the background_geolocator.dart, this particular error (Dart Error: Dart_LookupLibrary: library) is resolved, but then I get:

E/flutter (25032): [ERROR:flutter/runtime/dart_isolate.cc(668)] Could not resolve main entrypoint function.
E/flutter (25032): [ERROR:flutter/runtime/dart_isolate.cc(168)] Could not run the run main Dart entrypoint.
E/flutter (25032): [ERROR:flutter/runtime/runtime_controller.cc(390)] Could not create root isolate.
E/flutter (25032): [ERROR:flutter/shell/common/shell.cc(606)] Could not launch engine with configuration.

Possibly related to 1 and 2.

Please let us know how to proceed as I need this in production as soon as possible!

christocracy commented 1 year ago

Create for me a simple HelloWorld app which reproduces the issue. Post it to a public Github repo and share that repo here with me.

vidklopcic commented 1 year ago

It turned out that if I add @pragma('vm:entry-point') above the _headlessCallbackDispatcher in background_geolocator.dart everything started working. It has to do with new tree shaking (as also mentioned here as well).

You can easily reproduce the issue by running a release build with the latest flutter from stable channel (3.3.5 - d9111f6402). If you'd like I can still create a simple HelloWorld app, but I'm almost 100% sure that's the case, as it started working after annotating everything that is called from native code with @pragma('vm:entry-point').

Noblesoft commented 1 year ago

It also worked with background_fetch.dart Thank you so much for the solution.

christocracy commented 1 year ago

I have produced this. I will release a point-release soon for both background_fetch and background_geolocation.

christocracy commented 1 year ago

Fix is released:

jasonkaruza commented 1 year ago

Confirmed fixed with flutter 3.3.6. Thanks! ❤️