-
Notifications
You must be signed in to change notification settings - Fork 67
[jnigen] Enable "listener" callbacks #1568
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Tricky. Then a flag would be The flag would only exist for methods that are |
Only issue with a bool is that we're adding more Fwiw, the way ffigen handles this is by having 2 implementation methods, one which uses blocking callbacks for everything, and the other which uses listener callbacks for all the void methods. If the user wants more granularity than that, they can implement individual methods either as listeners or as blocking callbacks using the /// MyProtocol
abstract final class MyProtocol {
/// Builds an object that implements the MyProtocol protocol. To implement
/// multiple protocols, use [addToBuilder] or [objc.ObjCProtocolBuilder] directly.
static objc.ObjCObjectBase implement(
{int Function(SomeStruct)? optionalMethod_,
void Function(int)? voidMethod_,
required objc.NSString Function(objc.NSString, double)
instanceMethod_withDouble_}) {
final builder = objc.ObjCProtocolBuilder();
MyProtocol.optionalMethod_.implement(builder, optionalMethod_);
MyProtocol.voidMethod_.implement(builder, voidMethod_);
MyProtocol.instanceMethod_withDouble_
.implement(builder, instanceMethod_withDouble_);
return builder.build();
}
/// Builds an object that implements the MyProtocol protocol. To implement
/// multiple protocols, use [addToBuilder] or [objc.ObjCProtocolBuilder] directly. All
/// methods that can be implemented as listeners will be.
static objc.ObjCObjectBase implementAsListener(
{int Function(SomeStruct)? optionalMethod_,
void Function(int)? voidMethod_,
required objc.NSString Function(objc.NSString, double)
instanceMethod_withDouble_}) {
final builder = objc.ObjCProtocolBuilder();
MyProtocol.optionalMethod_.implement(builder, optionalMethod_);
MyProtocol.voidMethod_.implementAsListener(builder, voidMethod_);
MyProtocol.instanceMethod_withDouble_
.implement(builder, instanceMethod_withDouble_);
return builder.build();
}
static void addToBuilder(...
static void addToBuilderAsListener(...
/// optionalMethod:
static final optionalMethod_ =
objc.ObjCProtocolMethod<int Function(SomeStruct)>(...);
/// voidMethod:
static final voidMethod_ =
objc.ObjCProtocolListenableMethod<void Function(int)>(...);
/// instanceMethod:withDouble:
static final instanceMethod_withDouble_ =
objc.ObjCProtocolMethod<objc.NSString Function(objc.NSString, double)>(...);
} |
If we add "
This is convenient. The slight issue I have with it is the fact that it implies that everything is a listener. Of course we know that the functions returning a value cannot be listeners but the user might not. @lrhn What do you think about using |
I considered doing this for ObjC, but there are some details that make this not quite work (eg the block's invoke function pointer has a different signature to the user's function). Not sure if you'd have similar issues in jnigen. |
Yes, for starters every java object is a void pointer instead. Still, not sure if the enum value will be the best. Maybe it's best if we generate wrappers with multiple constructors like NativeCallable. And maybe factor out any potential compiler special cases for things like temporary isolate callables to be available anywhere with some pragma so we can also use them in the generated wrappers. |
I've already implemented the This is the check it uses to see if the (($impl is _$MyRunnable &&
($impl as _$MyRunnable)._run is _$core.Future<void>
Function() &&
($impl as _$MyRunnable)._run is! _$core.Never Function()) ||
($impl.run is _$core.Future<void> Function() &&
$impl.run is! _$core.Never Function())) // If the implementation is using the callback passing style, look at the
// actual passed callback instead of the wrapper function. The wrapper is
// always going to return `FutureOr<void>`.
//
// If the callback simply throws its return type will be `Never`. As any
// function `R <F>` is a subtype of `Never <F>`, we should have a special
// case for it. It's not possible to throw from a listener function, so
// functions that only throw and hence have a `Never` return type, will be
// considered to be sync. Also I could have used |
Actually FutureOr<void> Function() f = () { return Future<void>.delayed(Duration(seconds: 1)); }; but this isn't: void Function() f = () { return Future<void>.delayed(Duration(seconds: 1)); }; While this is: void Function() f = () async { return Future<void>.delayed(Duration(seconds: 1)); }; So it really makes it dependent on the existence of |
The problems here come from inference, but inference is hard to avoid with function expressions, since you cannot write a return type. void Function() f = () { return Future<void>.delayed(Duration(seconds: 1)); }; is an error is that the function return type is inferred to be I generally try to avoid deducing anything from a function return type because it is so hard to control the return type. And you do need to consider a function type like If I understand this correctly, after trying to swap it back in, the problem is:
We can't know if a Dart For a If the context type is (If we want to save space, use shorthands and shorter names. var javaComputeRunnable = Runnable.of(run: block((x) => compute(x)));
var javaAsyncComputeeRunnable = Runnable.of(run: blockAwait((x) async => await computeMore(x)));
var javaCallbackRunnable = Runnable.of(run: schedule((x) => laterAction(x))); where (If the behavior is chosen at runtime, based on the So JRunnable<T Function()> block<T>(T Function() dartFunction) => ...
JRunnable<T Function()> blockAwait<T>(Future<T> Function() dartFunction) => ...
JRunnable<void Function()> schedule(void Function() dartFunction) => ... and |
The functions can have arguments as well, so they should look more like: JCallable<T> block<T extends Function>(T dartFunction) => ... Which doesn't allow us to limit the return types.
I think I'll use the initial idea to use an extra enum value per void function then. Shorter and no magic. |
I wonder if this is what I'm looking for, but I'm looking for examples of how to invoke a callback from the native side back to Dart e.g. when some operation finishes. E.g. for a class like this on the native side: public class FlutterPDFViewController {
void onPageChanged(int page, int total) {
// how to notify Dart about it
}
}
Is there a way to somehow pass information back to Dart here? |
|
Thanks, I managed to do it along these lines package com.example.pdf;
public interface PDFStatusListener {
void onLoaded();
void onPageChanged(int page, int total);
void onError(String error);
void onDisposed();
void onLinkRequested(String uri);
} package com.example.pdf;
public class PDFViewController {
public void setPdfStatusListener(PDFStatusListener listener){
// handle me
}
} final example = pdf.PDFViewController();
final listener = pdf.PDFStatusListener.implement(
pdf.$PDFStatusListener(
onPageChanged: (int? page, int? total) {
},
onLoaded: () {
},
onError: (JString? string) {
},
onDisposed: () {
},
onLinkRequested: (JString? string) {
},
),
);
example.setPdfStatusListener(listener); android_sdk_config:
add_gradle_deps: true
android_example: "example/"
summarizer:
backend: asm
output:
dart:
path: lib/bindings.dart
structure: single_file
log_level: all
source_path:
- "android/src/main/java/"
classes:
- "com.example.pdf.PDFViewController"
- "com.example.pdf.PDFStatusListener" |
Looks good. If the individual methods don't need to be blocking, I suggest adding |
I also see that the void onError(@NonNull String error); jnigen will take the annotations into account when generating the types. |
Continuing in the path to get "interface implementation" feature out of experimental in JNIgen.
JNIgen currently generates an
implement
factory on interface types like so:If we pass
Runnable.implement(...)
to Java and it tries to call.run
method, it will block until Dart actually runs it. However sometimes we don't have to block when the callback is intended to be a listener.Of course, making a method into a "listener" is only possible to do if the return type of the said method is
void
(or maybe handling other cases like Java'sFuture
).We could add an enum parameter per (viable) method to communicate which methods have to be blocking vs. listener:
enum JCallMode { blocking, listener }
.Alternatively, a la
NativeCallable
, we could create aJCallable<T extends Function>
and instead of requiring a simple function, require aJCallable
. ThenJCallable
can have two factories:.listener
, and.blocking
.The issue here is that a simple callback implementation will be quite verbose:
And non-void returning methods cannot be
JCallable.listener
anyways so it's unnecessary in those cases.@liamappelbe @dcharkes @mkustermann @lrhn Which syntax do you prefer? Or maybe something different.
The text was updated successfully, but these errors were encountered: