How to make Flutter Workmanager plugin and Location plugin work together
Asked Answered
F

2

14

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
Finsen answered 17/2, 2020 at 12:25 Comment(3)
did you implement it successfully ?Seibold
Hi, did you find a solution for this?Implacental
android:name="io.flutter.plugins.CustomApplication" this is wrong with your manifest. Use proper path and if its in the root then just android:name=".CustomApplication"Headcheese
A
5

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.

Asberry answered 24/2, 2020 at 19:34 Comment(0)
F
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.

Faustino answered 19/2, 2020 at 12:43 Comment(4)
"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?Finsen
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.Faustino
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.Faustino
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.Finsen

© 2022 - 2024 — McMap. All rights reserved.