How to implement .aidl file in flutter - java

I am trying to use flutter to communicate with SunMi mobile printer. I want to invoke the printer using AIDL as one of the ways to communicate with the printer but i don't know how and where to place the AIDL files in flutter or is it even possible to use flutter in my case. I need to know if it is possible to communicate with the printer using its AIDL. I am opting to use flutter or android studio with java for my application.
Source of question : https://github.com/flutter/flutter/issues/49413#issue-554566892
i could not find the proper answer so posted the question here.

Since this is an AIDL file we're talking about, it would be safe to assume that this is an Android only feature.
To do this, like any other Android-specific MethodChannel implementation, you'll need to create a MethodCallHandler and/or an StreamHandler (depending if you want to do streams or just an command -> result approach), register it on the Main FlutterActivity, and call it from dart through MethodChannels.
DISCLAIMER
The code below is not tested and might have some syntax errors, let me know if you find issues and I'll address them.
I have combined everything in one file for brevity, you can move some parts of the code to separate files as you see fit.
Creating the MethodCallHandler/StreamHandler
Inside your android/app/src/main/com/some/path folder in your flutter app, create a new java file and implement ServiceConnection, MethodChannel.MethodCallHandler and/or EventChannel.StreamHandler:
public class PrinterPlugin implements MethodChannel.MethodCallHandler, EventChannel.StreamHandler, ServiceConnection {
public static final CHANNEL = "com.some.path/printer";
public static final EVENT_CHANNEL = "com.some.path/printer-events";
private Context context;
private IPrinterService printerService = null; // where IPrinterService would be the AIDL's name
public EventChannel.EventSink eventSink = null;
public PrinterPlugin(Context context) {
this.context = context;
if (printerService == null) {
// these strings should be in your documentation or you can find these values from the package manager
Intent intent = new Intent("com.your.printer.service");
intent.setPackage("com.whatever.aidl");
context.bindService(intent, this, Context.BIND_AUTO_CREATE);
}
}
public void disconnect() {
context.unbindService(this);
}
// streamhandler implementation
#Override
public void onListen(Object arguments, EventChannel.EventSink events) {
this.eventSink = events;
}
#Override
public void onCancel(Object arguments) {
this.eventSink = null;
}
// /streamhandler implementation
// methodcallhandler implementation
#Override
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
try {
switch (call.method) {
case "initialize": printerService.printerInit(); break;
case "print-text": printerService.printText(call.argument("data")); break;
// implement other aidl methods
}
} catch (RemoteException e) {
result.error("", ex.getMessage(), null);
}
}
// /methodcallhandler implementation
// serviceConnection implementation
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
printerService = IPrinterService .Stub.asInterface(service);
if (eventSink != null) {
eventSink.success("Printer Connected");
}
}
#Override
public void onServiceDisconnected(ComponentName name) {
printerService = null;
if (eventSink != null) {
eventSink.success("Printer Disconnected");
}
}
// /serviceConnection implementation
}
Register the PrinterPlugin to the MainActivity as a MethodChannel and EventChannel
Now that that's out of the way, you'll need to register this plugin on the MainActivity's configureFlutterEngine method:
public class MainActivity extends FlutterActivity {
private PrinterPlugin printerPlugin;
#Override
public void configureFlutterEngine(#NonNull FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
Context context = getContext();
printerPlugin = new PrinterPlugin(context);
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), PrinterPlugin.CHANNEL)
.setMethodCallHandler(printerPlugin);
new EventChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), PrinterPlugin.EVENT_CHANNEL)
.setStreamHandler(printerPlugin);
}
#Override
protected void onDestroy() {
super.onDestroy();
this.printerPlugin.unbindService();
}
}
Call the methods from dart
Now the last thing that you need to do is call these from dart.
const MethodChannel _channel = MethodChannel('com.some.path/printer'); // should match the CHANNEL constant on the java side
const EventChannel _evntChannel = EventChannel('com.some.path/printer-events'); // should match the EVENT_CHANNEL constant on the java side
class PrinterPlugin {
static Stream<dynamic> _printerStream = _eventChannel.receiveBroadcastStream();
Stream<String> status$;
PrinterPlugin() {
status$ = _printerStream;
}
static Future printText(String data) async {
await _channel.invokeMethod('initialize');
await _channel.invokeMethod('print-text', 'Foo Bar');
}
}
Well, that was a lot of code - but that's basically how I did it. Just a MethodCallHandler with a ServiceConnection implementation.
You can go crazy with this implementation as well, like real-time printing progress, or getting streamed printer status, etc.
Let me know if it works for your needs.

