I have an existing Flutter app that I use to prototype and test ideas before adding the idea to a project. I need a custom plugin to track location in the background for a project so I added plugin related code into the normal app project.
I am targeting android for a start. I have a Dart class representing the plugin that creates a method a channel to communicate with the platform code. On the platform side, I have created a class that extends FlutterPlugin
However, when I run the app and Dart native code calls methods on the Android side using the method channel, I get Unhandled Exception: MissingPluginException.
Here's the code
Dart code
class GeofencePlugin {
final MethodChannel _channel =
const MethodChannel('marcel/geofencing_plugin');
Future<bool> init() async {
//callbackDispatcher is a top level function that acts as entry point for background isolate
final callback = PluginUtilities.getCallbackHandle(callbackDispatcher);
await _channel
.invokeMethod('GeofencingPlugin.initialiseService', <dynamic>[callback!.toRawHandle()]);
return true;
}
Future<bool> registerGeofence(GeofenceRegion region) async {
return true;
}
Future<bool> removeGeofence(GeofenceRegion region) async {
return true;
}
}
Android code
class GeofencingPlugin : ActivityAware, FlutterPlugin, MethodChannel.MethodCallHandler {
private var mContext : Context? = null
private var mActivity : Activity? = null
private val geofencePendingIntent: PendingIntent by lazy {
val intent = Intent(mContext, GeofenceBroadcastReceiver::class.java)
PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
companion object {
#JvmStatic
private val TAG = "MARK_TAG"
#JvmStatic
val SHARED_PREFERENCES_KEY = "com.example.flutter_playground.geofencing"
#JvmStatic
val CALLBACK_DISPATCHER_HANDLE_KEY = "callback_dispatch_handler"
#JvmStatic
private fun initialiseService(context: Context, args: ArrayList<*>?) {
val callbackHandle = args!![0] as Long
context.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)
.edit()
.putLong(CALLBACK_DISPATCHER_HANDLE_KEY, callbackHandle)
.commit()
}
}
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
mContext = binding.applicationContext
val channel = MethodChannel(binding.binaryMessenger, "marcel/geofencing_plugin")
channel.setMethodCallHandler(this)
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
mContext = null
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
mActivity = binding.activity
}
override fun onDetachedFromActivity() {
mActivity = null
}
override fun onDetachedFromActivityForConfigChanges() {
mActivity = null
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
mActivity = binding.activity
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
val args = call.arguments<ArrayList<*>>()
when(call.method) {
"GeofencingPlugin.initialiseService" -> {
initialiseService(mContext!!, args)
setupGeo()
result.success(true)
}
else -> result.notImplemented()
}
}
private fun setupGeo(){
val geofencingClient = mContext!!.getGeofenceClient()
val fence = Geofence.Builder()
.setRequestId("Mark")
.setCircularRegion(46.5422,14.4011,500f)
.setExpirationDuration(600000)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT)
.build()
val request = GeofencingRequest.Builder().apply {
setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_DWELL)
addGeofences(listOf(fence))
}.build()
geofencingClient.addGeofences(request, geofencePendingIntent)?.run {
addOnSuccessListener {
Toast.makeText(mContext,"Hahaha", Toast.LENGTH_LONG).show()
}
addOnFailureListener {
Log.d(TAG,it.message?:"Eoo")
Toast.makeText(mContext,"Heeerrrh", Toast.LENGTH_LONG).show()
}
}
}
}
I tried the following
Manually adding the android plugin class to the GeneratedPluginRegistrant.java file
Using flutterEngine.plugins.add('my plugin') in the MainActivity's onCreate method.
After digging into the pubsec.yaml of other plugins, I found out in order to have my app's plugin recognised by flutter I have to add the following to my pubsec.yaml under the flutter section:
plugin:
platforms:
android:
package: com.example.flutter_playground.geofencing
pluginClass: GeofencingPlugin
Related
I am trying to turn on a screen once an alarm fires. I am using this
[package][1] for the alarm. However, after time doing research I have not come any closer to achieving waking the screen on alarm firing.
The solutions either use deprecated APIs or are only specific to code within some contexts like activities and fragments, which is not the case for a flutter plugin.
Here is what I have tried
-Implementing ActivityAware interface but the onAttachedToActivity() never seems to fire
-Changing activity settings of my main activity in android manifes
android:showWhenLocked="true"
android:turnScreenOn="true"
I am currently trying to get an activity within the plugin to add flags to it to turn the screen on
if (Build.VERSION.SDK_INT < 27)
activity!!.window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
else
activity!!.setTurnScreenOn(true)
The problem is the activity variable is always null because for some reason, onAttachedToActivity doesn't run.
I have created an activity within the native code but I don't know how to get a reference to that activity and use it to turn the flags on. Remember, my screen is turned off so this is possibly what is complicating the matter.
Here is where I create the activity:
private fun startMainActivity(context: Context) {
val pm = context.packageManager
val intent = pm.getLaunchIntentForPackage(context.packageName)
context.startActivity(intent)
turnScreenON()
}
How can I create this plugin to wake the screen when an alarm fires?
Here is my code for the android kotlin side:
package com.example.wake_screen
import android.app.Activity
import android.content.Context
import android.os.Build
import android.util.Log
import android.view.WindowManager
import androidx.annotation.NonNull
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
/** WakeScreenPlugin */
class WakeScreenPlugin: ActivityAware, FlutterPlugin, MethodCallHandler{
private lateinit var channel : MethodChannel
private lateinit var mContext: Context
private var activity: Activity? = null
override fun onAttachedToEngine(#NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "wake_screen")
channel.setMethodCallHandler(this)
mContext = flutterPluginBinding.applicationContext
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity
Log.d("We are attached",binding.lifecycle.toString())
}
override fun onDetachedFromActivity() {
//release resources
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
activity = binding.activity
}
override fun onDetachedFromActivityForConfigChanges() {
//release resources
}
override fun onMethodCall(#NonNull call: MethodCall, #NonNull result: Result) {
if (call.method == "getPlatformVersion") {
startMainActivity(mContext)
result.success("Android ${android.os.Build.VERSION.RELEASE}")
}
else {
result.notImplemented()
}
}
private fun turnScreenON(){
if (Build.VERSION.SDK_INT < 27)
activity!!.window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
else
activity!!.setTurnScreenOn(true)
}
private fun startMainActivity(context: Context) {
val pm = context.packageManager
val intent = pm.getLaunchIntentForPackage(context.packageName)
context.startActivity(intent)
turnScreenON()
}
override fun onDetachedFromEngine(#NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
}
Here's my code on the flutter side:
//Provide instruction to wake device after duration
void setAlarm() async {
await AndroidAlarmManager.oneShot(
const Duration(seconds: _duration),
0,
wakeTheScreen,
wakeup: true,
allowWhileIdle: true,
);
}
//Function invoking platform code
static Future<void> wakeTheScreen() async {
String? version = await WakeScreen().getPlatformVersion();
print(version);
}
In summary, How do I wake the screen up using a flutter plugin that needs an activity to work
[1]: https://pub.dev/packages/android_alarm_manager_plus
I created Flutter plugin. But I also need to add more channels. So I created both dart code and the native implementation. The problem is that the dart code cannot find the native implementation, so it throw out error with the message "Unhandled exception: MissingPluginException (No implementation found for method ...)"
I compared my native implementation with the boilerplate implementation, I don't see anything wrong. I have the correct channel name. I have registered the channel. My channel name is certainly different from the builderplate implementation's channel name. But it matches the one in dart code.
I wonder if I need to have all my native implementations to have the same channel name even though they represent different functionality blocks?
You can create multiple MethodChannels for your plugin and assign an individual MethodCallHandler to each MethodChannel. That way you can handle two methods with the same name differently if they are in different channels.
Here is how I've changed the Flutter plugin template to support multiple MethodChannels.
P.S.: I've used the Flutter 2.10.5 plugin template because it's a bit easier to understand. In Flutter 3 they've added an interface on the Dart side.
Dart:
class ExamplePlugin {
static const MethodChannel _channel = MethodChannel('example_plugin');
static const MethodChannel _channel2 = MethodChannel('example_plugin2');
static Future<String?> get platformVersion async {
final String? version = await _channel.invokeMethod('getPlatformVersion');
return version;
}
static Future<String?> get helloWorld async {
final String? helloWorld = await _channel2.invokeMethod('getHelloWorld');
return helloWorld;
}
}
Kotlin:
class ExamplePlugin: FlutterPlugin {
private lateinit var channel : MethodChannel
private lateinit var channel2 : MethodChannel
private val firstMethodCallHandler = FirstMethodCallHandler()
private val secondMethodCallHandler = SecondMethodCallHandler()
override fun onAttachedToEngine(#NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "example_plugin")
channel.setMethodCallHandler(firstMethodCallHandler)
channel2 = MethodChannel(flutterPluginBinding.binaryMessenger, "example_plugin2")
channel2.setMethodCallHandler(secondMethodCallHandler)
}
override fun onDetachedFromEngine(#NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
channel2.setMethodCallHandler(null)
}
private inner class FirstMethodCallHandler: MethodCallHandler {
override fun onMethodCall(call: MethodCall, result: Result) {
if (call.method == "getPlatformVersion") {
result.success("Android ${android.os.Build.VERSION.RELEASE}")
} else {
result.notImplemented()
}
}
}
private inner class SecondMethodCallHandler: MethodCallHandler {
override fun onMethodCall(call: MethodCall, result: Result) {
if (call.method == "getHelloWorld") {
result.success("Hello World!")
} else {
result.notImplemented()
}
}
}
}
Swift:
public class SwiftExamplePlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "example_plugin", binaryMessenger: registrar.messenger())
let channel2 = FlutterMethodChannel(name: "example_plugin2", binaryMessenger: registrar.messenger())
channel.setMethodCallHandler(firstMethodCallHandler)
channel2.setMethodCallHandler(secondMethodCallHandler)
}
static public func firstMethodCallHandler(_ call: FlutterMethodCall, result: #escaping FlutterResult) {
result("iOS " + UIDevice.current.systemVersion)
}
static public func secondMethodCallHandler(_ call: FlutterMethodCall, result: #escaping FlutterResult) {
result("Hello World!")
}
}
Complete source code: https://github.com/ColinSchmale/example_plugin
My MainActivity.kt:
class MainActivity : FlutterActivity() {
private val CHANNEL = "flutter.native/helper"
private var mapId: Int? = null
override fun onCreate(savedInstanceState: Bundle?, PersistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, PersistentState)
GeneratedPluginRegistrant.registerWith(FlutterEngine(this));
}
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
MethodChannel(flutterEngine.dartExecutor, CHANNEL).setMethodCallHandler { call, result ->
if (call.method == "KML") {
result.success(getKMLResource());
} else {
result.notImplemented();
}
}
}
private fun getKMLResource(): Int {
return R.raw.borders;
}
}
I'm trying to insert the below call, but I get a errors every time.
override fun onMethodCall(call: MethodCall, result: Result) {
when (call.method) {
"showToast" -> {
val text = call.argument<String>("text") // hello world
showToast(text)
}
}
}
Errors:
'onMethodCall' overrides nothing
One type argument expected for class Result<out T>
Unresolved reference: showToast
Here's my flutter portion:
Future<void> printSomething(GoogleMapController mapController) async {
const MethodChannel channel = MethodChannel('flutter.native/helper');
channel.invokeMethod('showToast', {'text': 'hello world'});
}
I have no idea how to incorporate this into my kotlin code properly, and there aren't many examples out there. At least none that I could find. My ultimate goal is to be able to manipulate private fun getKMLResource() and set return as a directory I select from my flutter app.
Anybody know how to do this? Please help. Thank you.
Here's my add kml flutter function:
Here's how I select my .kml:
Future<void> addKml(GoogleMapController mapController) async {
const MethodChannel channel = MethodChannel('flutter.native/helper');
try {
int kmlResourceId = await channel.invokeMethod('KML');
return mapController.channel.invokeMethod("map#addKML", <String, dynamic>{
'resourceId': kmlResourceId,
});
} on PlatformException catch (e) {
throw 'Unable to plot map: ${e.message}';
}
}
First of all, you gotta follow these documentations:
Writing custom platform-specific code
Supporting the new Android plugins APIs
Error:
'onMethodCall' overrides nothing
Means that you are trying override something that does not exist
In this situation, you have to implement missing classes that are found in the docs tutorial:
ActivityAware, FlutterPlugin, MethodChannel.MethodCallHandler
On:
MethodChannel.MethodCallHandler
You can override (Kotlin snippet):
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result)
That should fix your problem.
ActivityAware and FlutterPlugin are essentials for the new flutter upgrades and you can efficiently fix memory leaks with them.
class BubbleOverlayPlugin : ActivityAware, FlutterPlugin, MethodChannel.MethodCallHandler {
private var activity: Activity? = null
private var channel: MethodChannel? = null
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
//here lies the platform methods calls
}
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel?.setMethodCallHandler(null)
//release resources
}
override fun onAttachedToEngine(#NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, channelName)
channel?.setMethodCallHandler(this)
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity
}
override fun onDetachedFromActivity() {
//release resources
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
activity = binding.activity
}
override fun onDetachedFromActivityForConfigChanges() {
//release resources
}
}
You can check my source code from the plugin bubble_overlay, It is using the last docs recommendations.
Repo: bubble_overlay repo
Basically I'm invoking a payment sdk in native android(kotlin) using platform channel. SDK is initialised successfully. After payment transaction I will receive the payment status in a kotlin file which actually extends sdk class files (not an activity). From this callback method I need to pass the status to the flutter dart code. Everything runs successfully without any error but while passing the payment status flutter method is not invoked.
Flutter dart code.
#override
void initState() {
// TODO: implement initState
super.initState();
platform.setMethodCallHandler(_handlePaymentResponse); // To call from native android after payment response.
}
// Invoked from Native Android after payment response.
Future<dynamic> _handlePaymentResponse(MethodCall call) async {
print('_handlePaymentResponse method called');
switch(call.method) {
case "message":
debugPrint('From Native====' + call.arguments);
return new Future.value("");
}
}
// On button click this method will be invoked.
Future<void> _initializeSDK() async {
print('Token $token');
try {
await platform.invokeMethod('callPaymentSDK');
} on PlatformException catch (e) {
print("Failed to initialize SDK : '${e.message}'.");
}
}
Native Android : Paymentstatus.kt
class PaymentResponse() : LibraryPaymentStatusProtocol, Parcelable {
//val channel = MethodChannel(flutterView, MainActivity.CHANNEL)
var backgroundFlutterView: FlutterNativeView? = null
override fun paymentStatus(p0: String?, p1: Activity?) {
if (p1 != null) {
Handler(Looper.getMainLooper()).post {
Log.v("PaymentResponse", "Main thread called")
backgroundFlutterView = FlutterNativeView(p1, true)
val channel = MethodChannel(backgroundFlutterView, MainActivity.CHANNEL)
channel.invokeMethod("message", p0); // Invoking Flutter method
}
}
}
}
I ran in to the same issue, and in the end the solution that worked for me (not sure if this is the ideal approach) was to store a reference to the FlutterEngine inside my MainActivity like so:
class MainActivity: FlutterActivity() {
companion object {
var flutterEngineInstance: FlutterEngine? = null
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
flutterEngineInstance = flutterEngine
}
}
In my code I would then just invoke a method like this:
MethodChannel(
MainActivity.flutterEngineInstance.dartExecutor.binaryMessenger,
"com.example"
).invokeMethod("method", mapOf())
For my case i didn't receive data from android native to flutter when i called channel.invokeMethod from OnCreate() in MainActivity: FlutterActivity()
so i added channel.invokeMethod( ... ) inside onStart() method, its worked.
also you can use:
runOnUiThread { channel.invokeMethod( ... ) }
or
Handler(Looper.getMainLooper()).post { channel.invokeMethod( ... ) }
I'm trying to port Zendesk Native SDK for Android in Flutter using MethodChannel and Kotlin as my language choice.
It works when I use the Kotlin code directly inside the project which is
class MainActivity : FlutterActivity() {
// others code are hidden
private fun initialize(call: MethodCall, result: Result) {
val url: String = call.argument("url")!!
val appId: String = call.argument("appId")!!
val clientId: String = call.argument("clientId")!!
Zendesk.INSTANCE.init(this, url, appId, clientId)
val identity = AnonymousIdentity()
Zendesk.INSTANCE.setIdentity(identity)
Support.INSTANCE.init(Zendesk.INSTANCE)
RequestListActivity.builder().show(this)
result.success(true)
}
}
The this is referring to Activity which I guess FlutterApplication already has inside of it, but when I try to make the standalone plugin thing is a little bit different. I need to implement ActivityAware to get the activity (Get activity reference in flutter plugin).
https://github.com/flutter/flutter/wiki/Experimental:-Create-Flutter-Plugin
(Optional) If your plugin needs an Activity reference, also implement ActivityAware.
public class ZendeskPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
private lateinit var activityBinding: ActivityPluginBinding
// I can get ActivityPluginBinding from this method
override fun onAttachedToActivity(#NonNull binding: ActivityPluginBinding) {
activityBinding = binding
}
private fun initialize(call: MethodCall, result: Result) {
activityBinding?.activity?.let {
val appId: String = call.argument("appId")!!
val clientId: String = call.argument("clientId")!!
val url: String = call.argument("url")!!
Zendesk.INSTANCE.init(it, url, appId, clientId)
val identity = AnonymousIdentity()
Zendesk.INSTANCE.setIdentity(identity)
Support.INSTANCE.init(Zendesk.INSTANCE)
RequestListActivity.builder().show(it)
result.success(true)
return
}
result.error("INITIALIZE_FAILED", "Failed to initialize", null)
}
}
I tried to call initialize from dart and actually it runs but onAttachedToActivity seems is never be invoked and makes activityBinding is never be initialized so the code fails and result.error.
How do I get activity inside FlutterPlugin class?
Thank you
Probably the problem is the call of the setMethodCallHandler in the onAttachedToEngine
Look more here