Oops! Something went wrong while submitting the form.
We use cookies to improve your browsing experience on our website, to show you personalised content and to analize our website traffic. By browsing our website, you consent to our use of cookies. Read privacy policy.
Prepare to embark on a groundbreaking journey through the realms of Flutter as we uncover the remarkable new feature—JNIgen. In this blog, we pull back the curtain to reveal JNIgen’s transformative power, from simplifying intricate tasks to amplifying scalability; this blog serves as a guiding light along the path to a seamlessly integrated Flutter ecosystem.
As Flutter continues to mesmerize developers with its constant evolution, each release unveiling a treasure trove of thrilling new features, the highly anticipated Google I/O 2023 was an extraordinary milestone. Amidst the excitement, a groundbreaking technique was unveiled: JNIgen, offering effortless access to native code like never before.
Let this blog guide you towards a future where your Flutter projects transcend limitations and manifest into awe-inspiring creations.
1. What is JNIgen?
JNIgen, which stands for Java native interface generator, is an innovative tool that automates the process of generating Dart bindings for Android APIs accessible through Java or Kotlin code. By utilizing these generated bindings, developers can invoke Android APIs with a syntax that closely resembles native code.
With JNIgen, developers can seamlessly bridge the gap between Dart and the rich ecosystem of Android APIs. This empowers them to leverage the full spectrum of Android's functionality, ranging from system-level operations to platform-specific features. By effortlessly integrating with Android APIs through JNIgen-generated bindings, developers can harness the power of native code and build robust applications with ease.
1.1. Default approach:
In the current Flutter framework, we rely on Platform channels to establish a seamless communication channel between Dart code and native code. These channels serve as a bridge for exchanging messages and data.
Typically, we have a Flutter app acting as the client, while the native code contains the desired methods to be executed. The Flutter app sends a message containing the method name to the native code, which then executes the requested method and sends the response back to the Flutter app.
However, this approach requires the manual implementation of handlers on both the Dart and native code sides. It entails writing code to handle method calls and manage the exchange of responses. Additionally, developers need to carefully manage method names and channel names on both sides to ensure proper communication.
1.2. Working principle of JNIgen:
In JNIgen, our native code path is passed to the JNIgen generator, which initiates the generation of an intermediate layer of C code. This C code is followed by the necessary boilerplate in Dart, facilitating access to the C methods. All data binding and C files are automatically generated in the directory specified in the .yaml file, which we will explore shortly.
Consequently, as a Flutter application, our interaction is solely focused on interfacing with the newly generated Dart code, eliminating the need for direct utilization of native code.
1.3. Similar tools:
During the Google I/O 2023 event, JNIgen was introduced as a tool for native code integration. However, it is important to note that not all external libraries available on www.pub.dev are developed exclusively using channels. Another tool, FFIgen, was introduced earlier at Google I/O 2021 and serves a similar purpose. Both FFIgen and JNIgen function similarly, converting native code into intermediate C code with corresponding Dart dependencies to establish the necessary connections.
While JNIgen primarily facilitates communication between Android native code and Dart code, FFIgen has become the preferred choice for establishing communication between iOS native code and Dart code. Both tools are specifically designed to convert native code into intermediate code, enabling seamless interoperability within their respective platforms.
2. Configuration
Prior to proceeding with the code implementation, it is essential to set up and install the necessary tools.
2.1. System setup:
2.1.1 Install MVN
Windows
Download the Maven archive for Windows from the link here [download Binary zip archive]
After Extracting the zip file, you will get a folder with name “apache-maven-x.x.x”
Create a new folder with the name “ApacheMaven” in “C:\Program Files” and paste the above folder in it. [Your current path will be “C:\Program Files\ApacheMaven\apache-maven-x.x.x”]
Add the following entry in “Environment Variable” → “User Variables” M2 ⇒ “C:\Program Files\ApacheMaven\apache-maven-x.x.x\bin” M2_HOME ⇒ “C:\Program Files\ApacheMaven\apache-maven-x.x.x”
Add a new entry “%M2_HOME%\bin” in “path” variable
Mac
Download Maven archive for mac from the link here [download Binary tar.gz archive]
Run the following command where you have downloaded the *.tar.gz file
Figure 01 provides a visual representation of the .yaml file, which holds crucial configurations utilized by JNIgen. These configurations serve the purpose of identifying paths for native classes, as well as specifying the locations where JNIgen generates the resulting C and Dart files. Furthermore, the .yaml file allows for specifying Maven configurations, enabling the selection of specific third-party libraries that need to be downloaded to facilitate code generation.
By leveraging the power of the .yaml file, developers gain control over the path identification process and ensure that the generated code is placed in the desired locations. Additionally, the ability to define Maven configurations grants flexibility in managing dependencies, allowing the seamless integration of required third-party libraries into the generated code. This comprehensive approach enables precise control and customization over the code generation process, enhancing the overall efficiency and effectiveness of the development workflow.
Let's explore the properties that we have utilized within the .yaml file (Please refer "3.2.2. code implementation" section's example for better understanding):
android_sdk_config:
When the value of a specific property is set to "true," it triggers the execution of a Gradle stub during the invocation of JNIgen. Additionally, it includes the Android compile classpath in the classpath of JNIgen. However, to ensure that all dependencies are cached appropriately, it is necessary to have previously performed a release build.
output
As the name implies, the "output" section defines the configuration related to the generation of intermediate code. This section plays a crucial role in determining how the intermediate code will be generated and organized.
c >> library_name && c >> path: Here we are setting details for c_based binding code.
dart >> path && dart >> structure:
Here we are defining configuration for dart_based binding code.
source_path:
These are specific directories that are scanned during the process of locating the relevant source files.
classes:
By providing a comprehensive list of classes or packages, developers can effectively control the scope of the code generation process. This ensures that the binding code is generated only for the desired components, minimizing unnecessary code generation
By utilizing these properties within the .yaml file, developers can effectively control various aspects of the code generation process, including path identification, code organization, and dependency management. To get more in-depth information, please check out the official documentation here.
2.3. Generate bindings files:
Once this setup is complete, the final step for JNIgen is to obtain the jar file that will be scanned to generate the required bindings. To initiate the process of generating the Android APK, you can execute the following command:
With this configuration, we are specifying the path for the CMake file that will been generated by JNIgen.This path declaration is crucial for identifying the location of the generated CMake file within the project structure.
With the completion of the aforementioned steps, you are now ready to run your application and leverage all the native functions that have been integrated.
3. Sample Project
To gain hands-on experience and better understand the JNIgen, let's create a small project together. Follow the steps below to get started.
Let's start with:
3.1. Packages & directories:
3.1.1 Create a project using the following command:
After all of this, when we launch our app, we will see information about our Android device.
4. Result
For your convenience, the complete code for the project can be found here. Feel free to refer to this code repository for a comprehensive overview of the implementation details and to access the entirety of the source code.
5. Conclusion
In conclusion, we explored the limitations of the traditional approach to native API access in Flutter for mid to large-scale projects. Through our insightful exploration of JNIgen's working principles, we uncovered its remarkable potential for simplifying the native integration process.
By gaining a deep understanding of JNIgen's inner workings, we successfully developed a sample project and provided detailed guidance on the essential setup requirements. Armed with this knowledge, developers can embrace JNIgen's capabilities to streamline their native integration process effectively.
We can say that JNIgen is a valuable tool for Flutter developers seeking to combine the power of Flutter's cross-platform capabilities with the flexibility and performance benefits offered by native code. It empowers developers to build high-quality apps that seamlessly integrate platform-specific features and existing native code libraries, ultimately enhancing the overall user experience.
Hopefully, this blog post has inspired you to explore the immense potential of JNIgen in your Flutter applications. By harnessing the JNIgen, we can open doors to new possibilities.
Thank you for taking the time to read through this blog!
Prepare to embark on a groundbreaking journey through the realms of Flutter as we uncover the remarkable new feature—JNIgen. In this blog, we pull back the curtain to reveal JNIgen’s transformative power, from simplifying intricate tasks to amplifying scalability; this blog serves as a guiding light along the path to a seamlessly integrated Flutter ecosystem.
As Flutter continues to mesmerize developers with its constant evolution, each release unveiling a treasure trove of thrilling new features, the highly anticipated Google I/O 2023 was an extraordinary milestone. Amidst the excitement, a groundbreaking technique was unveiled: JNIgen, offering effortless access to native code like never before.
Let this blog guide you towards a future where your Flutter projects transcend limitations and manifest into awe-inspiring creations.
1. What is JNIgen?
JNIgen, which stands for Java native interface generator, is an innovative tool that automates the process of generating Dart bindings for Android APIs accessible through Java or Kotlin code. By utilizing these generated bindings, developers can invoke Android APIs with a syntax that closely resembles native code.
With JNIgen, developers can seamlessly bridge the gap between Dart and the rich ecosystem of Android APIs. This empowers them to leverage the full spectrum of Android's functionality, ranging from system-level operations to platform-specific features. By effortlessly integrating with Android APIs through JNIgen-generated bindings, developers can harness the power of native code and build robust applications with ease.
1.1. Default approach:
In the current Flutter framework, we rely on Platform channels to establish a seamless communication channel between Dart code and native code. These channels serve as a bridge for exchanging messages and data.
Typically, we have a Flutter app acting as the client, while the native code contains the desired methods to be executed. The Flutter app sends a message containing the method name to the native code, which then executes the requested method and sends the response back to the Flutter app.
However, this approach requires the manual implementation of handlers on both the Dart and native code sides. It entails writing code to handle method calls and manage the exchange of responses. Additionally, developers need to carefully manage method names and channel names on both sides to ensure proper communication.
1.2. Working principle of JNIgen:
In JNIgen, our native code path is passed to the JNIgen generator, which initiates the generation of an intermediate layer of C code. This C code is followed by the necessary boilerplate in Dart, facilitating access to the C methods. All data binding and C files are automatically generated in the directory specified in the .yaml file, which we will explore shortly.
Consequently, as a Flutter application, our interaction is solely focused on interfacing with the newly generated Dart code, eliminating the need for direct utilization of native code.
1.3. Similar tools:
During the Google I/O 2023 event, JNIgen was introduced as a tool for native code integration. However, it is important to note that not all external libraries available on www.pub.dev are developed exclusively using channels. Another tool, FFIgen, was introduced earlier at Google I/O 2021 and serves a similar purpose. Both FFIgen and JNIgen function similarly, converting native code into intermediate C code with corresponding Dart dependencies to establish the necessary connections.
While JNIgen primarily facilitates communication between Android native code and Dart code, FFIgen has become the preferred choice for establishing communication between iOS native code and Dart code. Both tools are specifically designed to convert native code into intermediate code, enabling seamless interoperability within their respective platforms.
2. Configuration
Prior to proceeding with the code implementation, it is essential to set up and install the necessary tools.
2.1. System setup:
2.1.1 Install MVN
Windows
Download the Maven archive for Windows from the link here [download Binary zip archive]
After Extracting the zip file, you will get a folder with name “apache-maven-x.x.x”
Create a new folder with the name “ApacheMaven” in “C:\Program Files” and paste the above folder in it. [Your current path will be “C:\Program Files\ApacheMaven\apache-maven-x.x.x”]
Add the following entry in “Environment Variable” → “User Variables” M2 ⇒ “C:\Program Files\ApacheMaven\apache-maven-x.x.x\bin” M2_HOME ⇒ “C:\Program Files\ApacheMaven\apache-maven-x.x.x”
Add a new entry “%M2_HOME%\bin” in “path” variable
Mac
Download Maven archive for mac from the link here [download Binary tar.gz archive]
Run the following command where you have downloaded the *.tar.gz file
Figure 01 provides a visual representation of the .yaml file, which holds crucial configurations utilized by JNIgen. These configurations serve the purpose of identifying paths for native classes, as well as specifying the locations where JNIgen generates the resulting C and Dart files. Furthermore, the .yaml file allows for specifying Maven configurations, enabling the selection of specific third-party libraries that need to be downloaded to facilitate code generation.
By leveraging the power of the .yaml file, developers gain control over the path identification process and ensure that the generated code is placed in the desired locations. Additionally, the ability to define Maven configurations grants flexibility in managing dependencies, allowing the seamless integration of required third-party libraries into the generated code. This comprehensive approach enables precise control and customization over the code generation process, enhancing the overall efficiency and effectiveness of the development workflow.
Let's explore the properties that we have utilized within the .yaml file (Please refer "3.2.2. code implementation" section's example for better understanding):
android_sdk_config:
When the value of a specific property is set to "true," it triggers the execution of a Gradle stub during the invocation of JNIgen. Additionally, it includes the Android compile classpath in the classpath of JNIgen. However, to ensure that all dependencies are cached appropriately, it is necessary to have previously performed a release build.
output
As the name implies, the "output" section defines the configuration related to the generation of intermediate code. This section plays a crucial role in determining how the intermediate code will be generated and organized.
c >> library_name && c >> path: Here we are setting details for c_based binding code.
dart >> path && dart >> structure:
Here we are defining configuration for dart_based binding code.
source_path:
These are specific directories that are scanned during the process of locating the relevant source files.
classes:
By providing a comprehensive list of classes or packages, developers can effectively control the scope of the code generation process. This ensures that the binding code is generated only for the desired components, minimizing unnecessary code generation
By utilizing these properties within the .yaml file, developers can effectively control various aspects of the code generation process, including path identification, code organization, and dependency management. To get more in-depth information, please check out the official documentation here.
2.3. Generate bindings files:
Once this setup is complete, the final step for JNIgen is to obtain the jar file that will be scanned to generate the required bindings. To initiate the process of generating the Android APK, you can execute the following command:
With this configuration, we are specifying the path for the CMake file that will been generated by JNIgen.This path declaration is crucial for identifying the location of the generated CMake file within the project structure.
With the completion of the aforementioned steps, you are now ready to run your application and leverage all the native functions that have been integrated.
3. Sample Project
To gain hands-on experience and better understand the JNIgen, let's create a small project together. Follow the steps below to get started.
Let's start with:
3.1. Packages & directories:
3.1.1 Create a project using the following command:
After all of this, when we launch our app, we will see information about our Android device.
4. Result
For your convenience, the complete code for the project can be found here. Feel free to refer to this code repository for a comprehensive overview of the implementation details and to access the entirety of the source code.
5. Conclusion
In conclusion, we explored the limitations of the traditional approach to native API access in Flutter for mid to large-scale projects. Through our insightful exploration of JNIgen's working principles, we uncovered its remarkable potential for simplifying the native integration process.
By gaining a deep understanding of JNIgen's inner workings, we successfully developed a sample project and provided detailed guidance on the essential setup requirements. Armed with this knowledge, developers can embrace JNIgen's capabilities to streamline their native integration process effectively.
We can say that JNIgen is a valuable tool for Flutter developers seeking to combine the power of Flutter's cross-platform capabilities with the flexibility and performance benefits offered by native code. It empowers developers to build high-quality apps that seamlessly integrate platform-specific features and existing native code libraries, ultimately enhancing the overall user experience.
Hopefully, this blog post has inspired you to explore the immense potential of JNIgen in your Flutter applications. By harnessing the JNIgen, we can open doors to new possibilities.
Thank you for taking the time to read through this blog!
Velotio Technologies is an outsourced software product development partner for top technology startups and enterprises. We partner with companies to design, develop, and scale their products. Our work has been featured on TechCrunch, Product Hunt and more.
We have partnered with our customers to built 90+ transformational products in areas of edge computing, customer data platforms, exascale storage, cloud-native platforms, chatbots, clinical trials, healthcare and investment banking.
Since our founding in 2016, our team has completed more than 90 projects with 220+ employees across the following areas:
Building web/mobile applications
Architecting Cloud infrastructure and Data analytics platforms