Related

What are the alternatives to RCTEventEmitter and RCTModernEventEmitter?

I am trying to integrate a Native UI component in react-native using the new architecture with fabric enabled
Here is my spec file
import type {HostComponent, ViewProps} from 'react-native';
import type {
DirectEventHandler,
BubblingEventHandler,
} from 'react-native/Libraries/Types/CodegenTypes';
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
type Event = Readonly<{
text?: string;
}>;
interface NativeProps extends ViewProps {
text: string;
onClickHandler?: DirectEventHandler<Event>; ////Event name should start with on
}
export default codegenNativeComponent<NativeProps>(
'MyButtonView',
) as HostComponent<NativeProps>;
Then on native side I created following files
public class MyButtonViewManager extends SimpleViewManager<MyButtonView> {
public static final String NAME = "MyButtonView";
ReactApplicationContext mCallerContext;
public MyButtonViewManager(ReactApplicationContext reactContext) {
mCallerContext = reactContext;
}
#NonNull
#Override
public String getName() {
return NAME;
}
#NonNull
#Override
protected MyButtonView createViewInstance(#NonNull ThemedReactContext reactContext) {
return new MyButtonView(reactContext);
}
#ReactProp(name = "text")
public void setQrCodeText(MyButtonView view, String text) {
view.setText(text);
}
#Nullable
#Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return MapBuilder.of("topOnClickHandler",
MapBuilder.of("registrationName", "onClickHandler")
);
}
}
public class MyButtonView extends androidx.appcompat.widget.AppCompatButton {
public MyButtonView(Context context) {
super(context);
configureViews();
}
private void configureViews(){
setBackgroundColor(Color.YELLOW);
setOnClickListener(view -> {
ReactContext reactContext = (ReactContext)getContext();
EventDispatcher eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(
reactContext ,getId()
);
eventDispatcher.dispatchEvent(new MyButtonClickEvent(getId()));
});
}
}
public class MyButtonClickEvent extends Event<MyButtonClickEvent> {
public MyButtonClickEvent(int viewId) {
super(viewId);
}
#Override
public String getEventName() {
return "topOnClickHandler";
}
#Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
super.dispatch(rctEventEmitter);
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), Arguments.createMap());
}
#Nullable
#Override
protected WritableMap getEventData() {
WritableMap event = Arguments.createMap();
event.putString("message", "MyMessage");
return event;
}
}
What is the alternative to dispatch and RCTEventEmitter as both are deprecated? I was looking into RCTModernEventEmitter and it also extends the deprecated RCTEventEmitter
Also i have to change the event name from OnClickHandler to topOnClickHandler in Native Android side. It was throwing hermes error. Not sure why there should be top prefix, why can't it just be OnClickHandler.
From the react-native source code:
This [RCTModernEventEmitter] is a transitional replacement for RCTEventEmitter that works with Fabric and non-Fabric renderers. RCTEventEmitter works with Fabric as well, but there are negative perf implications and it should be avoided.
You can use this for now as a replacement that works with Fabric.
This [RCTModernEventEmitter] interface will also be deleted in the distant future and be replaced with a new interface that doesn't need the old receiveEvent method at all. But for the foreseeable future, this is the recommended interface to use for EventEmitters.
However, in the long run this will be removed.
There's ReactEventEmitter that might help based on your use-case.

Unable to use AccessibilityService and DispatchGesture

