We are trying to invoke Flutter code from an Android home screen widget in Kotlin, using the pigeon package. The widget is supposed to invoke Flutter code from a screen touch implemented by a PendingIntent
in the AppWidgetProvider
class which should call the Flutter code through the pigeon-generated Kotlin/Flutter bridge.
This works fine as long as the Intent
spawning the call is in the MainActivity
activity. The caveat is that this also activates the app to the foreground, which we do not want.
When we instead try to use a Service
as the Intent
, the pigeon bridge in Kotlin is kicked off correctly but the Flutter code is not invoked. The following code snippets show the Service
-setup.
Is a foreground/background issue? Are we missing something here? What is required to get this working without app popping into the foreground?
Pigeon spec:
import 'package:pigeon/pigeon.dart';
@FlutterApi()
abstract class MessageFlutterApi {
String flutterMethod(String operation, String? param);
}
Flutter code:
Class instantiated at main.dart level
class WidgetCallback extends interaction.MessageFlutterApi {
WidgetCallback._internal();
static final WidgetCallback _callback = WidgetCallback._internal();
factory WidgetCallback() {
interaction.MessageFlutterApi.setUp(_callback);
return _callback;
}
@override
String flutterMethod(String operation, String? param) {
return "Received $operation ($param)";
}
}
Kotlin – Service:
Logically we want to use a service (non-activity) as the Intent
. In our scenario, onStartCommand
is called successfully, the generated flutterMehod
in Kotlin is executed, but corresponding method at Flutter end is not invoked
class InteractionService:Service() {
private lateinit var flutterEngine: FlutterEngine
var caller: MessageFlutterApi? = null
override fun onCreate() {
super.onCreate()
flutterEngine = FlutterEngine(this)
caller = MessageFlutterApi(flutterEngine.dartExecutor.binaryMessenger)
FlutterEngineCache.getInstance().put("my_engine_id", flutterEngine)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
intent?.action?.let { action -> caller?.flutterMethod(action, "", {}) }
return START_STICKY
}
override fun onDestroy() {
super.onDestroy()
FlutterEngineCache.getInstance().remove("my_engine_id")
}
}
Kotlin – AppWidgetProvider:
Intent
set to InteractionService
, and PendingIntent
to corresponding service
class TinyTempWidget : AppWidgetProvider(){
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
super.onUpdate(context, appWidgetManager, appWidgetIds)
val flags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
for (appWidgetId in appWidgetIds) {
val widgetData = HomeWidgetPlugin.getData(context)
val intent = Intent(context, InteractionService::class.java)
val pendingIntentUpdate = PendingIntent.getService(context,0,intent.setAction("ACTION_WIDGET_UPDATE"),flags)
val thisView = RemoteViews(context.packageName, R.layout.tiny_temp_widget_1x1).apply {
setOnClickPendingIntent(R.id.updated, pendingIntentUpdate)
}
val views = RemoteViews(thisView)
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}
AndroidManifest.xml:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<activity
android:name=".MainActivity"
android:launchMode="singleInstance"
</activity>
<service android:name=".InteractionService"
android:exported="false"
android:stopWithTask="false" />
pubspec.yaml:
dev_dependencies:
pigeon: ^20.0.2