| [](https://github.com/dart-lang/native/actions/workflows/jnigen.yaml) | |
| [](https://app.codecov.io/gh/dart-lang/native) | |
| [](https://pub.dev/packages/jnigen) | |
| [](https://pub.dev/packages/jnigen/publisher) | |
| ## Introduction | |
| Bindings generator to call Java APIs from Dart code via `dart:ffi` and JNI. | |
| JNIgen scans compiled JAR files or Java source code to generate a description | |
| of the API, then uses that to generate Dart bindings. The Dart bindings call the | |
| C bindings, which in-turn call the Java functions through JNI. Shared | |
| functionality and base classes are provided through the support library, | |
| `package:jni`. | |
| ## Getting Started | |
| This guide demonstrates how to call a custom Java API from a Flutter | |
| application targeting Android. It assumes that Flutter has been set up to build | |
| apps for Android | |
| ([instructions](https://docs.flutter.dev/platform-integration/android/setup)) | |
| and that the Flutter app was created via `flutter create in_app_java`. If you | |
| encounter any issues running the commands below, check the | |
| [Requirements](#requirements) section below for additional platform-specific | |
| instructions. | |
| 1. Run `flutter pub get` at least once to get the dependencies of your app. | |
| This is necessary so that JNIgen can get the classpaths of Android Gradle | |
| libraries. | |
| 2. Add the helper package `package:jni` as a dependency and the bindings | |
| generator `package:jnigen` as a dev_dependency to the pubspec of your app by | |
| running: `flutter pub add jni dev:jnigen`. | |
| 3. Write the Java code and place it in the `android/` subproject of your app. | |
| For this example, we'll place the following code under | |
| `android/app/src/main/java/com/example/in_app_java/AndroidUtils.java`. It | |
| defines a simple Java API to show a native Android toast. | |
| ```java | |
| package com.example.in_app_java; | |
| import android.app.Activity; | |
| import android.widget.Toast; | |
| import androidx.annotation.Keep; | |
| @Keep | |
| public abstract class AndroidUtils { | |
| private AndroidUtils() {} // Hide constructor | |
| public static void showToast(Activity mainActivity, CharSequence text, int duration) { | |
| mainActivity.runOnUiThread(() -> Toast.makeText(mainActivity, text, duration).show()); | |
| } | |
| } | |
| ``` | |
| 4. To generate the bindings, we will write a script using `package:jnigen` and | |
| place it under `tool/jnigen.dart`. The script constructs a `JniGenerator` object | |
| and calls `generate()`. The `JniGenerator` object configures the | |
| bindings that JNIgen will generate for the Java code. Refer to the code | |
| comments below and the API docs to learn more about available configuration | |
| options. | |
| ```dart | |
| import 'dart:io'; | |
| import 'package:jnigen/jnigen.dart'; | |
| Future<void> main() async { | |
| final packageRoot = Platform.script.resolve('../'); | |
| final generator = JniGenerator( | |
| input: Input( | |
| // Required. List of classes or packages for which bindings should be generated. | |
| classes: ['com.example.in_app_java'], | |
| // Optional. List of directories that contain the source files for which to generate bindings. | |
| sourcePath: [packageRoot.resolve('android/app/src/main/java')], | |
| // Optional. Configuration to search for Android SDK libraries. | |
| androidSdk: AndroidSdk(addGradleDeps: true), | |
| ), | |
| output: Output( | |
| dart: DartOutput( | |
| // Required. Output path for generated bindings. | |
| path: packageRoot.resolve('lib/android_utils.g.dart'), | |
| // Optional. Write bindings into a single file (instead of one file per class). | |
| structure: OutputStructure.singleFile, | |
| ), | |
| ), | |
| ); | |
| await generator.generate(); | |
| } | |
| ``` | |
| 5. Run the script with `dart run tool/jnigen.dart` to generate the bindings. | |
| This will create the output `lib/android_utils.g.dart` file, which can be | |
| imported by Dart code to access the Java APIs. This command must be re-run | |
| whenever the JNIgen configuration (in `tool/jnigen.dart`) or the Java | |
| sources for which bindings are generated change. | |
| 6. Import `android_utils.g.dart` in your Flutter app and call the generated | |
| methods to access the native Java API: | |
| ```dart | |
| import 'package:jni/jni.dart'; | |
| import 'android_utils.g.dart'; | |
| // ... | |
| void showToast() { | |
| JObject activity = JObject.fromReference(Jni.getCurrentActivity()); | |
| final message = 'This is a native toast shown from a Flutter app via JNI.'; | |
| AndroidUtils.showToast(activity, message.toJString(), 0); | |
| } | |
| ``` | |
| That's it! Run your app with `flutter run` on an Android device to see it in | |
| action. | |
| The complete example can be found in | |
| [example/in_app_java](example/in_app_java), which adds a few more classes | |
| to demonstrate using classes from Gradle JAR and source dependencies. | |
| ## More Examples | |
| Additional examples showcasing how JNIgen can be used in different scenarios | |
| (e.g. to generate bindings for Kotlin) can be found in the [example](example/) | |
| directory. | |
| ## Supported platforms | |
| | Platform | Dart Standalone | Flutter | | |
| |----------|-----------------|-----------| | |
| | Android | n/a | Supported | | |
| | Linux | Supported | Supported | | |
| | Windows | Supported | Supported | | |
| | macOS | Supported | Not Yet | | |
| On Android, the Flutter application runs embedded in the Android JVM. On other | |
| platforms, a JVM needs to be explicitly spawned using `Jni.spawn`. The helper | |
| package `package:jni` provides the infrastructure for initializing and managing | |
| the JNI on both Android and non-Android platforms. | |
| ## Dart (standalone) target | |
| `package:jni` is an FFI plugin containing native code, and any bindings | |
| generated from JNIgen contain native code too. | |
| On Flutter targets, native libraries are built automatically and bundled. On | |
| standalone platforms, no such infrastructure exists yet. As a stopgap solution, | |
| running `dart run jni:setup` in a target directory builds all JNI native | |
| dependencies of the package into `build/jni_libs`. | |
| To start a JVM, call `Jni.spawn`. It's assumed that all dependencies are built | |
| into the same target directory, so that once JNI is initialized, generated | |
| bindings can load their respective C libraries automatically. | |
| If a custom build path has been set for the dynamic libraries built by | |
| `dart run jni:setup --build-path path/to/dylib`, the same path must be passed to | |
| `Jni.spawn`. Also, anytime a new Dart isolate is spawned, the directory must be | |
| set again using `Jni.setDylibDir`. | |
| ## Requirements | |
| ### SDK | |
| Flutter SDK is required. | |
| Dart standalone target is supported, but due to some problems with pubspec | |
| format, the `dart` command must be from the Flutter SDK and not Dart SDK. See | |
| [dart-lang/pub#3563](https://github.com/dart-lang/pub/issues/3563). | |
| ### Java tooling | |
| JNIgen requires a JDK to be installed. JNIgen officially supports Java | |
| versions 17 to 21. | |
| By default, JNIgen uses the JDK configured in Flutter (which is typically | |
| Android Studio's bundled JDK). If that fails (eg because Flutter is not | |
| installed, or isn't set up for Android development) it falls back to | |
| relying on the `JAVA_HOME` and `PATH` environment variables. | |
| #### Windows | |
| On windows, append the path of `jvm.dll` in your JDK installation to PATH. For | |
| example, on PowerShell: | |
| ```powershell | |
| $env:Path += ";${env:JAVA_HOME}\bin\server" | |
| ``` | |
| The above will only add `jvm.dll` to `PATH` for the current PowerShell session, | |
| use the Control Panel to add it to the path permanently. | |
| If `JAVA_HOME` not set, find the `java.exe` executable and set the environment | |
| variable in Control Panel. If java is installed through a package manager, there | |
| may be a more automatic way to do this. (e.g. `scoop reset`). | |
| ### C tooling | |
| CMake and a standard C toolchain are required to build `package:jni`. | |
| ## Configuration | |
| JNIgen is configured using a Dart script, typically placed under | |
| `tool/jnigen.dart` and executed via `dart run tool/jnigen.dart`. | |
| The script should instantiate a `JniGenerator` with your desired configuration | |
| and call `await generator.generate()`. | |
| ### Example | |
| ```dart | |
| import 'dart:io'; | |
| import 'package:jnigen/jnigen.dart'; | |
| Future<void> main() async { | |
| final packageRoot = Platform.script.resolve('../'); | |
| final generator = JniGenerator( | |
| // Required. Output path and structure for the generated bindings. | |
| output: Output( | |
| dart: DartOutput( | |
| path: packageRoot.resolve('lib/src/generated_bindings.dart'), | |
| structure: OutputStructure.singleFile, | |
| ), | |
| ), | |
| // Where to find Java classes and source files. | |
| input: Input( | |
| // Required. Fully-qualified names of classes or packages to generate bindings for. | |
| classes: ['com.example.MyClass'], | |
| // Optional. Directories to search for Java source files. | |
| sourcePath: [packageRoot.resolve('android/app/src/main/java')], | |
| ), | |
| ); | |
| await generator.generate(); | |
| } | |
| ``` | |
| Run the script to generate bindings: | |
| ```shell | |
| dart run tool/jnigen.dart | |
| ``` | |
| See the [examples](https://github.com/dart-lang/native/tree/main/pkgs/jnigen/example) | |
| and [API documentation](https://pub.dev/documentation/jnigen/latest/jnigen/) | |
| for more information. | |
| ## FAQs | |
| #### I am getting ClassNotFoundError at runtime. | |
| JNIgen does not handle getting the classes into application. It has to be done | |
| by target-specific mechanism. Such as adding a Gradle dependency on Android, or | |
| manually providing classpath to `Jni.spawn` on desktop / standalone targets. | |
| On Android, `proguard` prunes classes which it deems inaccessible. Since JNI | |
| class lookup happens in runtime, this leads to ClassNotFound errors in release | |
| mode even if the dependency is included in Gradle. | |
| [in_app_java example](example/in_app_java/) discusses two mechanisms to prevent | |
| this: using `Keep` annotation (`androidx.annotation.Keep`) for the code written | |
| in the application itself, and | |
| [proguard-rules file](example/in_app_java/android/app/proguard-rules.pro) for | |
| external libraries. | |
| Lastly, some libraries such as `java.awt` do not exist in Android. Attempting to | |
| use libraries which depend on them can also lead to ClassNotFound errors. | |
| #### JNIgen is not finding classes. | |
| Ensure you are providing the correct source and class paths, and they follow the | |
| standard directory structure. If your class name is `com.abc.MyClass`, | |
| `MyClass` must be in `com/abc/MyClass.java` relative to one of the source paths, | |
| or `com/abc/MyClass.class` relative to one of the class paths specified in the configuration. | |
| If the classes are in JAR file, make sure to provide the path to the JAR file | |
| itself, and not to the containing directory. | |
| #### JNIgen is unable to parse sources. | |
| If the errors are similar to `symbol not found`, ensure all dependencies of the | |
| source are available. If such dependency is compiled, it can be included in | |
| `class_path`. | |
| #### Generate bindings for built-in types | |
| For your convenience, a number of built-in Java types, for example, many of | |
| those in `java.lang` and `java.util`, are provided to your build by `jni` by | |
| default. | |
| Those don't need to be included in the classes block, and will error if you do | |
| so, with the following error: | |
| `Fatal: Trying to re-import the generated classes`. | |
| For any other types that are in core Java, you can add them in your `classes` | |
| block and bindings will be generated when you run the generate bindings task. | |
| Below is an example showing how you might generate bindings for several classes | |
| in `java.time.*` and a `java.lang` class that is not included by default: | |
| ```dart | |
| final generator = JniGenerator( | |
| input: Input( | |
| classes: [ | |
| 'java.time.Instant', | |
| 'java.time.ZoneOffset', | |
| 'java.time.ZonedDateTime', | |
| 'java.lang.Math', | |
| // 'java.lang.Integer', // Will error, already included in binary | |
| ], | |
| ), | |
| output: Output( | |
| dart: DartOutput( | |
| path: packageRoot.resolve('lib/gen/'), | |
| ), | |
| ), | |
| ); | |
| await generator.generate(); | |
| ``` | |
| #### How are classes mapped into bindings? | |
| Each Java class generates a subclass of `JObject` class, which wraps a `jobject` | |
| reference in JNI. Nested classes use `_` as separator, `Example.NestedClass` | |
| will be mapped to `Example_NestedClass`. | |
| #### Does `JObject` hold a local or global reference? Does it need to be manually released? | |
| Each Java object returned into Dart creates a JNI global reference. Reference | |
| deletion is taken care of by `NativeFinalizer` and that's usually sufficient. | |
| It's a good practice to keep the interface between languages sparse. However, if | |
| there's a need to create several references (e.g. in a loop), you can use the | |
| FFI Arena mechanism (`using` function) and `releasedBy` method, or manually | |
| release the object using `release` method. | |
| ## Android core libraries | |
| These days, Android projects depend heavily on AndroidX and other libraries | |
| downloaded via Gradle. We have a tracking issue to improve detection of Android | |
| SDK and dependencies ([#793](https://github.com/dart-lang/native/issues/793)). | |
| Currently, we can fetch the JAR dependencies of an Android project by running a | |
| Gradle stub, if `AndroidSdk(addGradleDeps: true)` is specified. However, | |
| core libraries (the `android.**` namespace) are not downloaded through Gradle. | |
| The core libraries are shipped as stub JARs with the Android SDK. | |
| (`$SDK_ROOT/platforms/android-$VERSION/android-stubs-src.jar`). Currently, we | |
| don't have an automatic mechanism for using these. You can unpack this JAR | |
| manually into some directory and provide it as a source path. | |
| Having said that, there are two caveats to this caveat: | |
| * SDK stubs after version 28 are incomplete. OpenJDK Doclet API we use to | |
| generate API summaries will error on incomplete sources. | |
| * The API can't process the `java.**` namespaces in the Android SDK stubs, | |
| because it expects a module layout. So if you want to generate bindings for, | |
| say, `java.lang.Math`, you cannot use the Android SDK stubs. OpenJDK sources | |
| can be used instead. See | |
| [Generate bindings for built-in types](#generate-bindings-for-built-in-types) | |
| above for instructions on how to use OpenJDK sources. | |
| The JAR files (`$SDK_ROOT/platforms/android-$VERSION/android.jar`) can be used | |
| instead. But compiled JARs do not include JavaDoc and method parameter names. | |
| This JAR is automatically included by Gradle when | |
| `AndroidSdk(addGradleDeps: true)` is specified. | |
| ## Contributing | |
| See [CONTRIBUTING.md](../../CONTRIBUTING.md) in the root of the repository for | |
| information on how to contribute. | |