I decided to create an application that can automatically perform screen touches that I'm going to use it on different devices that need automation.
I heard about '''AccessibilityService''' that led me to using Android.Graphics.Path, GestureDescription and AccessibilityService. I wasn't able to find a way that I could communicate with AccessibilityService so I decided to create a class that inherits from AccessibilityService that led me to access to DispatchGesture function but it actually doesn't work when I use this function.
This is my code and my AndroidManifest file:
Code:
public class DispatchGController : AccessibilityService
{
public override void OnAccessibilityEvent(AccessibilityEvent e)
{
}
public override void OnInterrupt()
{
}
public override void OnCreate()
{
base.OnCreate();
Core.WLDel("Service Created!");
}
protected override void OnServiceConnected()
{
base.OnServiceConnected();
this.SetServiceInfo(new AccessibilityServiceInfo() { EventTypes = EventTypes.AllMask, FeedbackType = Android.AccessibilityServices.FeedbackFlags.Spoken, NotificationTimeout = 100 });
}
protected override bool OnGesture(int gestureId)
{
Core.WLDel(gestureId.ToString());
return base.OnGesture(gestureId);
}
public DispatchGController()
{
var res = DispatchGesture(Core.TouchGestures.CreateClick(new Core.ACPointF(10, 200)), Core.MainCallback, null);
}
}
public class TouchGestures
{
const int ClickDuration = 1;
public static GestureDescription CreateClick(ACPointF Point)
{
Path clickPath = new Path();
clickPath.MoveTo(Point.X, Point.Y);
GestureDescription.StrokeDescription clickStroke = new GestureDescription.StrokeDescription(clickPath, 0, ClickDuration);
GestureDescription.Builder builder = new GestureDescription.Builder();
builder.AddStroke(clickStroke);
return builder.Build();
}
}
And this is my Manifest File:
Manifest
Any recommendations over this?

RxJava2 success always called twice

I'm implementing an MVP architecture with Retrofit, and RxJava2 (with RxAndroid). I have the following setup
NetworkInterface.java where retrofit calls lie
public interface NetworkInterface {
#GET("/input")
Single<List<InformationData>> getInfoData();
}
InformationDataDao.java Data access layer for the information data
#Dao
public interface InformationDataDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
Completable createInfoData(List<InformationData> infoData);
}
MainPresenter.java where the calls are being made public class MainPresenter {
public MainPresenter() {
}
// Method called to initialize the subscription
#Override
public void subscribe() {
this.collectInfoData();
}
private void collectInfoData() {
Single<List<InformationData>> singleInfoData = networkInterface.getInfoData()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
singleInfoData.subscribe(informationDatas -> {
InformationDataDao informationDataDao = database.informationDataDao();
informationDataDao.createInfoData(informationDatas);
// What made me detect that this was being called twice
Log.d(Constant.LOG_TAG, "Info Data: " + informationDatas.size());
// Rest of the code here
}, Throwable::printStackTrace);
}
}
SplashActivity.java The method that calls the presenter
public class SplashActivity extends AppCompatActivity {
// Helps in getting the location
private FusedLocationProviderClient locationClient;
private SplashPresenter mPresenter;
#Override
protected void onCreate(Bundle savedInstance) {
super.onCreate(savedInstance);
setContentView(R.layout.activity_splash);
mPresenter = new SplashPresenter();
}
#Override
protected void onResume() {
super.onResume();
// These three lines ensure that you have the appropriate permissions to proceed. In the third line for instance, the user will be navigated out to the settings pane to turn on location.
// When he/she returns to the activity, the onResume is called so I'm assuming that's the best place to start the presenter
this.ensureHasPermissions();
boolean isGoogleServiceAvailable = this.ensureGoogleServiceAvailability();
boolean isLocationServiceAvailable = this.ensureLocationServiceAvailable();
if (isLocationServiceAvailable && isGoogleServiceAvailable) {
locationClient.getLastLocation()
.addOnSuccessListener(this, location -> {
// Set the closest location and subscribe
presenter.setClosestLocation(location);
presenter.subscribe();
})
.addOnFailureListener(this, Throwable::printStackTrace);
}
}
}
From the SplashPresenter.java, the log is printed twice, indicating that the complete callback was called twice. Is there any idea why this may be so? I'm quite new to RxJava2 and will greatly appreciate the feedback.
Thanks.

Volley static callback listeners context GCed

