Channel Invokemethod from native android not invoking the flutter method - flutter

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( ... ) }

Related

Unhandled Exception: MissingPluginException(No implementation found for method...)

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

What is the proper way to launch app using alarm manager?

I need to implement a flutter alarm app that opens through the alarm manager.
It seems that there is the way to do it using android_intent and android_alarm_manager_plus.
I was looking for the right way to create such functionality, but this option does not work:
await AndroidAlarmManager.oneShot(
Duration(seconds: 2), 1, setAlarm);
void setAlarm()async{
AndroidIntent intent =
AndroidIntent(action: 'action_view', package: 'com.example.test_alarm');
await intent.launch().catchError((e) {
print(e.toString());
});
}
Debug console gives this:
I/IntentSender( 4886): Cannot resolve explicit intent - ignoring package
V/IntentSender( 4886): Sending intent Intent { act=android.intent.action.VIEW (has extras) }
try using https://pub.dev/packages/app_launcher
void setAlarm() async{
await AppLauncher.openApp(
androidApplicationId: "com.example.test_alarm",
);
}
I think your code is not working because the setAlarm() function will be run from Dart Background Isolate. So you need to modify the AlarmBroadcastReceiver.java code of plugin like below.
public class AlarmBroadcastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
PowerManager powerManager = (PowerManager)
context.getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK |
PowerManager.ACQUIRE_CAUSES_WAKEUP |
PowerManager.ON_AFTER_RELEASE, "AlarmBroadcastReceiver:My wakelock");
Intent startIntent = context
.getPackageManager()
.getLaunchIntentForPackage(context.getPackageName());
if (startIntent != null)
startIntent.setFlags(
Intent.FLAG_ACTIVITY_REORDER_TO_FRONT |
Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
);
wakeLock.acquire(3 * 60 * 1000L /*3 minutes*/);
context.startActivity(startIntent);
AlarmService.enqueueAlarmProcessing(context, intent);
wakeLock.release();
}
}
If you want to launch your app on a lock screen, need to add codes at the MainActivity. Here is an example code.
class MainActivity : FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// ...
turnScreenOnAndKeyguardOff()
}
private fun turnScreenOnAndKeyguardOff() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setShowWhenLocked(true)
setTurnScreenOn(true)
window.addFlags(WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON)
} else {
window.addFlags(
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED // deprecated api 27
or WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD // deprecated api 26
or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON // deprecated api 27
or WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
)
}
val keyguardMgr = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
keyguardMgr.requestDismissKeyguard(this, null)
}
}
}

Having trouble figuring out how to place code into kotlin and use a method channel to send functions from flutter

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

Flutter: How to wait for android platform channel function to complete and return the result

Creating a platform channel on Android for File upload using AWS SDK.
Now I want to wait for the upload to complete in the background and return the status of the result.
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
if (call.method == "uploadToAWS") {
new DoUpload().execute();
// how to await here ?
result.success(true);
} else {
result.notImplemented()
}
}
Instead of waiting there, remove "result.success(true);" statement from there and simply assign "result" object to your own object of type "MethodChannel.Result".
Then use it to execute its "success", "error" or "notImplemented" method from anywhere as shown in below example.
public class Activity extends FlutterActivity {
private MethodChannel.Result myResult;
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
.setMethodCallHandler(
(call, result) -> {
myResult = result;
}
);
private void myCustomMethod {
myResult.success(yourData);
}
}
Now you can use these myResult.success(), myResult.error() methods from anywhere within class.

Native Dialog for location setting on Flutter

Is there a way to implement a Dialog for Location setting like the image below which gets triggered when app requires GPS location and doesn't find. Hitting OK will right away turn on the system GPS. This seems more convenient for users instead of taking them to location and manually turn on.
Is it possible to implement such thing in Flutter?
Expanded View of dialog:
Credits to Rajesh, as answered here. The plugin lets you add this native dialog for a quick location setting.
The implementation is quite simple as this:
import 'package:location/location.dart';
var location = Location();
Future _checkGps() async {
if(!await location.serviceEnabled()){
location.requestService();
}
}
#override
void initState() {
super.initState();
_checkGps();
}
In Kotlin, try this code:
class HomeActivity : AppCompatActivity() {
private val requestLocation = 199
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableLoc()
}
private fun enableLoc() {
val mLocationRequest = LocationRequest.create()
mLocationRequest.interval = 10000
mLocationRequest.fastestInterval = 5000
// mLocationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
val builder = LocationSettingsRequest.Builder()
.addLocationRequest(mLocationRequest)
val client = LocationServices.getSettingsClient(this)
val task =
client.checkLocationSettings(builder.build())
task.addOnSuccessListener(this) {
// All location settings are satisfied. The client can initialize
// location requests here.
// ...
}
task.addOnFailureListener(this) { e ->
if (e is ResolvableApiException) {
// Location settings are not satisfied, but this can be fixed
// by showing the user a dialog.
try {
// Show the dialog by calling startResolutionForResult(),
// and check the result in onActivityResult().
e.startResolutionForResult(
this,
requestLocation
)
} catch (sendEx: SendIntentException) {
// Ignore the error.
}
}
}
}
}