Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make Flutter Workmanager plugin and Location plugin work together

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
like image 241
Tom O Avatar asked Feb 17 '20 12:02

Tom O


1 Answers

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.

like image 69
Martin Zeitler Avatar answered Oct 27 '22 15:10

Martin Zeitler