After using LeakCanary I found that there were many leaks in my app, most of them due to Volley's anonymous callback listeners. So I wrote a Util (below) class which uses static callbacks and WeakReference to keep reference to Context and an anonymous callback. But when I open the app for the first time, i.e. a cold start, the context is GCed soon after the request is made but during a warm start all works fine. Also this happens only for the first activity in the app.
Any alternative way of handling memory leaks with volley which works properly are also welcome.
public abstract class VUtil {
public static final String TAG = VUtil.class.getSimpleName();
public interface JsonCallback {
void onSuccess(JSONObject response);
}
public interface StringCallback {
void onSuccess(String response);
}
public interface ErrorCallback {
void onError(VolleyError error);
}
public static class JsonResponseListener implements Response.Listener<JSONObject> {
private final WeakReference<Context> mContextWeakReference;
private final WeakReference<JsonCallback> mCallbackWeakReference;
public JsonResponseListener(Context context, JsonCallback callback) {
mContextWeakReference = new WeakReference<>(context);
mCallbackWeakReference = new WeakReference<>(callback);
}
#Override
public void onResponse(JSONObject jsonObject) {
Context context = mContextWeakReference.get();
JsonCallback callback = mCallbackWeakReference.get();
if (context != null && callback != null) {
callback.onSuccess(jsonObject);
} else {
Log.d(TAG, "Context was GCed");
}
}
}
public static class StringResponseListener implements Response.Listener<String> {
private final WeakReference<Context> mContextWeakReference;
private final WeakReference<StringCallback> mCallbackWeakReference;
public StringResponseListener(Context context, StringCallback callback) {
mContextWeakReference = new WeakReference<>(context);
mCallbackWeakReference = new WeakReference<>(callback);
}
#Override
public void onResponse(String response) {
Context context = mContextWeakReference.get();
StringCallback callback = mCallbackWeakReference.get();
if (context != null && callback != null) {
callback.onSuccess(response);
} else {
Log.d(TAG, "Context was GCed");
}
}
}
public static class ErrorListener implements Response.ErrorListener {
private final WeakReference<Context> mContextWeakReference;
private final WeakReference<ErrorCallback> mCallbackWeakReference;
public ErrorListener(Context context, ErrorCallback callback) {
mContextWeakReference = new WeakReference<>(context);
mCallbackWeakReference = new WeakReference<>(callback);
}
#Override
public void onErrorResponse(VolleyError error) {
Context context = mContextWeakReference.get();
ErrorCallback callback = mCallbackWeakReference.get();
if (context != null && callback != null) {
callback.onError(error);
} else {
Log.d(TAG, "Context was GCed");
}
}
}
}
GC depends on many many things that are happening. One possible cause for your case is that when you do your first request after a 'cold boot' you app must init various custom objects, fragments, activities, views caches etc. and thus needs memory before increases the heap and thus do a GC.
The solution I propose however is to change your architecture.
1) it seems that u keep ref to context but it is never used. just drop it
2) you have Volley callbacks which delegates to your custom callbacks which you need to pass anyway, why don't you simply use 1 set of callbacks which you pass to the respective requests.
3) you WeekRef your custom callbacks but u cannot do without them. Week Referencing is not the ultimate solution to memory leaks. you have to find out why the ref is still there when you don't need it.
so if you leak issue is in JsonCallback, StringCallback and ErrorCallback implementations just try to figure this out instead of making the chain longer and cutting it at the end.
Thanks to djodjo's answer which helped me to reach a solution
Always use addToRequestQueue(request, TAG). Here TAG bit is what we'll use to cancel requests when their Activity/Fragment/View or anything is GCed
What i did is create a base activity and add all this request cancellation code in that activity. Here's what it looks like
public abstract class BaseActivity extends AppCompatActivity {
public final String tag;
public BaseActivity() {
super();
tag = getClass().getSimpleName();
}
#Override
protected void onDestroy() {
App.getInstance().cancelRequests(tag);
super.onDestroy();
}
protected <T> void addToRequestQueue(Request<T> request) {
App.getInstance().addToRequestQueue(request, tag);
}
}
cancelRequests is just simple code
getRequestQueue().cancelAll(tag);
Extend your activities from this BaseActivity and use addToRequestQueue to make requests, which will get cancelled automatically when your activity is destroyed. Do similar thing for fragment / dialog / whatever else.
If you make requests from anywhere else which doesn't follow a life-cycle, make sure that it's not binding to any Context and you'll be fine.

Detecting if you're in the main process or the remote service process in Application

