I am working on a native Android widget in a Flutter App. In which there is refresh button, on click of that I have to call a method in the Flutter code. I am using Flutter Method Channel for the communication and it is working fine when app is in foreground. But it does not work when app is minimised or closed. I get error PlatformException(NO_ACTIVITY, null, null). Below is my code.
Android (AppWidgetProvider)
if (methodChannel == null && context != null) {
FlutterMain.startInitialization(context)
FlutterMain.ensureInitializationComplete(context, arrayOf())
// Instantiate a FlutterEngine.
val engine = FlutterEngine(context.applicationContext)
// Define a DartEntrypoint
val entrypoint: DartEntrypoint = DartEntrypoint.createDefault()
// Execute the DartEntrypoint within the FlutterEngine.
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)
methodChannel = MethodChannel(engine.dartExecutor.binaryMessenger, MainActivity.CHANNEL)
}
methodChannel!!.invokeMethod("fetchNewData", "", object : MethodChannel.Result {
override fun notImplemented() {
Toast.makeText(context, "method not implemented", Toast.LENGTH_SHORT).show()
}
override fun error(errorCode: String?, errorMessage: String?, errorDetails: Any?) {
Toast.makeText(context, errorMessage, Toast.LENGTH_SHORT).show()
}
override fun success(result: Any?) {
Toast.makeText(context, "success", Toast.LENGTH_SHORT).show()
}
})
Flutter
/// calling in main
static Future<void> attachListeners() async {
WidgetsFlutterBinding.ensureInitialized();
var bloc = new AqiCnDashboardBloc();
_channel.setMethodCallHandler((call) {
switch (call.method) {
case 'fetchNewData':
bloc.getAqiCn(false);
return null;
default:
throw MissingPluginException('notImplemented');
}
});
}
I am collecting information/discussion that redirects us to run flutter engine in background.
void callbackDispatcher() {
WidgetsFlutterBinding.ensureInitialized();
print("Our background job ran!");
}
void main() {
static const MethodChannel _channel = const MethodChannel("channel-name");
Future<void> initialize(final Function callbackDispatcher) async {
final callback = PluginUtilities.getCallbackHandle(callbackDispatcher);
await _channel.invokeMethod('initialize', callback.toRawHandle());
}
}
As stated here How to run Flutter in the background?
When a background job is started by native the Flutter engine is not active. So we are unable to run Dart.
Can Android/iOS starts the Flutter engine in the background?
Yes! We’ll first need to register a Dart callback function which will only be invoked whenever a background job is started by the native code.
This callback function is referred to as a callbackDispatcher.
Also please check out these stackoverflow discussions.
Flutter : Run an app as a background service
How to create a service in Flutter to make an app to run always in background?
How to create a Flutter background service that works also when app closed
Executing Dart in the Background with Flutter Plugins and Geofencing
You may start the Flutter Engine in the background by register a Dart callback function which will only be invoked whenever a background job is started in Flutter.
Try this.
https://medium.com/vrt-digital-studio/flutter-workmanager-81e0cfbd6f6e
Related
I want my flutter app to get a notification every time the android system back button is pressed, and furthermore, I want to otherwise disable the back button. So this is the code I have on the kotlin side of things:
package com.example.ui
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity: FlutterActivity() {
override fun onBackPressed() {
// send message to flutter that the system back button was pressed
val flutterEngine = getFlutterEngine()
if (flutterEngine != null) {
MethodChannel(flutterEngine!!.dartExecutor.binaryMessenger, "backButtonChannel").invokeMethod("backButtonPressed", null)
}
}
}
and here's my flutter app main function:
Future<void> backButtonPressed() async {
// Code to run when the back button is pressed
print('PRESSED!');
}
const platform = MethodChannel('com.example.ui/back_button');
void main() {
WidgetsFlutterBinding.ensureInitialized();
platform.setMethodCallHandler((call) async {
if (call.method == "backButtonPressed") {
return backButtonPressed();
}
});
return runApp(MyApp());
}
Unfortunately it seems to have no effect, are you able to see why?
You should make a call by using registered MethodChannel channel
Your kotlin code should be like
MethodChannel(flutterEngine!!.dartExecutor.binaryMessenger, "com.example.ui/back_button").invokeMethod("backButtonPressed", null)
I suggest to use WillPopScope widget to handle back press from dart code
I want to send data with intent from native android app to flutter app, So if flutter app is closed below code working fine to fetch intent data in main.dart file. If flutter app is in foreground and i tries to send data from native app to flutter app nothing happens. Is their anything else to need implement for this case?
Native app code to start flutter app
var intent = getPackageManager().getLaunchIntentForPackage("com.flutterapp");
intent.putString( "MapParams", jsonObj.toString())
startActivity(intent)
Flutter app code
class MainActivity: FlutterActivity()
var sharedData="";
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
GeneratedPluginRegistrant.registerWith(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger,
"share_channel").setMethodCallHandler { call, result ->
if (call.method == "MapParams") {
handleIntent()
result.success(sharedData)
sharedData = ""
}
}
}
private fun handleIntent() {
if (getIntent().hasExtra("MapParams")) {
getIntent().getSerializableExtra("MapParams")?.let { intentData ->
sharedData = intentData.toString()
}
}
}
Main.Dart file
Future<String> getSharedData() async {
return await MethodChannel('share_channel')
.invokeMethod("MapParams") ??
"";
}
I want to call a native method from my flutter app but I have an issue:
No implementation found for method getApplicationDocumentsDirectory on channel my_channel/name
I launch my Flutter app from an existing native Android Application and I want to use some native code from Flutter.
So I use an Flutter engine cache and I register my native method handler but it doesn’t work:
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
"CHANNEL"
).setMethodCallHandler { call, result ->
when (call.method) {
"myMethod" -> {
doMyNativeCode(call, result)
}
else -> {
result.notImplemented()
}
}
}
}
I call this code from Application.onCreate() when I create flutter engine and put it into cache.
But my EventChannel's (listening events from native on flutter) work well.
What's wrong with it?
Maybe my experience can help you.
My issue was exception on native side:
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
"CHANNEL"
).setMethodCallHandler { call, result ->
when (call.method) {
"myMethod" -> {
// THERE WAS EXCEPTION!!!
}
else -> {
result.notImplemented()
}
}
}
}
I have created a custom MethodChannel "com.example.app/widget" that updates a home screen widget on Android after receiving a Firebase Cloud Message. It runs fine when it is called while the app is in the foreground, but I would also like to call it when a Firebase Cloud Message is received while the app is closed or in the background.
When the app is in the background, it gives me a MissingPluginException error, like below:
E/flutter (28540): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: MissingPluginException(No implementation found for method updateFromFlutter on channel com.example.app/widget)
E/flutter (28540): #0 MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:157:7)
... and so on. There are a lot of other threads about MissingPluginException errors that deal with adding a plugin to the registry, but I haven't been able to find any that address custom MethodChannels that are not part of another plugin. Is it possible to add my custom MethodChannel to the registry or do something similar that will result in the Dart code being able to call a method from that channel while in the background?
I have tried using workmanager and android_alarm_manager and they seem to run fine themselves, but they still can't get past this block with my custom channel.
My MainActivity.kt has the method details in it:
class MainActivity: FlutterActivity(), MethodChannel.MethodCallHandler {
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
val channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.example.app/widget")
channel.setMethodCallHandler(this)
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"updateFromFlutter" -> {
val views = RemoteViews(context.packageName, R.layout.appwidget).apply {
setTextViewText(R.id.text, call.argument("text"))
}
val manager = AppWidgetManager.getInstance(this)
manager.updateAppWidget(call.argument("idNumber"), views)
}
}
}
}
Then in main.dart I call:
Future<void> updateAndroidWidget(String text) async {
WidgetsFlutterBinding.ensureInitialized();
const MethodChannel platform = MethodChannel('com.example.app/widget');
try {
platform.invokeMethod("updateFromFlutter", {
"text": text,
"idNumber": savedPreferences.androidWidgetID
});
} catch (e) {
print("failed: $e");
}
}
I already have Flutter Android Embedding V2 (Flutter Version >= 1.12).
Any help is greatly appreciated.
I have successfully integrated flutter module in my native android application by following steps here .
The process of caching flutter engine I have already done in Application class. I am launching my flutter screen with this from android fragment.
startActivity(
FlutterActivity
.withCachedEngine("my_engine_id")
.build(currentActivity)
);
Now I want to pass my auth token to flutter module for making api calls.
I am following the process from here and created method channel in dart code but I do not know where to create the method channel in the native side.
If I am creating it in project/moduleName/.android/app/src/main/java/com/package/host/MainActivity.java
It is giving exception Unhandled Exception: MissingPluginException(No implementation found for method
Also note that this folder is placed in .gitignore by default when I created this flutter module in Android studio.
I already has a look at older tutiorals but this caching of flutter engine option is not there in them.
Please tell where am I doing wrong ?
You need to have a reference for the Flutter Engine and then use it to create the Method Channel. Take in account that the activity/fragment that launch the Flutter activity is in charge to manage the Method Channel.
private const val FLUTTER_ENGINE_ID = "flutter_engine"
private const val CHANNEL = "myApp.flutter.dev/auth"
class MainActivity : AppCompatActivity(R.layout.activity_main) {
private lateinit var flutterEngine: FlutterEngine
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setupFlutterEngine()
setupMethodChannel()
setSupportActionBar(findViewById(R.id.toolbar))
fab.setOnClickListener {
launchFlutterModule()
}
}
private fun setupFlutterEngine() {
createAndConfigureFlutterEngine()
FlutterEngineCache
.getInstance()
.put(FLUTTER_ENGINE_ID, flutterEngine)
}
private fun createAndConfigureFlutterEngine() {
flutterEngine = FlutterEngine(this)
flutterEngine.dartExecutor.executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault())
}
private fun setupMethodChannel() {
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
CHANNEL
).setMethodCallHandler { call, result ->
// All your implementation for auth token
}
}
private fun launchFlutterModule() {
startActivity(getFlutterIntent())
}
private fun getFlutterIntent(): Intent {
return FlutterActivity
.withCachedEngine(FLUTTER_ENGINE_ID)
.backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent)
.build(this)
}
}
You can make a Wrapper where you can hold the method channel code and flutter engine setup, and this wrapper is init in your Application class or maybe init and injected with Dagger or Hilt where is needed.
You can pass simple data as request parameters.
Android code:
FlutterActivity
.withNewEngine()
.initialRoute("/MyPage?username=my_user&age=30&gender=male")
.build(MainActivity.this)
Flutter code, I use GetX library to get the parameters
Map<String, String?> parameters = Get.parameters;
print("parameters: $parameters");
Output:
parameters: {username: my_user, age: 30, gender: male}
First create flutter platform client with channel
set methodCallHamdler passing same channel
Refer this official doc-
https://flutter.dev/docs/development/platform-integration/platform-channels