dart-lang / native

Dart packages related to FFI and native assets bundling.
BSD 3-Clause "New" or "Revised" License
157 stars 44 forks source link

Cross-cutting aspects to make it easy for Flutter apps to interact with Android APIs #699

Open mkustermann opened 1 year ago

mkustermann commented 1 year ago

Our package:jnigens purpose is to produce dart bindings for Java/Kotlin APIs based on .jar files or java/kotlin sources. That is useful in general - but our main motivation for building this is to allow easy interop with Java/Kotlin APIs on Android in the context of Flutter.

There are some specifics to this that we should keep in mind:

Thread restricted APIs

Certain Android APIs are only usable from the platform UI thread or worker threads. The APIs may indicate that via a @UiThread or @WorkerThread annotation.

Once we have a mechanism to run code on the platform UI thread, the bindings generator could take advantage of that mechanism (e.g. by producing asynchronous Future-based APIs that perform the actual call on the platform thread)

APIs requiring Context / Activity / ... objects

Certain Android APIs require callers to pass along special objects. Among them e.g. android.content.Context objects. There's different kinds of context objects (most importantly: application context, activity contexts, service contexts). Those special objects have lifecycles during which they can be used (e.g. while an activity is active).

Flutter code (on UI isolate or background isolate) that wishes to invoke Android APIs may need access to such special objects. We'll probably need a way to obtain those and expose them to Dart so they can be used when invoking the generated Android API bindings. This may require integration into flutter engine and/or making a flutter-plugin with method channels that exposes them.

Normal Flutter apps are structured in the following way: The application code extends FlutterActivity. Once the activitie's onCreate() lifecycle method is invoked, it will create a FlutterEngine object with the activity context. Once plugins are attached to an engine, they will get notified when the engine gets attached/detached to/from activities/services/... The plugins do have - in those lifecycle callbacks - access to e.g. context / activity objects.

We should also think about whether there can be possible races happening in the lifecycle of those objects and the usage of those from Dart.

Testing

Given the nature of method channels - which are similar to e.g. gRPC (i.e. generated message classes and RPC stubs) - one could mock the native side using Dart code rather easily - which may be amenable to unit tests. We should think about how using direct interop with native impacts testability of code.

/cc @HosseinYousefi

mahesh-hegde commented 1 year ago

check out

https://github.com/dart-lang/jnigen/blob/823d8dc8e683d7ff320c518d54cc80b07cfb3817/jni/lib/src/jni.dart#L153

https://github.com/dart-lang/jnigen/blob/823d8dc8e683d7ff320c518d54cc80b07cfb3817/jni/android/src/main/java/com/github/dart_lang/jni/JniPlugin.java#L30

It's actually used in some examples, but overall UX can be improved.