I added a native Android home screen widget to my Flutter application.
In my AppWidgetProvider
implementation, I'd like to call dart code in my onUpdate()
method using a platform channel.
Is this possible? If so, how can this be achieved?
My current Android (Java) code:
package com.westy92.checkiday;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.util.Log;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.view.FlutterNativeView;
public class HomeScreenWidget extends AppWidgetProvider {
private static final String TAG = "HomeScreenWidget";
private static final String CHANNEL = "com.westy92.checkiday/widget";
private static FlutterNativeView backgroundFlutterView = null;
private static MethodChannel channel = null;
@Override
public void onEnabled(Context context) {
Log.i(TAG, "onEnabled!");
backgroundFlutterView = new FlutterNativeView(context, true);
channel = new MethodChannel(backgroundFlutterView, CHANNEL);
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
Log.i(TAG, "onUpdate!");
if (channel != null) {
Log.i(TAG, "channel not null, invoking dart method!");
channel.invokeMethod("foo", "extraJunk");
Log.i(TAG, "after invoke dart method!");
}
}
}
Dart code:
void main() {
runApp(Checkiday());
}
class Checkiday extends StatefulWidget {
@override
_CheckidayState createState() => _CheckidayState();
}
class _CheckidayState extends State<Checkiday> {
static const MethodChannel platform = MethodChannel('com.westy92.checkiday/widget');
@override
void initState() {
super.initState();
platform.setMethodCallHandler(nativeMethodCallHandler);
}
Future<dynamic> nativeMethodCallHandler(MethodCall methodCall) async {
print('Native call!');
switch (methodCall.method) {
case 'foo':
return 'some string';
default:
// todo - throw not implemented
}
}
@override
Widget build(BuildContext context) {
// ...
}
}
When I add the widget to my home screen, I see:
I/HomeScreenWidget(10999): onEnabled!
I/HomeScreenWidget(10999): onUpdate!
I/HomeScreenWidget(10999): channel not null, invoking dart method!
I/HomeScreenWidget(10999): after invoke dart method!
However, my dart code does not seem to be receiving the invocation.
You need to pass the getFlutterView() from the MainActivity instead of creating a new BackgroundFlutterView:
"This" being like:
First, please ensure that you are invoking
FlutterMain.startInitialization()
and thenFlutterMain.ensureInitializationComplete()
before attempting to execute any Dart code. These calls are necessary to bootstrap Flutter.Second, can you try this same goal using the new experimental Android embedding?
Here is a guide for executing Dart code using the new embedding: https://github.com/flutter/flutter/wiki/Experimental:-Reuse-FlutterEngine-across-screens
If your code still doesn't work as expected with the new Android embedding then it should be easier to debug what the problem is. Please post back with success, or any new error information.
maybe you can use
invokeMethod(String method, @Nullable Object arguments, MethodChannel.Result callback)
and use callback to get the fail reason.I also needed some native android widgets to communicate with my dart code and after some tinkering I managed to do this. In my opinion the documentation on how to do this is a bit sparse but with a bit of creativity I managed to get this to work. I haven't done enough testing to call this 100% production ready, but it seems to be working...
Dart setup
Go to
main.dart
and add the following top-level function:then call this function before running your app
this will ensure that we can get a callback handle on the native side for our entry point.
Now add an entry point like so:
This function will be the entry point for our widgets and gets called when our widgets
onUpdate
method is called. We can then pass back some data (for example after calling an api).Android setup
The samples here are in Kotlin but should work with some minor adjustments also in Java.
Create a
WidgetHelper
class that will help us in storing and getting a handle to our entry point:Replace your
MainActivity
with this:This will simply ensure that we store the handle (the hash of the entry point) to
SharedPreferences
to be able to retrieve it later in the widget.Now modify your
AppWidgetProvider
to look something similar to this:The important thing here is
initializeFlutter
that will make sure we can get a handle to our entry point. InonUpdate
we are then callingchannel?.invokeMethod("update", appWidgetId, this)
that will trigger the callback in ourMethodChannel
on the dart side defined earlier. Then we handle the result later insuccess
(at least when the call is successful).Hopefully this will give you a rough idea on how to achieve this...