我在Flutter应用程序中添加了本地Android主屏幕小部件。
在我的AppWidgetProvider
实现中,我想使用平台通道在onUpdate()
方法中调用dart代码。
这可能吗?如果是这样,如何实现?
我当前的Android(Java)代码:
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代码:
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) {
// ...
}
}
将小部件添加到主屏幕时,我看到:
I/HomeScreenWidget(10999): onEnabled!
I/HomeScreenWidget(10999): onUpdate!
I/HomeScreenWidget(10999): channel not null, invoking dart method!
I/HomeScreenWidget(10999): after invoke dart method!
但是,我的dart代码似乎没有收到调用。
答案 0 :(得分:3)
我还需要一些本机android小部件来与我的dart代码进行通信,经过一番修补后,我设法做到了这一点。在我看来,有关如何执行此操作的文档有点稀疏,但由于有了一些创造力,我设法使它起作用。我尚未进行足够的测试以将其100%生产准备就绪,但似乎可以正常工作...
飞镖设置
转到main.dart
并添加以下顶级功能:
void initializeAndroidWidgets() {
if (Platform.isAndroid) {
// Intialize flutter
WidgetsFlutterBinding.ensureInitialized();
const MethodChannel channel = MethodChannel('com.example.app/widget');
final CallbackHandle callback = PluginUtilities.getCallbackHandle(onWidgetUpdate);
final handle = callback.toRawHandle();
channel.invokeMethod('initialize', handle);
}
}
然后在运行应用程序之前调用此功能
void main() {
initializeAndroidWidgets();
runApp(MyApp());
}
这将确保我们可以在本机端获得入口点的回调句柄。
现在添加这样的入口点:
void onWidgetUpdate() {
// Intialize flutter
WidgetsFlutterBinding.ensureInitialized();
const MethodChannel channel = MethodChannel('com.example.app/widget');
// If you use dependency injection you will need to inject
// your objects before using them.
channel.setMethodCallHandler(
(call) async {
final id = call.arguments;
print('on Dart ${call.method}!');
// Do your stuff here...
final result = Random().nextDouble();
return {
// Pass back the id of the widget so we can
// update it later
'id': id,
// Some data
'value': result,
};
},
);
}
此函数将成为窗口小部件的入口点,并在调用窗口小部件onUpdate
方法时被调用。然后,我们可以传回一些数据(例如,在调用api之后)。
Android设置
这里的示例在Kotlin中,但在Java中也应进行一些小的调整。
创建一个WidgetHelper
类,它将帮助我们存储和获取入口点的句柄:
class WidgetHelper {
companion object {
private const val WIDGET_PREFERENCES_KEY = "widget_preferences"
private const val WIDGET_HANDLE_KEY = "handle"
const val CHANNEL = "com.example.app/widget"
const val NO_HANDLE = -1L
fun setHandle(context: Context, handle: Long) {
context.getSharedPreferences(
WIDGET_PREFERENCES_KEY,
Context.MODE_PRIVATE
).edit().apply {
putLong(WIDGET_HANDLE_KEY, handle)
apply()
}
}
fun getRawHandle(context: Context): Long {
return context.getSharedPreferences(
WIDGET_PREFERENCES_KEY,
Context.MODE_PRIVATE
).getLong(WIDGET_HANDLE_KEY, NO_HANDLE)
}
}
}
为此替换您的MainActivity
:
class MainActivity : FlutterActivity(), MethodChannel.MethodCallHandler {
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
val channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, WidgetHelper.CHANNEL)
channel.setMethodCallHandler(this)
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"initialize" -> {
if (call.arguments == null) return
WidgetHelper.setHandle(this, call.arguments as Long)
}
}
}
}
这只会确保我们将句柄(入口点的哈希值)存储到SharedPreferences
以便以后在小部件中检索它。
现在修改您的AppWidgetProvider
使其类似于以下内容:
class Foo : AppWidgetProvider(), MethodChannel.Result {
private val TAG = this::class.java.simpleName
companion object {
private var channel: MethodChannel? = null;
}
private lateinit var context: Context
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
this.context = context
initializeFlutter()
for (appWidgetId in appWidgetIds) {
updateWidget("onUpdate ${Math.random()}", appWidgetId, context)
// Pass over the id so we can update it later...
channel?.invokeMethod("update", appWidgetId, this)
}
}
private fun initializeFlutter() {
if (channel == null) {
FlutterMain.startInitialization(context)
FlutterMain.ensureInitializationComplete(context, arrayOf())
val handle = WidgetHelper.getRawHandle(context)
if (handle == WidgetHelper.NO_HANDLE) {
Log.w(TAG, "Couldn't update widget because there is no handle stored!")
return
}
val callbackInfo = FlutterCallbackInformation.lookupCallbackInformation(handle)
// You could also use a hard coded value to save you from all
// the hassle with SharedPreferences, but alas when running your
// app in release mode this would fail.
val entryPointFunctionName = callbackInfo.callbackName
// Instantiate a FlutterEngine.
val engine = FlutterEngine(context.applicationContext)
val entryPoint = DartEntrypoint(FlutterMain.findAppBundlePath(), entryPointFunctionName)
engine.dartExecutor.executeDartEntrypoint(entryPoint)
// Register Plugins when in background. When there
// is already an engine running, this will be ignored (although there will be some
// warnings in the log).
GeneratedPluginRegistrant.registerWith(engine)
channel = MethodChannel(engine.dartExecutor.binaryMessenger, WidgetHelper.CHANNEL)
}
}
override fun success(result: Any?) {
Log.d(TAG, "success $result")
val args = result as HashMap<*, *>
val id = args["id"] as Int
val value = args["value"] as Int
updateWidget("onDart $value", id, context)
}
override fun notImplemented() {
Log.d(TAG, "notImplemented")
}
override fun error(errorCode: String?, errorMessage: String?, errorDetails: Any?) {
Log.d(TAG, "onError $errorCode")
}
override fun onDisabled(context: Context?) {
super.onDisabled(context)
channel = null
}
}
internal fun updateWidget(text: String, id: Int, context: Context) {
val views = RemoteViews(context.packageName, R.layout.small_widget).apply {
setTextViewText(R.id.appwidget_text, text)
}
val manager = AppWidgetManager.getInstance(context)
manager.updateAppWidget(id, views)
}
这里重要的是initializeFlutter
,它可以确保我们可以处理入口点。然后,在onUpdate
中调用channel?.invokeMethod("update", appWidgetId, this)
,它将触发我们在MethodChannel
中较早定义的飞镖端上的回调。然后,我们稍后在success
中处理结果(至少在调用成功时)。
希望这将使您大致了解如何实现此目标...
答案 1 :(得分:0)
首先,请确保先调用FlutterMain.startInitialization()
,然后再调用FlutterMain.ensureInitializationComplete()
,然后再尝试执行任何Dart代码。这些调用是引导Flutter所必需的。
第二,您可以使用新的实验性Android嵌入尝试相同的目标吗?
以下是使用新嵌入代码执行Dart代码的指南: https://github.com/flutter/flutter/wiki/Experimental:-Reuse-FlutterEngine-across-screens
如果您的代码在新的Android嵌入中仍然无法正常工作,则应该更容易调试问题所在。请重新发送成功或任何新的错误信息。
答案 2 :(得分:0)
也许您可以使用invokeMethod(String method, @Nullable Object arguments, MethodChannel.Result callback)
并使用回调来获取失败原因。
答案 3 :(得分:0)
您需要从MainActivity传递getFlutterView()而不是创建新的BackgroundFlutterView:
channel = new MethodChannel(MainActivity.This.getFlutterView(), CHANNEL);
“此”类似于:
public class MainActivity extends FlutterActivity {
public static MainActivity This;
@Override
protected void onCreate(Bundle savedInstanceState) {
This = this;
...
}
答案 4 :(得分:0)
FlutterMain
已弃用,请使用 FlutterLoader
。
例如 (kotlin)
val loader = FlutterLoader()
loader?.startInitialization(context!!)
loader?.ensureInitializationComplete(context!!, arrayOf())
还有一点,当应用程序在后台并且您想与父应用程序通信时,您需要再次初始化方法通道,然后从 onUpdate
进行的初始初始化将不起作用。在这种情况下,flutter 部分的代码将在单独的隔离中执行。