JNIGen: An Easy Way to Access Platform APIs

Janak Mistry
Simform Engineering
6 min readFeb 15, 2023

Learn a new approach to system interoperability for easy access to native classes

With the release of Flutter 3.7.0 at Flutter Forward, we’re introduced to a ton of new features like 3D support, Dart3 Alpha, Element embedding, and Adaptive View. But the two outstanding features we really love are — JNIGen (Java Native Interface Generator) and FFIGen (Foreign Function Interface).

Both of these new approaches to system interoperability allow users to access native methods directly from dart code with the least effort. Sounds interesting, right??

In this article, we will see how FFIGen and JNIGen work and how to implement JNIGen in your project to use native libraries.

What approach do we have currently?

  • Currently, in Flutter, we have Platform channels that act as a bridge for sending and receiving messages between Dart code and native code.
  • Here, we have methods written in native code and our Flutter app as a client; that sends a message containing the method name to be executed, and then after, the response is sent back.
  • But with this approach, we need to write a handler on both sides to handle method calls and return responses. We also have to manually write and take care of method names and names of the channels on both sides. This is where FFIGen and JNIGen come into the picture.

Working Principle of FFI & JNIGen

  • FFIGen & JNIGen both work on the basic principle of using FFI to access methods of C files, but first, both convert native code to C. Here, JNIGen is used for android libraries and classes written in JAVA; similarly, FFIGen is used for Swift and Objective-C libraries.
  • In JNIGen, we pass our Java classes path to the generator, which first generates an intermediate layer of C code and then generates all the boilerplate in Dart for accessing C methods using FFI. The classes to be converted from a particular library and output files are all specified in a .yaml binding file, which we’ll see in a moment.
  • Similarly, FFIGen takes an Objective-C header file and generates Dart boilerplates for access methods as FFI can directly access the method of objective-C.

JNIGen Implementation for android Libraries

Let’s say I’ve written one class in android for Battery Utilities, which will provide me with details regarding battery information. Now I want to use this class in my flutter app.

We will use JNIGen to convert this code into C and generate a dart boilerplate to access getBatteryStatus() method in our Dart code.

1. Installation of tools

Before diving into code implementation, we first need to install some tools.

Installation of MVN:

  • Follow the below link for installing MVN.

Install clang-format

  • Use the below link for the installation guide.

2. Add Packages to pubspec.yaml

3. Binding File

  • It is basically a .yaml that contains configurations to be used by the JNIGen for identifying paths for native classes. The paths where generated files of C and Dart are written, and also maven configuration for specifying which third-party libraries are to be downloaded for generating code.
  • Follow the below instruction for creating a .yaml file.

Create a “jnigen.yaml” file in your source directory

Now, there are a few required configuration properties for this file.

* denotes Required

  1. output*: Is used to specify configuration regarding output file, i.e., paths and library name.
  • bindings_type: It takes two values ‘c_based’ or ‘dart_only’. If you want the generator to generate only the dart code without having C as the intermediate layer, you can use the dart_only option.

2. c (Not required if you are using the dart_only option)*:

  • path:* Path of directory to write C bindings
  • library_name*: Name of generated C Library

3. dart*:

  • path*: Specify the path for dart bindings
  • structure*: Specify how to structure generated dart binding, whether it should be a single file or to generate a different file for each class given in the classes property.

4. classes*: Specifies the list of classes to be converted.

If you use this property, make sure to use the source_path property, too, so that the generator knows where to look for the classes you want to convert.

5. source_path: We have to pass the list of directory paths where classes are placed.

Write the following configuration in “jnigen.yaml” file for converting ArthimeticUtils classes written in the android java directory.

I have used add_gradle_deps here so that it will run gradle stubs before generating JNIGen so that android compiled classpath is available before execution.

Properties of JNIGen configuration file:

Now, as we have our jnigen configuration file and android classes ready, we will run JNIGen to generate bindings.

Run the following command in your terminal:

dart run jnigen --config jnigen.yaml

Once the command is successful, you can see that all bindings files are generated and placed in the directories you specified in the “jnigen.yaml” file.

Now we can easily access those class and their methods in dart.

Now we need to perform one last step before we can run our app.

In app-level build.gradle file add the following line of code

Specify the path to your CMakeList.txt file; it will be in your directory where C bindings are placed. This step is to ensure that your c code is compiled into a native library. Next, Gradle will package this with your app.

Now you can run your app and use all those native functions.

But what if we want to use some maven libraries in our flutter project?

  • Let’s say I want to use amazon aws-java-sdk for accessing S3 storage.
  • For this, we will use maven_downloads properties. In maven_downloads, we have two options for libraries — source_deps for maven-based libraries and jar_only_deps for downloading jar files of libraries.

Add the following lines to your “jnigen.yaml” file for downloading the maven library.

Note: Currently JNIGen does not have a feature to extract classes from core libraries (i.e., androidx.*, android.*), so we need to extract those classes in a directory and add that directory’s path to the source_path property.

Now we will add classes that we want to use in our dart code to the “jnigen.yaml” file.

  • Many times your given classes will be in a classes.jar file. For such cases, you need to specify the classpath using the class_path property.

For this example, we do not need to set class_path.

And there you go! Now you can easily use any libraries and native method in your dart code.

Wrapping up

I hope this clarifies your understanding of how JNIGen works and how to implement it in your project for accessing native API easily without hassle.

NOTE: Currently, many features of this package are in the experimental phase and are prone to changes. So, stay tuned for the updates!

What’s Next

In the next part, we’ll explore FFIGen and describe how to implement it for accessing swift classes.

Here’s the repo for the Battery Utils example of JNIGen.

Follow the below link for the FFIGen implementation for iOS.

Happy Coding :)

--

--