I have an application which has a remote service running in a separate process:
<service android:name=".MyService" android:process=":remote"/>
I'm also using an Application class:
<application android:label="#string/app_name" android:name=".MyApplication" ...
Can I do something like this?
public class MyApplication extends Application {
public MyApplication() {
if (isRemoteService()) {
setupLog("remoteservice.log");
} else {
setupLog("application.log");
}
}
I'm thinking I could get the process name and use that to detect if I'm in the remote service or the main app, but I haven't found out how to get the process name. I can get the PID from android.os.Process.myPID(), but that doesn't help me much.
For example, if you want to check whether you are in main process, you can write code in your application like this:
public class MyApplication extends Application {
#Override
public void onCreate() {
//...code here will be execute in every process...
if (isMainProcess()) {
//...code here will be execute only in main process
}
super.onCreate();
}
// your package name is the same with your main process name
private boolean isMainProcess() {
return getPackageName().equals(getProcessName());
}
// you can use this method to get current process name, you will get
// name like "com.package.name"(main process name) or "com.package.name:remote"
private String getProcessName() {
int mypid = android.os.Process.myPid();
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
List<RunningAppProcessInfo> infos = manager.getRunningAppProcesses();
for(RunningAppProcessInfo info : infos) {
if (info.pid == mypid) {
return info.processName;
}
}
// may never return null
return null;
}
}
I can offer an indirect solution:
Within each of the respective StartUp methods, set a System Property:
System.setProperty("PROCESS_TYPE","SERVICE");
System.setProperty("PROCESS_TYPE","RECEIVER");
System.setProperty("PROCESS_TYPE","ACTIVITY");
The Properties are static, isolated to the process, and can be reached from everywhere. Added benefit is they can be directly used by logging frameworks like Logback.
I had a similar issue and this is what I did.
MyService and MyActivity have a common part, MyEngine, but the behaviour must be a bit different in these two cases.
One thing that is different is setup, but this setup is done in the classes MyService and MyActivity.
Another thing that is different for the activity and the service is done via a listener: MyEngine defines the interface MyEngine.Listener while MyService and MyActivity provide the engine with different implementations of that interface.
So, if you want to pass a boolean value, two methods are possible:
// Method 1: different initialization
class MyEngine {
MyEngine(boolean isService) { ... }
}
class MyActivity extends Activity {
private MyEngine = new MyEngine(false);
...
}
class MyService extends Service {
private MyEngine = new MyEngine(true);
...
}
// Method 2: callbacks
class MyEngine {
interface Listener {
boolean isService();
}
private Listener mListener;
MyEngine(Listener listener) { mListener = listener; }
}
class MyActivity extends Activity {
private mListener = new MyEngine.Listener() {
boolean isService() { return false; }
}
private MyEngine = new MyEngine(mListener);
...
}
class MyService extends Service {
private mListener = new MyEngine.Listener() {
boolean isService() { return true; }
}
private MyEngine = new MyEngine(mListener);
...
}
Notes.
The boolean value used in the above example is useless in the real world: if you want to use, say, different log file names, it is better to pass the file name rather than the boolean. If you want to perform two different actions, it's better to have one listener function with two implementations.
Of course, one could pass a Context and check if it is a child of Activity or a Service, or get the name of the current process, but these things are Android-specific implementation details, and it's better not to depend on them unless absolutely necessary.
I'm using a piece of code, which is taken from Google's WorkManager lib. As of writing this it's located in GreedyScheduler.java. Having defined method getProcessName() as such:
#Nullable
private String getProcessName() {
if (SDK_INT >= 28) {
return Application.getProcessName();
}
// Try using ActivityThread to determine the current process name.
try {
Class<?> activityThread = Class.forName(
"android.app.ActivityThread",
false,
GreedyScheduler.class.getClassLoader());
final Object packageName;
if (SDK_INT >= 18) {
Method currentProcessName = activityThread.getDeclaredMethod("currentProcessName");
currentProcessName.setAccessible(true);
packageName = currentProcessName.invoke(null);
} else {
Method getActivityThread = activityThread.getDeclaredMethod(
"currentActivityThread");
getActivityThread.setAccessible(true);
Method getProcessName = activityThread.getDeclaredMethod("getProcessName");
getProcessName.setAccessible(true);
packageName = getProcessName.invoke(getActivityThread.invoke(null));
}
if (packageName instanceof String) {
return (String) packageName;
}
} catch (Throwable exception) {
Log.d("TAG", "Unable to check ActivityThread for processName", exception);
}
// Fallback to the most expensive way
int pid = Process.myPid();
ActivityManager am =
(ActivityManager) mContext.getSystemService(ACTIVITY_SERVICE);
if (am != null) {
List<ActivityManager.RunningAppProcessInfo> processes = am.getRunningAppProcesses();
if (processes != null && !processes.isEmpty()) {
for (ActivityManager.RunningAppProcessInfo process : processes) {
if (process.pid == pid) {
return process.processName;
}
}
}
}
return null;
}
Then on the calling side:
boolean isMainProcess() {
return TextUtils.equals(mContext.getPackageName(), getProcessName());
}

Categories