Skip to content
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

Use jnigen and ffigen to call Java/Kotlin/Swift/ObjC natively without the MethodChannels #1444

Open
marandaneto opened this issue May 14, 2023 · 8 comments

Comments

@marandaneto
Copy link
Contributor

Description

https://dart.dev/guides/libraries/java-interop and https://dart.dev/guides/libraries/objective-c-interop
Depends on Dart 3, still experimental.
Can probably use gen packages (ffigen) for Linux, Windows, etc.
Need to figure out how to call Web without MethodChannels

@ueman
Copy link
Collaborator

ueman commented May 14, 2023

For web you don't need any method channels or similar since Dart has JS interopt built-in: https://dart.dev/web/js-interop

@vaind
Copy link
Collaborator

vaind commented Aug 24, 2023

  1. We may need to do this to enable profiling. Starting a profiler via a method channel is an async operation (even though the actual profiler startup call in objective-c/swift is synchronous). However, Hub.startTransaction() is synchronous so we cannot wait for the profiler to be actually started before returning from this function.
  2. I don't see why this would be limited to Dart 3, considering it seems to use standard FFI which has been around forever. Do you remember the source for that SDK version limitation @marandaneto?

@ueman
Copy link
Collaborator

ueman commented Aug 24, 2023

ffigen for Swift was introduced in Dart 2.18 which shipped with Flutter 3.3, see https://medium.com/flutter/announcing-flutter-3-3-at-flutter-vikings-6f213e068793

Then there's dart:ffi (which is part of the Dart SDK), and package:ffi (distributed via pub). ffigen makes use of a specific version of package:ffi. ffigen is also the tool where most of the Swift binding magic and its improvments happens.

In addition to that, Swift ffi is considered experimental, however cupertino_http (a package using those Swfit/objc bindings) is considered stable.

@vaind
Copy link
Collaborator

vaind commented Aug 24, 2023

Looking at the wording in the Dart 2.18 announcement, it looks like it's just a documentation issue (and the availability of a generator), not that it didn't work with an older version of the SDK. The changelog for 2.18 doesn't mention anything (or I'm blind) so that would indicate my assumption may be true.
I may be wrong but I think we wouldn't even need to bump the minimum Dart version in pubspec. Anyway, I'll likely give this a quick try, considering the need for profiling I've mentioned above.

(FYI, I am pretty familiar with FFI: https://github.com/objectbox/objectbox-dart/graphs/contributors - the whole thing is based on a C(++) library)

@marandaneto
Copy link
Contributor Author

@vaind I read somewhere about min Dart 3 but I don't recall where exactly, it could be a docs issue so go ahead and try it out.

@vaind
Copy link
Collaborator

vaind commented Sep 1, 2023

I've evaluated this (in the experiment PR #1622) so let me summarize the findings.

  • both Cocoa SDK and JNI are usable in the versions available at the time of writing (see pubspec.yaml)
  • both generators (ffigen & jnigen) seem to be actively developed in areas important to us (objective-c & java/android support)
  • generated interfaces look pretty usable (not very cumbersome to use)
  • we should see a smaller call overhead with FFI compared to MethodChannels - I don't have any benchmarks at hand, just based on my understanding of MethodChannels and how they work (e.g. that they require data serialization for all invocations).
  • we would be able to call native functions without async, further simplifying the API (and improving performance).
  • as is, the generated code doesn't require raising the SDK dependency to Dart 3, although the just released (an hour ago) version of jnigen has just raised this from 2.18 to 3.1 for no obvious reason. I think it's safe to say they'd try to address new SDK so it may not be a great fit for us. For cocoa SDK, the generator (ffigen) is more mature and trying more to keep the compatibility with older SDKs so even though the generated code has some incompatible code, it's currently limited to adding the final specifier to classes which can be easily replaced locally after code generation ran.

Risks

  • JNI generation is fairly new so there may be more risks involved (the generated code may be more buggy)
  • the previous point applies to some extent to ffigen too because even though that's quite a mature package, the new code addressing objective-c specifically hasn't been out for so long
  • on the other hand - it's possible to just stick to the generator version that "works for us" and ignore new versions - even have a custom fork with cherry-picked changes if we really wanted to. For example to keep compatibility with older Dart SDK version (2.*). However, that comes with its own drawbacks so we may want to wait out till we raise the SDK dependency bar.

Next steps

  • switching could be done either separately for Cocoa and for JNI, with some changes how those are currently being initialized (distinguish in the NativeSdkIntegration class), or do them both together, saving some code complexity of not having to maintain method-channel integrations at all
  • there should be a decision on whether, when and to what extent we want to make use of FFI
  • we can even do an incremental approach - implement only the new code via FFI

Notes on code generation updates

Since this involves code generation, updating the SDK would have an additional step (this should be part of upgrade script that's triggered by the updater, e.g. flutter/scripts/update-cocoa.sh ): dart run ffigen --config ffi-cocoa.yaml (or the alternative for JNI).

@vaind vaind removed their assignment Sep 1, 2023
@vaind
Copy link
Collaborator

vaind commented Apr 11, 2024

I'm currently looking into replay integration for Android and while it's not a hard requirement, it would greatly simplify things
to use JNI instead of method channels because of the callbacks involved. Also, there is a performance consideration because we may be able to avoid memory copies when transferring frames from Flutter to the native video encoder.
Last time I checked this it seemed quite usable and for cocoa, we're using it for some time for the profiler. I'm going to prepare (or rather update #1622) to introduce native JNI integration and we can start using it incrementally.

FYI @buenaflor @denrase

@vaind
Copy link
Collaborator

vaind commented May 6, 2024

After working with (and around) jni/jnigen in the replay support PR #2011, I don't feel it's feasible to update to it at the moment. The issue is that we need (for native callbacks) at least jni/jnigen package at v0.6.0, which in turn depends on Dart SDK v3.1.0 (Flutter 3.13). We currently keep compatibility of sentry_flutteer with a much older version (Dart SDK 2.17, i.e. Flutter 3.0) so not sure it's worth bumping without further consideration of the benefits we get.
Additionally, even the jni/jnigen v0.6.0 is actually quite old and any updates they're making (be it new features or bugfixes), they use new features coming from the most recent dart SDKs, so for example jni 0.9.0 (the most recent one as of the time of writing) depends on Dart SDK 3.3 (Flutter 3.19 released Feb'24).
As such, I'd recommend waiting with the JNI/JNIGEN switch for now until it stabilizes a bit more and/or hopefully stops bumping the required SDK version or when we decide to bump the minimum Flutter/Dart SDK version for the sentry_flutter package

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Needs Discussion
Development

No branches or pull requests

3 participants