How to make Flutter Workmanager plugin and Location plugin work together

9,651

Solution 1

I haven't tried what background location tracking but considering you only want location tracking from background then I think workmanager + geolocater would work. You should see this project ha_client it's using workmanager and geolocater together to get the coords

Also the location package you're using has it's own background location tracking which is in experimental Background Location Updates.

Solution 2

With these two plugins, you could schedule a one-off task and pass the current location into it.

be.tramckrijte.workmanager has limitations (it even states that not everything is being supported). And combining these two plugins through Dart might lead nowhere, because the application is not always running. Even if there is a simple_callback_dispatcher_registration.dart, which shows how it works, this will call back to the Flutter engine and not query the other plugin.

Writing & scheduling a location-aware ListenableWorker might be the best option available to get a GPS fix while running a task - unless there is any way to query the LocationPlugin plugin through the Flutter engine, while running in the background. I mean, if the ListenableWorker is location aware, it does not need to obtain the location elsewhere. Threading in WorkManager literally states it:

ListenableWorker is the base class for Worker, CoroutineWorker, and RxWorker. It is intended for Java developers who have to interact with callback-based asynchronous APIs such as FusedLocationProviderClient ...

Therefore ListenableWorker, Worker and CoroutineWorker would be applicable classes to extend from. The equivalent for the iOS implementation would be a location-aware background service. The Flutter engine may start/stop them, provide start parameters or receive callbacks, but the code which is running in the background usually won't be Dart, since it should run independently.

Share:
9,651
Tom O
Author by

Tom O

Updated on December 17, 2022

