Closed Skyost closed 4 years ago
Have you Upgraded your pre 1.12 Android Project?
In fact I've created this project using flutter v1.12. I'm gonna check your instructions just to be sure.
EDIT : Wow, it seems I forgot to do that step, is it related to my problem ?
Show me where you’re creating this method channel on the native side
In my main activity :
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler{
call, result -> handleMethod(call, result)
}
}
private fun handleMethod(call: MethodCall, result: MethodChannel.Result) {
when(call.method) {
"method" -> {
// Do things...
result.success(true)
return
}
else -> result.notImplemented()
}
}
So I tried to add the part I forgot in my Application class but I got a compile error saying "Application.kt: (16, 48): Type mismatch: inferred type is PluginRegistry but FlutterEngine was expected"
Here's my Application.kt :
class Application : FlutterApplication(), PluginRegistry.PluginRegistrantCallback {
override fun onCreate() {
super.onCreate()
BackgroundFetchPlugin.setPluginRegistrant(this)
}
override fun registerWith(registry: PluginRegistry) {
GeneratedPluginRegistrant.registerWith(registry) // <-- Line 16
}
}
Oh and I added in my AndroidManifest.xml
:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="fr.skyost.timetable">
<uses-permission
android:name="android.permission.AUTHENTICATE_ACCOUNTS"
android:maxSdkVersion="22" />
<uses-permission
android:name="android.permission.GET_ACCOUNTS"
android:maxSdkVersion="22" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission
android:name="android.permission.MANAGE_ACCOUNTS"
android:maxSdkVersion="22" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
tools:replace="android:label"
android:name=".Application"
android:label="@string/app_name"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher">
<!-- ... -->
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
"headless" means there is no MainActivity
. MainActivity
IS the "head" of your application.
You must create your method-channel in the Application.kt
.
Another name for "headless" could be "activity-less"
Oh that's okay thanks for your answer ! I will definitely do that.
And please can you tell me why I have a compile error saying "Application.kt: (16, 48): Type mismatch: inferred type is PluginRegistry but FlutterEngine was expected" ?
Where did you get that code for your Application.kt, it’s wrong. That’s pre-1.12 code
Yes, well then read that doc again and ask yourself “what version of Flutter sdk am I using?”.
The plugin no longer requires Application extension for anything.
If you want to create a custom Application class, consult with Fluttet sdk docs, not here.
Alright.
I closed this issue as this is not a bug but the intended behavior but how can I create my method channel in my Application.kt
as I need to have a binary messenger (which is provided by the flutter engine in configureFlutterEngine
) ?
EDIT : I think I need to create a FlutterNativeView
by myself and use it to communicate with my Dart code.
So I've done it this way in my Application.kt
:
class Application : FlutterApplication() {
companion object {
const val CHANNEL = "my_channel"
fun handleMethod(call: MethodCall, result: MethodChannel.Result, context: Context) {
when(call.method) {
"method" -> {
// Do things...
result.success(true)
}
else -> result.notImplemented()
}
}
}
override fun onCreate() {
super.onCreate()
FlutterMain.ensureInitializationComplete(this, null)
val nativeView = FlutterNativeView(this, true)
MethodChannel(nativeView.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler{
call, result -> handleMethod(call, result, this)
}
}
}
And I've removed my method channel implementation in my MainActivity
. Now I have errors in my UI thread.
EDIT 2 :
My second attempt, Application.kt
:
class Application : FlutterApplication(), PluginRegistry.PluginRegistrantCallback {
companion object {
const val CHANNEL = "my_channel"
fun handleMethod(call: MethodCall, result: MethodChannel.Result, context: Context) {
when(call.method) {
"method" -> {
// Do things...
result.success(true)
}
else -> result.notImplemented()
}
}
}
override fun onCreate() {
super.onCreate()
BackgroundFetchPlugin.setPluginRegistrant(this)
}
override fun registerWith(registry: PluginRegistry) {
FlutterMain.ensureInitializationComplete(applicationContext, null)
val engine = FlutterEngine(applicationContext)
val entrypoint: DartEntrypoint = createDefault()
engine.dartExecutor.executeDartEntrypoint(entrypoint)
MethodChannel(engine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
handleMethod(call, result, this)
}
GeneratedPluginRegistrant.registerWith(engine)
}
}
And MainActivity.kt
:
class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, Application.CHANNEL).setMethodCallHandler{
call, result ->
Application.handleMethod(call, result, this)
}
}
}
Let's see if it works... Still no success : Unhandled Exception: MissingPluginException(No implementation found for method method on channel my_channel)
.
You can see an example of a suitable FlutterApplication
extension in the /example app.
Where a Context
instance is required within FlutterApplication
, you can use this
within onCreate
or getApplicationContext()
.
Thank you @christocracy, will check it out soon !
I know for getApplicationContext()
or this
, I'm currently using getApplicationContext()
.
Just checked the example FlutterApplication
. So, I see that you're not implementing PluginRegistrantCallback
at all so I've removed it. My onCreate
method now looks like this :
override fun onCreate() {
super.onCreate()
FlutterMain.ensureInitializationComplete(applicationContext, null)
val engine = FlutterEngine(applicationContext)
val entrypoint: DartEntrypoint = createDefault()
engine.dartExecutor.executeDartEntrypoint(entrypoint)
GeneratedPluginRegistrant.registerWith(engine)
MethodChannel(engine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
handleMethod(call, result, this)
}
(Well, the only thing I did was putting everything that was in the registerWith
method back in the onCreate
method). I get the following error :
══╡ EXCEPTION CAUGHT BY SERVICES LIBRARY ╞══════════════════════════════════════════════════════════
The following MissingPluginException was thrown while activating platform stream on channel
com.transistorsoft/flutter_background_fetch/events:
MissingPluginException(No implementation found for method listen on channel
com.transistorsoft/flutter_background_fetch/events)
When the exception was thrown, this was the stack:
#0 MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart:319:7)
<asynchronous suspension>
#1 EventChannel.receiveBroadcastStream.<anonymous closure> (package:flutter/src/services/platform_channel.dart:517:29)
#3 EventChannel.receiveBroadcastStream.<anonymous closure> (package:flutter/src/services/platform_channel.dart:503:64)
#8 BackgroundFetch.configure (package:background_fetch/background_fetch.dart:242:20)
EDIT : Okay, let's try another approach. I will try to use a different channel name for headless tasks. Now headless tasks are using the my_channel.headless
channel and non-headless tasks are using the my_channel
channel. So the onCreate
method in Application.kt
looks like :
override fun onCreate() {
super.onCreate()
FlutterMain.ensureInitializationComplete(this, null)
val engine = FlutterEngine(this)
MethodChannel(engine.dartExecutor.binaryMessenger, "$CHANNEL.headless").setMethodCallHandler { call, result -> handleMethod(call, result, this) }
}
And the MainActivity.kt
configureFlutterEngine
method looks like :
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, Application.CHANNEL).setMethodCallHandler{ call, result -> Application.handleMethod(call, result, this) }
}
Nop : Unhandled Exception: MissingPluginException(No implementation found for method method on channel my_channel)
I think I'm gonna do all my operations outside my register function as you did here.
Your code above isn't going to work. It's far more tricky to create a custom MethodChannel
in the headless context. It's easy in the foreground case, in the context of the MainActivity
.
However, I did some experimenting. I can offer an event-handler on BackgroundFetch
's HeadlessTask
class to offer you a reference to its FlutterEngine
. In the Application#onCreate
, you'll do something like this:
NOTE: This onInitialized
event handler isn't yet public. I have it working on my local copy only.
HeadlessTask.onInitialized(new HeadlessTask.InitializedCallback() {
@Override
public void onStarted(FlutterEngine engine) {
Log.d("TSBackgroundFetch", "********* engine started: " + engine);
new MethodChannel(engine.getDartExecutor().getBinaryMessenger(), "channel_foo").setMethodCallHandler(new MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
Log.d("TSBackgroundFetch", "**************** Application method call handler: " + call.method);
result.success(true);
}
});
}
});
main.dart:
/// Create a custom MethodChannel
const MethodChannel _methodChannel = const MethodChannel("channel_foo");
/// This "Headless Task" is run when app is terminated.
void backgroundFetchHeadlessTask(String taskId) async {
print("[BackgroundFetch] Headless event received: $taskId");
// execute a method on custom EventChannel
_methodChannel.invokeMethod('test');
}
02-01 16:01:18.381 29525 29525 D TSBackgroundFetch: *********************** MainApplication
02-01 16:01:18.392 29525 29525 D TSBackgroundFetch: - Background Fetch event received
02-01 16:01:18.427 29525 29525 D TSBackgroundFetch: - finish: com.transistorsoft.fetch
02-01 16:01:18.427 29525 29525 D TSBackgroundFetch: - jobFinished
02-01 16:01:18.441 29525 29525 D TSBackgroundFetch: HeadlessJobService onStartJob: com.transistorsoft.fetch
02-01 16:01:18.441 29525 29525 D TSBackgroundFetch: 💀 [HeadlessTask com.transistorsoft.fetch]
02-01 16:01:18.720 29525 29525 D TSBackgroundFetch: [HeadlessTask] waiting for client to initialize
02-01 16:01:18.943 29525 29577 I flutter : Observatory listening on http://127.0.0.1:40081/OUS9S3jVjvo=/
02-01 16:01:19.694 29525 29525 I TSBackgroundFetch: $ initialized
02-01 16:01:19.695 29525 29525 D TSBackgroundFetch: ********* engine started: io.flutter.embedding.engine.FlutterEngine@f639884
02-01 16:01:19.753 29525 29563 I flutter : [BackgroundFetch] Headless event received: com.transistorsoft.fetch
02-01 16:01:19.767 29525 29525 D TSBackgroundFetch: **************** Application method call handler: test
Btw, why do you even need to write a custom MethodChannel
? What are you doing on the native side that you can't do via some 3rd-party plugin?
However, I did some experimenting. I can offer an event-handler on
BackgroundFetch
'sHeadlessTask
class to offer you a reference to itsFlutterEngine
. In theApplication#onCreate
, you'll do something like this:
Wow great ! I will wait for you to release it then.
Btw, why do you even need to write a custom
MethodChannel
? What are you doing on the native side that you can't do via some 3rd-party plugin?
In fact I need to access a device account. I need to manage it (create, update and remove it from my app) and with background_fetch
, I get the account (username / password) then I synchronize it with my server.
And I think there is no plugin that can do that so far.
Thanks for taking time to help me @christocracy 🎉
I've been running into the same problem, and coincidentally I noticed that the fix is in development. In my initial testing, it seems to be working well. 🙂
@pento Out of curiosity, are you using background_fetch
as a mean to synchronize your data at a given interval ? If so, is it working well for you ?
I have a large refactor coming in branch BGTaskScheduler
that’s almost ready for release.
In addition to migrating deprecated iOS fetch api to new iOS 13 BGTaskScheduler, This version introduces a new method #scheduleTask for running arbitrary oneshot/ periodic tasks.
BacgroundGeolocationConfig also introduces new Android-only option #forceAlarmManager to bypass JobScheduler (which is optimized to conserve battery) for more precise minimumFetchInterval.
@Skyost If you want to try the branch BGTaskScheduler
, you can implement this in your MainApplication
(I use Java, you'll have to convert to Kotlin), modifying the method-channel name "channel_foo" as desired.
dependencies:
background_fetch:
git:
url: https://github.com/transistorsoft/flutter_background_fetch
ref: BGTaskScheduler
public class Application extends FlutterApplication {
@Override
public void onCreate() {
super.onCreate();
HeadlessTask.onInitialized(new HeadlessTask.OnInitializedCallback() {
@Override
public void onInitialized(FlutterEngine engine) {
new MethodChannel(engine.getDartExecutor().getBinaryMessenger(), "channel_foo").setMethodCallHandler(new MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
result.success(true);
}
});
}
});
}
}
@Skyost If you try BGTaskScheduler
branch, you'll also want to read the Breaking Changes in CHANGELOG
@christocracy Thank you so much ! But as I am not in a hurry, I think I'm gonna wait for a stable release. Your help and your code snippets are greatly appreciated tho.
@Skyost I'd really appreciate some feedback on implementing this branch. If it works for you, it's pretty much ready to go as-is.
@christocracy Oh that's okay, no problem. I'm gonna add it in my project and I will give you some feedback at the end of the week (something like thursday or friday). But I can only test on Android, not on iOS.
Out of curiosity, are you using
background_fetch
as a mean to synchronize your data at a given interval ? If so, is it working well for you ?
Yah, my use case is to fetch data from an API every 30 minutes, in a background_fetch
headless task. It then has to do a MethodChannel.invokeMethod()
call, to update an Android home screen widget. I was getting the same exception as you described earlier when the app is closed.
I'm running into a separate issue with getting the current location in the headless task, it appears to be a known issue in the geolocator
package. (It's only a hobby project which may generate revenue some time in the future, so I may just have to live with the bugs until I can replace my hacky workarounds with @christocracy's excellent flutter_background_geolocation
package. 🙂)
@christocracy
Just tested and it works perfectly on Android, thank you ! I still have a little question : is it possible to use third party flutter plugins in a headless task ?
EDIT : Just tested and it works perfectly. Thank you again !
shared_preferences
is a 3rd party plugin I use in the example, right?
@christocracy You've made a point 😁
Your Environment Windows 10 x64.
flutter info
,flutter doctor
):[√] Android toolchain - develop for Android devices (Android SDK version 28.0.3) [√] Android Studio (version 3.5) [√] Connected device (1 available)
• No issues found!
To Reproduce Steps to reproduce the behavior:
background_fetch
can register a headless task.MethodChannel.invoke
method in the task.Debug logs
Additional context:
Here's the code I'm trying to use in a headless task :
Oh and the method
method
is registered in the Android side.