I tried to app to app communication on Flutter - Android.
First, Android app(A) bound Flutter service which Android Service(B).
Then A call to B and B start activity.
Second, activity invoke method on Flutter using MethodChannel.
But I faced the Exception.
How to invoke flutter method on Android activity that start with Android service.
Exception
E/flutter (12950): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: MissingPluginException(No implementation found for method getDatabasesPath on channel com.tekartik.sqflite)
E/flutter (12950): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: MissingPluginException(No implementation found for method getAll on channel plugins.flutter.io/shared_preferences)
MyService.kt
class MyService : Service() {
private lateinit var mMessenger: Messenger
internal class IncomingHandler(context: Context,
private val applicationContext: Context = context.applicationContext
) : Handler() {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
when (msg.what) {
WHAT_REQ_VP -> {
applicationContext.startActivity(Intent(applicationContext,
TempActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
}
}
}
}
override fun onBind(intent: Intent): IBinder {
mMessenger = Messenger(IncomingHandler(this))
return mMessenger.binder
}
}
TempActivity.kt
class TempActivity : FlutterActivity() {
private lateinit var channel: MethodChannel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_temp)
val backgroundFlutterView = FlutterNativeView(baseContext, true)
channel = MethodChannel(backgroundFlutterView, "com.example.test")
GeneratedPluginRegistrant.registerWith(this)
channel.setMethodCallHandler { call, result -> run {
when (call.method) {
"result" -> {
print(call.argument<String>("result"))
}
else -> {}
}
} }
findViewById<Button>(R.id.btn_confirm).setOnClickListener {
val arguments = HashMap<String, String>()
arguments["message"] = "From Android"
channel.invokeMethod("test", arguments)
}
}
}
temp.dart
import 'package:flutter/services.dart';
class Temp {
static final _singleton = Temp._internal();
static const MethodChannel _channel = const MethodChannel('com.example.test');
factory Temp() {
return _singleton;
}
Temp._internal() {
_channel.setMethodCallHandler(_handleMethod);
}
_handleMethod(MethodCall call) {
switch (call.method) {
case 'test':
print('_handleMethod: ${call.method}');
_channel.invokeMethod('result', 'Result from flutter client');
}
}
}
Related
I'm trying to write platform specific code in Flutter. First of all, created a method to obtain battery temperature inside MainActivity.kt
This is my full code in MainActivity.kt file:
package com.xyz.zyx
import android.os.BatteryManager;
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.EventChannel
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
import io.flutter.plugin.common.PluginRegistry.Registrar
class MainActivity: FlutterFragmentActivity() {
// ...
private var batteryManager: BatteryManager? = null
private var methodChannel: MethodChannel? = null
private var temperatureChannel: EventChannel? = null
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "battery").setMethodCallHandler {
call, result ->
if (call.method == "getBatteryTemperature") {
val batteryTemperature = getBatteryTemperature()
if (batteryTemperature != -1) {
result.success(batteryTemperature)
} else {
result.error("UNAVAILABLE", "Battery temperature not available.", null)
}
} else {
result.notImplemented()
}
}
}
private fun getBatteryTemperature(): Int {
val batteryStatus: Intent? =
IntentFilter(Intent.ACTION_BATTERY_CHANGED).let { ifilter ->
this.registerReceiver(null, ifilter)
val batteryTemperature: Int
val batteryManager =
getSystemService(Context.BATTERY_SERVICE) as BatteryManager
batteryTemperature = intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, -99)
return batteryTemperature
}
}
}
This is how I call my platform specific code from Flutter side:
static const platform = MethodChannel('battery');
Future<void> _getBatteryTemp() async {
String batteryLevel;
try {
var result = await platform.invokeMethod('getBatteryTemperature');
batteryLevel = 'Battery temperature at $result % .';
} on PlatformException catch (e) {
batteryLevel = "Failed to get battery level: '${e.message}'.";
}
print(batteryLevel);
}
Lastly, this is the error on Flutter app running on an Android device. It throws an MissingPluginException even though there is a method called getBatteryTemperature, it says there isn't.
E/flutter (25427): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: MissingPluginException(No implementation found for method getBatteryTemperature on channel battery)
E/flutter (25427): #0 MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:294:7)
Is there something did I miss to implement in my code? Why does it not work?
I am working on a flutter plugin to calculate total steps in interval using Google Fit History Client (Fitness.getHistoryClient) which requires flutter activity and I want to call some of its methods in the background. I am using workmanager 0.5.0 to schedule a background job.
class MyPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, PluginRegistry.ActivityResultListener {
private lateinit var channel : MethodChannel
private val TAG = "MY TAG"
private var activity: Activity? = null
private var mResult: Result? = null
private lateinit var context: Context
override fun onAttachedToEngine(#NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "my_channel_name")
channel.setMethodCallHandler(this)
context = flutterPluginBinding.applicationContext
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"getTotalStepsInInterval" -> getTotalStepsInInterval(call, result)
else -> result.notImplemented()
}
}
private fun getTotalStepsInInterval(call: MethodCall, result: Result){
mResult = result
if (activity == null) {
mResult?.success(-1)
return
}
// val readRequest = ...
Fitness.getHistoryClient(activity!!, getGoogleAccount())
.readData(readRequest)
.addOnSuccessListener { response ->
var totalSteps = 0
// Calculate total steps from response
mResult?.success(totalSteps)
}
.addOnFailureListener { }
}
override fun onDetachedFromEngine(#NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
if (channel == null) {
return
}
binding.addActivityResultListener(this)
activity = binding.activity
}
// onDetachedFromActivityForConfigChanges + onReattachedToActivityForConfigChanges + onDetachedFromActivity
}
I am able to get the total steps when I invoke this method from a function in dart.
MyPlugin myPlugin = MyPlugin();
int res = await myPlugin.getTotalStepsInInterval();
print(res);
Activity is null when a method is invoked from the callback dispatcher of workmanager.
void callbackDispatcher() async {
workmanager.executeTask((task, inputData) async {
print('Running - Callback Dispatcher');
try {
MyPlugin myPlugin = MyPlugin();
int res = await myPlugin.getTotalStepsInInterval();
print(res);
} catch (err) {
throw Exception(err);
}
return Future.value(true);
});
}
// Output -------> -1
Since subscription is active using Recording API, I can fetch the data using readData when the user comes back to the app but I want to get the data at a specific time (For eg: 5:00 PM which I can schedule using workmanager) even when the app is not running. Duplicate Question
Still not possible?
package com.example.unit_testing
import android.content.Intent
import android.graphics.Bitmap
import android.provider.MediaStore
import android.view.WindowManager.LayoutParams
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodChannel
class MainActivity : FlutterActivity() {
private val METHOD_CHANNEL_NAME = "com.jinto/method"
private var methodChannel: MethodChannel? = null
private val cameraRequest = 1888
var imageView: Bitmap? = null
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
window.addFlags(LayoutParams.FLAG_SECURE)
super.configureFlutterEngine(flutterEngine)
// Setup Channels
setupChannels(flutterEngine.dartExecutor.binaryMessenger)
}
override fun onDestroy() {
teardownChannels()
super.onDestroy()
}
private fun setupChannels(messenger: BinaryMessenger) {
methodChannel = MethodChannel(messenger, METHOD_CHANNEL_NAME)
val cameraIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
methodChannel!!.setMethodCallHandler { call, result ->
if (call.method == "cameraOn") {
startActivityForResult(cameraIntent, cameraRequest)
result.success(imageView)
} else {
result.notImplemented()
}
}
}
private fun teardownChannels() {
methodChannel!!.setMethodCallHandler(null)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == cameraRequest && resultCode == RESULT_OK) {
val imageBitmap = data?.extras?.get("data") as Bitmap
println("wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww${imageBitmap.width}")
imageView = imageBitmap
}
}
}
if we pass the bitmap(imageView) it won't work , initially it retune nothing , after the camera service started not getting any data
how can i convert the bitmap into file and return it, can you help me to solve this problem,
This is my code to get data from platform side,
Future<void> _checkAvailability() async {
try {
var apiImage = await methodChannel.invokeMethod("cameraOn");
print('$apiImage');
} on PlatformException catch (e) {
print('error');
print(e);
}
}
I have an issue in service connection issue in that line var binder:TwentyFourHoursService.LocalBinder = service as TwentyFourHoursService.LocalBinder and could not find solution:
override fun onServiceConnected(
className: ComponentName,
service: IBinder
) { // cast the IBinder and get MyService instance
var binder:TwentyFourHoursService.LocalBinder = service as TwentyFourHoursService.LocalBinder
myService = binder.getService()
bound = true
// serviceCallbacks =this as ServiceCallbacks
myService!!.setCallbacks(mActivity) // register
}
Here is my service:
class TwentyFourHoursService : Service() {
private val mHandler: Handler = Handler() //run on another Thread to avoid crash
private var mTimer: Timer? = null //timer handling
// Binder given to clients
private val binder: IBinder = LocalBinder()
override fun onBind(intent: Intent): IBinder {
// throw UnsupportedOperationException("Not yet implemented")
return binder
}
override fun onCreate() {
if (mTimer != null) // Cancel if already existed
mTimer!!.cancel() else mTimer = Timer() //recreate new
mTimer!!.scheduleAtFixedRate(
TimeDisplay(),
0,
notify
) //Schedule task
//Timer().scheduleAtFixedRate(TimeDisplay(),0, notify)
}
fun setCallbacks(callbacks: ServiceCallbacks?) {
serviceCallbacks = callbacks
}
override fun onDestroy() {
super.onDestroy()
mTimer!!.cancel() //For Cancel Timer
Toast.makeText(this, "Service is Destroyed", Toast.LENGTH_SHORT).show()
}
//class TimeDisplay for handling task
internal inner class TimeDisplay : TimerTask() {
override fun run() { // run on another thread
mHandler.post(Runnable {
// display toast
/* if (serviceCallbacks!=null) {
serviceCallbacks!!.doSomething()
}*/
// Reload current fragment
// Reload current fragment
// startActivity(Intent(applicationContext, FitnessSlideMenuScreen::class.java))
// rFitnessSlideMenuScreen().displaySelectedFragment(HomeFragment())
Toast.makeText(applicationContext, "Service is running", Toast.LENGTH_SHORT).show()
})
}
}
Error:
***java.lang.ClassCastException: android.os.BinderProxy cannot be cast to com.example.beyahfitness.service.TwentyFourHoursService$LocalBinder***
The issue as I realised was as a result of the way had declared my service in Manifest,
<service
android:name=".MyService"
android:enabled="true"
android:process=":MyService" >
When I try getting the service at;
override fun onServiceConnected(name: ComponentName,service: IBinder)
I kept getting this error; java.lang.ClassCastException: android.os.BinderProxy cannot be cast to com.demo.MyService$LocalBinder
I resolved to refactoring my Service as below;
<service
android:name=".MyService" >
This apparently solves my problem, hope it helps you too
Please, help me to make interface in Swift to use it for callbacks purposes.
Example in Java:
public interface ErrorListener {
void onError(String errorMsg);
}
class SomeUiClass implements ErrorListener {
public SomeUiClass () {
SomeWorkingClass s = new SomeWorkingClass();
s.setErrorListener(this);
s.doSomething(true);
}
#Override
void onError(String errorMsg) {
System.out.println("Error msg: " + errorMsg);
}
}
class SomeWorkingClass {
ErrorListener listener;
void setErrorListener (ErrorListener listener) {
this.listener = listener;
}
void doSomething (boolean withError) {
if (withError) listener.onError("Test error");
}
}
//....
SomeUiClass ui = new SomeUiClass(); // prints: Error msg: Test error
So, I tried to use protocol in Swift for this purpose, but I didn't understand, how to use it properly.
It would look like this in swift
protocol ErrorListener {
func onError(errorMsg:String)
}
class SomeUiClass : UIViewController , ErrorListener {
func onError(errorMsg:String)
print("Error msg: ", errorMsg)
}
}
class SomeWorkingClass : UIViewController{
weak var listener:ErrorListener?
func setErrorListener (listener:ErrorListener) {
self.listener = listener
}
}
let ui = SomeUiClass() // prints: Error msg: Test error