Comments

  • Tom O
    Tom O over 1 year

    1. Problem description

    My goal is to build a Flutter app that gets periodic location updates using this workmanager plugin and using this location plugin. But I can't get the Location plugin to be loaded properly when my Workmanager callback fires. I get this error:

    MissingPluginException(No implementation found for method getLocation on channel lyokone/location)
    

    So basically, the problem is that when the Workmanager plugin tries to run dart code, it doesn't load up the Location plugin.

    2. Other resources I have researched

    I found others facing the same issue, here, here, and here.

    As far as I understand, the solution provided to these questions boils down to: create a file named CustomApplication.java, which extends FlutterApplication, and which registers your plugin(s). And then register the CustomApplication.java file inside you AndoidManifest.xml file.

    3. My code thus far

    I have tried to make a bare-minimum app that implements the features I require:

    1. I implemented Workmanager plugin (works fine)
    2. I implemented Location plugin (works fine)
    3. I attempted to combine these features (does not work)

    To see exactly what I have done at each step, please look here: https://gitlab.com/tomoerlemans/workmanager_with_location/-/commits/master. (This repository can also be used to quickly replicate the issue).

    The relevant code files are as follows:

    main.dart

    import 'package:flutter/material.dart';
    import 'package:workmanager/workmanager.dart';
    import 'package:location/location.dart';
    
    void main() {
      WidgetsFlutterBinding.ensureInitialized();
      Workmanager.initialize(callbackDispatcher, isInDebugMode: true);
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: MyHomePage(),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                RaisedButton(
                  onPressed: () {
                    Workmanager.registerPeriodicTask(
                        "1", "simpleTask");
                  },
                  child: Text("Start workmanager"),
                ),
                RaisedButton(
                  onPressed: () {
                    getLocation();
                  },
                  child: Text("Get current location"),
                ),
              ],
            ),
          ),
        );
      }
    }
    
    void callbackDispatcher() {
      Workmanager.executeTask((task, inputData) {
        print("Native called background task at ${DateTime.now().toString()}");
        getLocation();
        return Future.value(true);
      });
    }
    
    void getLocation() async {
      LocationData currentLocation;
      var location = new Location();
      try {
        currentLocation = await location.getLocation();
      } on Exception catch (e) {
        print("Error obtaining location: $e");
        currentLocation = null;
      }
      print("Location altitude: ${currentLocation.altitude}");
      print("Location longitude: ${currentLocation.longitude}");
    }
    

    pubspec.yaml

    name: background_location
    description: A new Flutter project.
    
    version: 1.0.0+1
    
    environment:
      sdk: ">=2.1.0 <3.0.0"
    
    dependencies:
      flutter:
        sdk: flutter
    
      cupertino_icons: ^0.1.2
      workmanager: ^0.2.0
      location: ^2.3.5
    
    
    dev_dependencies:
      flutter_test:
        sdk: flutter
    
    flutter:
      uses-material-design: true
    

    CustomApplication.java

    package io.flutter.plugins;
    
    import io.flutter.app.FlutterApplication;
    import io.flutter.plugin.common.PluginRegistry;
    import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback;
    import io.flutter.plugins.GeneratedPluginRegistrant;
    import be.tramckrijte.workmanager.WorkmanagerPlugin;
    import com.lyokone.location.LocationPlugin;
    
    public class CustomApplication extends FlutterApplication implements PluginRegistry.PluginRegistrantCallback {
        @Override
        public void onCreate() {
            super.onCreate();
            WorkmanagerPlugin.setPluginRegistrantCallback(this);
    
        }
        @Override
        public void registerWith(PluginRegistry registry) {
            WorkmanagerPlugin.registerWith(registry.registrarFor("be.tramckrijte.workmanager.WorkmanagerPlugin"));
            LocationPlugin.registerWith(registry.registrarFor("com.lyokone.location.LocationPlugin"));
        }
    }
    

    AndroidManifest.xml

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.background_location">        
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    
        <application
            android:name="io.flutter.plugins.CustomApplication"
            android:label="background_location"
            android:icon="@mipmap/ic_launcher">
            <activity
                android:name=".MainActivity"
                android:launchMode="singleTop"
                android:theme="@style/LaunchTheme"
                android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
                android:hardwareAccelerated="true"
                android:windowSoftInputMode="adjustResize">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN"/>
                    <category android:name="android.intent.category.LAUNCHER"/>
                </intent-filter>
            </activity>
            <meta-data
                android:name="flutterEmbedding"
                android:value="2" />
        </application>
    </manifest>
    

    Finally, I am running the following versions of Dart/Flutter/etc:

    Flutter 1.12.13+hotfix.5 • channel stable • https://github.com/flutter/flutter.git
    Framework • revision 27321ebbad (10 weeks ago) • 2019-12-10 18:15:01 -0800
    Engine • revision 2994f7e1e6
    Tools • Dart 2.7.0
    
  • Tom O
    Tom O over 4 years
    "it even states that not everything is being supported" is kind of a blanket statement.. Can you tell me where it states that the feature I am looking for is impossible?
  • Martin Zeitler
    Martin Zeitler over 4 years
    This way of thinking is kinda absurd; this is just alike pretending some imaginary entity would exist, only because one cannot proof it does not exist - the WorkManager documentation is pretty clear, like it or not. And you probably not fully comprehend what Flutter actually is, if assuming that the abstraction would be responsible for whatever native or JVM implementation (else there wouldn't be an android and an ios directory)... all this plugin does is scheduling worker tasks by their name and eventually receive a callback.
  • Martin Zeitler
    Martin Zeitler over 4 years
    A one-off task is no problem, because the abstraction-layer can directly pass lat/lng - but with a periodic task, you might in best case have the lat/lng of the moment when the worker task had been scheduled. And anybody experienced with WorkManager might suggest a location-aware Worker - the only alternative might be flutter_geofire, which eventually also has limitations, while the application is not running.
  • Tom O
    Tom O over 4 years
    One of the main contributors to the workmanager plugin repo, actually talks about running flutter plugins from the workmanager callback: github.com/vrtdev/flutter_workmanager/issues/6. I referenced this in my original question.