<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Marco Domingos</title>
    <description>The latest articles on DEV Community by Marco Domingos (@marco4763).</description>
    <link>https://dev.to/marco4763</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1049099%2F99a8bc92-5104-4947-9948-3befe3fa17ba.jpeg</url>
      <title>DEV Community: Marco Domingos</title>
      <link>https://dev.to/marco4763</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/marco4763"/>
    <language>en</language>
    <item>
      <title>The Ultimate Showdown: MethodChannel vs. JNI - Unraveling the Secrets of Native Integration in Flutter</title>
      <dc:creator>Marco Domingos</dc:creator>
      <pubDate>Wed, 14 Jun 2023 15:02:22 +0000</pubDate>
      <link>https://dev.to/marco4763/the-ultimate-showdown-methodchannel-vs-jni-unraveling-the-secrets-of-native-integration-in-flutter-k49</link>
      <guid>https://dev.to/marco4763/the-ultimate-showdown-methodchannel-vs-jni-unraveling-the-secrets-of-native-integration-in-flutter-k49</guid>
      <description>&lt;p&gt;We recently had &lt;strong&gt;Google I/O 2023&lt;/strong&gt; and with them we received incredible news for &lt;strong&gt;Flutter&lt;/strong&gt;, in one of them, we were introduced to a new approach to System Interoperability with Native Android, more specific: &lt;strong&gt;JNIgen&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is JNIgen?
&lt;/h2&gt;

&lt;p&gt;For developers familiar with &lt;strong&gt;Java&lt;/strong&gt;, the name &lt;strong&gt;JNI&lt;/strong&gt; is not really new, &lt;strong&gt;JNI **being a **Java Native Interface&lt;/strong&gt; that allows Java/Kotlin code to interact with written native code in C/C++ and even Assembly in some cases.&lt;/p&gt;

&lt;p&gt;In a more visual way, in the image below we can see that the &lt;strong&gt;JNI&lt;/strong&gt; works as a bridge between the codes:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mPlvnm8Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/clrzaqnmr9v8k1yshhtx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mPlvnm8Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/clrzaqnmr9v8k1yshhtx.png" alt="JNI Bridge" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Therefore, &lt;strong&gt;JNIgen&lt;/strong&gt; follows the same principle plus the generator, that is, this Dart Interoperability tool was designed to generate all the necessary bindings by analyzing the java code, generating the native code in C/C++ and the bindings in Dart, all this automatically.&lt;/p&gt;
&lt;h2&gt;
  
  
  Ok, but why use JNIgen and not just use MethodChannel?
&lt;/h2&gt;

&lt;p&gt;To answer this question, we first need to see the differences between each in practice.&lt;/p&gt;

&lt;p&gt;For this example, we will use the &lt;a href="https://developers.google.com/ml-kit/vision/barcode-scanning?hl=en"&gt;ML Kit Google Code Scanner API&lt;/a&gt;. This should be the end result:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pHc5u3Aa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v4x70iyb5gbtrp2ueqqc.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pHc5u3Aa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v4x70iyb5gbtrp2ueqqc.gif" alt="GIF of final result" width="600" height="1275"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So we'll do it both ways: &lt;strong&gt;MethodChannel&lt;/strong&gt; and &lt;strong&gt;JNIgen&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  1 - MethodChannel
&lt;/h2&gt;

&lt;p&gt;There is no need to define these basic steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creating a flutter project(&lt;a href="https://docs.flutter.dev/get-started/test-drive?tab=androidstudio"&gt;Read more here&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Adding the necessary gradle dependencies for the ML Kit API (&lt;a href="https://developers.google.com/ml-kit/vision/barcode-scanning/code-scanner?hl=pt-br"&gt;Read more here&lt;/a&gt; )&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After following the basic steps, this should be your MainActivity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class MainActivity: FlutterActivity() {

  override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine)
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;I used Kotlin as my native language, for some it might look different(&lt;a href="https://docs.flutter.dev/platform-integration/platform-channels?tab=type-mappings-kotlin-tab"&gt;More here&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let's continue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Steps:
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1 - Creating the communication channel:&lt;/strong&gt;&lt;br&gt;
Now we must define the name of our channel like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private val CHANNEL = "com.example.com/Scanner"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we need to define the function responsible for the handler that the method will call, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
      call, result -&amp;gt;
      // This method is invoked on the main thread.
      // TODO
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the end, the code will look like this;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class MainActivity: FlutterActivity() {
private val CHANNEL = "com.example.com/Scanner"

  override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine)
    MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
      call, result -&amp;gt;
      // This method is invoked on the main thread.
      // TODO
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2- Adding barcode function:&lt;/strong&gt;&lt;br&gt;
Now, if you've read the ML Kit guide above, the code below is self-explanatory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val scanner = GmsBarcodeScanning.getClient(this)
scanner.startScan()
    .addOnSuccessListener { barcode -&amp;gt;
        // Task completed successfully
    }
    .addOnCanceledListener {
        // Task canceled
    }
    .addOnFailureListener { e -&amp;gt;
        // Task failed with an exception
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well, we know that the response we will get from this method is not synchronous, so we will launch this method inside a new Thread so that we can handle the response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val scanner = GmsBarcodeScanning.getClient(this)
object : Thread() {
scanner.startScan()
    .addOnSuccessListener { barcode -&amp;gt;
        // Task completed successfully
    }
    .addOnCanceledListener {
        // Task canceled
    }
    .addOnFailureListener { e -&amp;gt;
        // Task failed with an exception
    }
}.start()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can get the response and return it as a response from the channel we created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class MainActivity: FlutterActivity() {
private val CHANNEL = "com.example.com/Scanner"

  override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine)
    MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
      call, result -&amp;gt;
      if (call.method == "getBarCode") {
                val scanner = GmsBarcodeScanning.getClient(this)
                object : Thread() {
                    override fun run() {
                        scanner.startScan()
                            .addOnSuccessListener { barcode -&amp;gt;
                                result.success(barcode.rawValue)
                            }
                            .addOnCanceledListener { -&amp;gt;
                                result.success("")
                            }
                            .addOnFailureListener { e -&amp;gt;
                                result.error("Exception", "Found Exception", e)
                            };
                    }
                }.start()
            }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Almost forgot, &lt;strong&gt;getBarCode&lt;/strong&gt; is the method we'll call to access the Barcode Scanner from our dart code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3- Configuring the communication Dart Channel:&lt;/strong&gt;&lt;br&gt;
You probably got a main.dart file with a default example code, you can remove any unnecessary code, for this case we will work with the StatefulWidget class, first let's define the methodChannel with the same channel name as we gave it in our native code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;static const platform = MethodChannel('com.example.com/Scanner');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So let's create a method that will handle the communication, we can call this function &lt;strong&gt;_getBarCode&lt;/strong&gt;, this method must be a &lt;strong&gt;Future&lt;/strong&gt;, as we mentioned before, the scanner is not synchronous, so the return will not be immediate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Future&amp;lt;void&amp;gt; _getScanner() async {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Within this method we have the code responsible for calling and handling the response from the channel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await platform.invokeMethod('getBarCode');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside the &lt;strong&gt;invokeMethod&lt;/strong&gt; we pass as an argument the method that we added in the native code that calls the Barcode function. Now, we know that the method will return a string with the code value we read, so we can wrap it in a String:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;String scanned = await platform.invokeMethod('getBarCode');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we must wrap this inside a function and we must add a way to handle any exceptions that may occur, in the end this should be the end result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;void _getScanner() async {
  String? scanned;
  try {
    scanned = await platform.invokeMethod('getBarCode');
  } on PlatformException catch (e) {
    scanned = "";
  }

  setState(() {
    result = scanned ?? "";
  });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can call this function in our code, which should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyScannerPage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyScannerPage extends StatefulWidget {
  const MyScannerPage({super.key, required this.title});

  final String title;

  @override
  State&amp;lt;MyScannerPage&amp;gt; createState() =&amp;gt; _MyScannerPageState();
}

class _MyScannerPageState extends State&amp;lt;MyScannerPage&amp;gt; {
  static const platform = MethodChannel('com.example.com/Scanner');
  String result = "";

  void _getScanner() async {
    String? scanned;
    try {
      scanned = await platform.invokeMethod('getBarCode');
    } on PlatformException catch (e) {
      scanned = "";
    }

    setState(() {
      result = scanned ?? "";
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              "ScanResult: ",
              style: TextStyle(
                color: Colors.black,
                fontWeight: FontWeight.bold,
              ),
            ),
            Text(
              result,
              style: const TextStyle(color: Colors.black),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          _getScanner();
        },
        tooltip: 'Scan',
        child: const Icon(Icons.qr_code_scanner),
      ), 
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And with that we arrive at the result we were looking for:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pHc5u3Aa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v4x70iyb5gbtrp2ueqqc.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pHc5u3Aa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v4x70iyb5gbtrp2ueqqc.gif" alt="Image description" width="600" height="1275"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  2- JNIGen
&lt;/h2&gt;

&lt;p&gt;Now, let's get the same result but with a different approach, again, no need to explain the basic steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creating a flutter project(&lt;a href="https://docs.flutter.dev/get-started/test-drive?tab=androidstudio"&gt;Read more here&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Adding the necessary gradle dependencies for the ML Kit API (&lt;a href="https://developers.google.com/ml-kit/vision/barcode-scanning/code-scanner?hl=pt-br"&gt;Read more here&lt;/a&gt; )&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Steps:
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1- Creating a class with scanner code&lt;/strong&gt;&lt;br&gt;
As with &lt;strong&gt;MethodChannel&lt;/strong&gt;, you'll create a native class with the code that will handle the QrCode scan, something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Scanner {
    suspend fun getCode(context: Context): String? {
        val scanner = GmsBarcodeScanning.getClient(context)
        return suspendCoroutine&amp;lt;String?&amp;gt; { continuation -&amp;gt;
            scanner.startScan()
                .addOnSuccessListener { barcode -&amp;gt;
                    continuation.resume(barcode.rawValue)
                }
                .addOnCanceledListener {
                    continuation.resume("")
                }
                .addOnFailureListener { e -&amp;gt;
                    continuation.resumeWithException(e)
                }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference here is that we can do this in a normal class, normal function without the need for a channel or a handler.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2- Adding JNIgen to the project&lt;/strong&gt;&lt;br&gt;
To work with &lt;strong&gt;JNIgen&lt;/strong&gt;, we need to add 2 libs to our &lt;strong&gt;pubspec.yaml&lt;/strong&gt;, we can do this with just a few commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flutter pub add jni dev:jnigen
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will add &lt;strong&gt;jni&lt;/strong&gt; as a dependency and &lt;strong&gt;jnigen&lt;/strong&gt; as a dev_dependence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3- Creating the configuration file&lt;/strong&gt;&lt;br&gt;
Now that we have the &lt;strong&gt;JNIgen&lt;/strong&gt; dependency in our project, we can define the configuration we want, for this we need to create a new &lt;em&gt;yaml&lt;/em&gt; file in the root of the project, you can define the name whatever you want, like *&lt;em&gt;jnigen.yaml&lt;/em&gt; *, inside this file we would have the code below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;android_sdk_config:
  # The add_gradle_deps: true property allow the bindings to compile the Android dependencies and classpath to the jnigen
  add_gradle_deps: true

  # The suspend_fun_to_async: true property is pretty explanatory itself, but to resume, it will convert Kotlin suspend functions to Dart async functions
suspend_fun_to_async: true

output:
  # In the output we set two languages, the first will be native code in "c"  and the other will be the one we use in Flutter, meaning "Dart"
  c:
    # In this property you will set the name of the library we are creating, this way jnigen will set the generated code according to this name.
    library_name: scanner
    # For this property, you first need to create this path in your project, it will the path were the generated native code will be deployed.
    path: src/scanner/
  dart:
    # For this property, you first need to create this path in your project, it will the path were the generated bindings code will be deployed.
    path: lib/scanner.dart
    # This set the structure of the bindings that will be generated in dart, it can be file-per-class(package_structure) or all wrapped in a single file(single_file)
    structure: single_file

  # In this property we set the directory to the class we will need
source-path:
  - 'android/app/src/main/kotlin/ao/marco_domingos/scanner_api_example'

  # In this property we set the exact class we will need, we set it with his package_name only this way the jnigen will be able to find it
classes:
  - 'ao.marco_domingos.scanner_api_example.Scanner'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this file you can read the comments to understand all its properties.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4- Generating the bindings&lt;/strong&gt;&lt;br&gt;
Now that we have the configuration file ready, all that remains is to generate the bindings, we can do it with this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dart run jnigen --config jni.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But, jnigen needs to scan compiled &lt;strong&gt;jar&lt;/strong&gt; files to generate the bindings, so you might get an error if you just run the above command without a compiled &lt;strong&gt;jar&lt;/strong&gt; file, so first you need to run the below command and only after the above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flutter build apk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything worked fine, you can check the path you set in the configuration file and you will find all the generated bindings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5- Adding generated native code&lt;/strong&gt;&lt;br&gt;
Now we need to change our &lt;strong&gt;app/build.gradle&lt;/strong&gt; with the following line of code inside the android property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;externalNativeBuild {
    cmake {
        path "../../src/scanner/CMakeLists.txt"
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will add the &lt;strong&gt;CMakeList&lt;/strong&gt; file that was generated by &lt;strong&gt;JNIgen&lt;/strong&gt; into our &lt;strong&gt;build.gradle&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6- Calling native function getCode&lt;/strong&gt;&lt;br&gt;
Now we need to call our native function, but before that we need to initialize the JNI communication channel between Dart and native code, we can do this by adding the code below to our main:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Jni.initDLApi();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can call our native function like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  void _getScanner() async {
    String? scanned;
    try {
      final scanner = await Scanner().getCode(JObject.fromRef(Jni.getCachedApplicationContext()));
      scanned = scanner.toDartString();
    } catch (e) {
      scanned = "";
    }

    setState(() {
      result = scanned ?? "";
    });
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looking at our function, we can see that we can call the native class and function directly from our Dart code without having to define a channel and method as we would have to with MethodChannel. And when we run it, the result is:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pHc5u3Aa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v4x70iyb5gbtrp2ueqqc.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pHc5u3Aa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v4x70iyb5gbtrp2ueqqc.gif" alt="Image description" width="600" height="1275"&gt;&lt;/a&gt;&lt;br&gt;
Exactly what we wanted!&lt;/p&gt;

&lt;p&gt;If we go to see both codes, we can see that in both we use 50% Kotlin and 50% Dart, but the JNI facilitates the interoperation between the native code that we have and the dart without the need to create a channel, a platform to manipulate it, calling native code just like other Dart code. If that doesn't convince you, then let's talk about &lt;strong&gt;Game Changer&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Game Changer
&lt;/h2&gt;

&lt;p&gt;So with &lt;strong&gt;JNIgen&lt;/strong&gt; we have a game changer, in MethodChannel we can't reduce the % of native code we use to work with a native Feature, we can just increase it, but with JNIgen we can reduce, like now , I'll just use 10% Kotlin and 90% Dart to get the same result.&lt;/p&gt;

&lt;p&gt;First, we can go to our native class &lt;strong&gt;Scanner.kt&lt;/strong&gt; and remove any traces of it and we will have our class like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Scanner {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Okay, now let's just add a small function to handle a task and convert it to a suspend function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Scanner {
    suspend fun &amp;lt;T&amp;gt; Task&amp;lt;T&amp;gt;.await(): T {
        return suspendCancellableCoroutine { continuation -&amp;gt;
            addOnSuccessListener { result -&amp;gt;
                continuation.resume(result)
            }

            addOnFailureListener { exception -&amp;gt;
                continuation.resumeWithException(exception)
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you might ask: &lt;em&gt;If there is no native code to call the scanner, how can we scan your code?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Well, this is where the magic starts, let's go back to our jnigen configuration file and add some classes responsible for accessing the Scanner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;classes:
  - 'ao.marco_domingos.scanner_api_example.Scanner'
  - 'com.google.mlkit.vision.codescanner.GmsBarcodeScanning'
  - 'com.google.mlkit.vision.codescanner.GmsBarcodeScanner'
  - 'com.google.mlkit.vision.barcode.common.Barcode'
  - 'com.google.mlkit.vision.barcode.common.internal.BarcodeSource'
  - 'com.google.android.gms.tasks.Task'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, remember that jnigen only generates the bindings according to a compiled jar file, we already ran &lt;strong&gt;flutter build apk&lt;/strong&gt; in the beginning, but we made changes in our native code, so we need to run &lt;strong&gt;flutter build apk ** again to compile the latest changes in the jar file and only then run **dart run jnigen --config jnigen.yaml&lt;/strong&gt; again to update the bindings.&lt;/p&gt;

&lt;p&gt;After finishing this part let's go to what really matters, our function and change our old code to this one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  void _getScanner() async {
    String? scanned;
    try {
      final scanner = GmsBarcodeScanning.getClient(JObject.fromRef(Jni.getCachedApplicationContext()));
      final result = await Scanner().await0(scanner.startScan());
      scanned = result.getRawValue().toDartString();
    } catch (e) {
      scanned = "";
    }

    setState(() {
      result = scanned ?? "";
    });
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looking at this code you can see that the code we had in our native Kotlin class is now in our Dart function and the only code we have in our native class is the function that converts the scanner task to a suspend function which is converted to an async function in our Dart code as defined in the config file.&lt;/p&gt;

&lt;p&gt;Now you might ask &lt;em&gt;Does it work?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here is your answer:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9R5Je5a9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xk1lc3yz3qbnx1kpzw6z.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9R5Je5a9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xk1lc3yz3qbnx1kpzw6z.gif" alt="Image description" width="600" height="1275"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the next level of Dart interop tools, remembering that &lt;strong&gt;JNIgen&lt;/strong&gt; is still actively under development, this is what the tool can do in its early stages, what will the tool be able to do in the later stages? Perhaps we can develop features with 0% native code, which is actually already possible with some API's and JNIgen.&lt;/p&gt;

&lt;p&gt;You can see all the code used in this article in &lt;a href="https://github.com/Marco4763/scanner_api_example"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And if you are curious about the link that is returned when reading the QRCode, here it is:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/-P-ein58laA"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Hope you enjoyed the reading.&lt;/p&gt;

&lt;p&gt;Follow my profile for more articles and interesting projects:&lt;br&gt;
&lt;a href="https://github.com/Marco4763"&gt;Github&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.linkedin.com/in/marco-domingos-239756171/"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>jnigen</category>
      <category>google</category>
    </item>
    <item>
      <title>Flutter -Storage access for Android SDK 33</title>
      <dc:creator>Marco Domingos</dc:creator>
      <pubDate>Mon, 20 Mar 2023 21:37:21 +0000</pubDate>
      <link>https://dev.to/marco4763/flutter-storage-access-for-android-sdk-33-4mh1</link>
      <guid>https://dev.to/marco4763/flutter-storage-access-for-android-sdk-33-4mh1</guid>
      <description>&lt;p&gt;Recently Android SDK 33 became available for most devices and with that, came the task for many developers of upgrading the **compileSdkVersion **to the **TIRAMISU **version code. But, what many of these developers forgot to check was, what changes in this new version? Mostly Flutter developers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uOHIvpSt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jfet38d54zq2zdyz1hte.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uOHIvpSt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jfet38d54zq2zdyz1hte.gif" alt="Image description" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Hum… Why Especially Flutter?
&lt;/h2&gt;

&lt;p&gt;As Google itself defines, Flutter is an open source framework by Google for building beautiful, natively compiled, multi-platform applications from a single codebase.&lt;/p&gt;

&lt;p&gt;Flutter is still growing and currently most of the Flutter features that developers use rely on third-party libraries built with native code. This means that before changing the &lt;strong&gt;compileSdkVersion&lt;/strong&gt;, the developer must make sure that the third-party library has also made the necessary changes to work with their new sdk version.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Wyb3kNLT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1xu67vunn6n0ffypuc7w.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Wyb3kNLT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1xu67vunn6n0ffypuc7w.gif" alt="Image description" width="220" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Like What?
&lt;/h2&gt;

&lt;p&gt;Recently in the Community, someone ask me to help him with a code that he was using to practice his knowledge of Flutter. So, he was following this &lt;a href="https://mukhtharcm.com/storage-permission-in-flutter/"&gt;article &lt;/a&gt;and overall there is nothing wrong with the article, the problem was that according to him the storage permission worked in some devices, but not in his device, later he told me that the device was with the last update of android 13 (Tiramisu) and with that the reason why it wasn't working was clear to me.&lt;/p&gt;

&lt;p&gt;What most dev doesn't know is that since Android 10, the app doesn't need to ask the storage permission since the app by default has access to any files that the device owns, this means files that weren't generated by others apps and are stored in unusual storage. But these is only visible now because only in android 13 that they start using &lt;strong&gt;Granular Media Permissions&lt;/strong&gt;, and with that the app no longer ask for the storage permission in the app as before, in some devices you can still set it manually at the app info page on the device configurations. You can read more about it &lt;strong&gt;&lt;a href="https://developer.android.com/about/versions/13/behavior-changes-13?hl=pt-br#granular-media-permissions"&gt;here&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So, basically what he was doing wrong is checking if the storage was granted in app running on device with Tiramisu, by default the permission will be PermanentlyDenied and even if the user opens the app info to allow it manually, is still a long shot, as I said before, only in some devices the storage permission will appear in the app info, not all of them.&lt;/p&gt;

&lt;p&gt;Before I gave him the solution, I decided to check the library he was using to confirm that they where working with &lt;strong&gt;Granular Media&lt;/strong&gt;, that was the &lt;a href="https://pub.dev/packages/permission_handler"&gt;permission_handler &lt;/a&gt;library, and even the lib being updated, I was surprised to find out that many others devs were having the same difficult, they were asking and checking for the storage permission on devices running the SDK 33.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LQ4soMSK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bpmkiyzltkhsazr2n16h.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LQ4soMSK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bpmkiyzltkhsazr2n16h.gif" alt="Image description" width="220" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What was the solution?
&lt;/h2&gt;

&lt;p&gt;Well, there were three possible solutions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1 - Solution One&lt;/strong&gt;&lt;br&gt;
He could simply remove the check option in general, but this was not scalable as the app run on older versions of android.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2 - Solution Two&lt;/strong&gt;&lt;br&gt;
I simply suggested him to before ask and check the storage permission, he needed to check if the SDK version that is running is older that Tiramisu, so he add the &lt;a href="https://pub.dev/packages/device_info_plus"&gt;device_info_plus &lt;/a&gt;library and made the changes, this was the final result:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Kjk2ISsR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s6gem7w46bvnc3rca951.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Kjk2ISsR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s6gem7w46bvnc3rca951.png" alt="Image description" width="800" height="673"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3 - Solution Three&lt;/strong&gt;&lt;br&gt;
If there was the need to access some of the files generated by others apps, then he should use Granular Media Permissions, this knowing that the library was already working with them.&lt;br&gt;
So first he just had to add one of these permissions, with Granular Media Permissions he just had to specify the type of media he needed access to:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VWA__Nl3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e29xfx0gw1c5wx0j3g6v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VWA__Nl3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e29xfx0gw1c5wx0j3g6v.png" alt="Image description" width="800" height="343"&gt;&lt;/a&gt;&lt;br&gt;
And then use the permission that the lib has prepared for this type of situation, which in this case can be Permission.photos, Permission.videos or Permission.audio, like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tYSvtSKx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sor8eh6w4q1fdh9dsn72.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tYSvtSKx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sor8eh6w4q1fdh9dsn72.png" alt="Image description" width="800" height="829"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: if you need the access for both photos and videos, you can use either Permission.photos or Permission.video, you don't need both of them, because in Granular Media the access is granted for both media types.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--t2zy_y35--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dxffk6knt7dkqisla9p2.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--t2zy_y35--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dxffk6knt7dkqisla9p2.gif" alt="Image description" width="200" height="200"&gt;&lt;/a&gt;&lt;br&gt;
And with that the problem was solved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;So in the end we only needed to add a few extra lines of code and the code worked perfectly.&lt;/p&gt;

&lt;p&gt;With this type of update it is always good to check the library before making the app available for any recent SDK, not every library is constantly updated. In this case, &lt;a href="https://pub.dev/packages/permission_handler"&gt;permission_handler &lt;/a&gt;have made the changes and is working with &lt;strong&gt;Granular Media Permissions&lt;/strong&gt;, but not all stories have a happy ending.&lt;/p&gt;

&lt;p&gt;You can read more about &lt;strong&gt;Granular Media Permissions&lt;/strong&gt; and &lt;strong&gt;Tiramisu&lt;/strong&gt; on this links: Link &lt;a href="https://developer.android.com/reference/android/Manifest.permission"&gt;one&lt;/a&gt;, Link &lt;a href="https://developer.android.com/training/data-storage/shared/media?hl=pt-br#storage-permission"&gt;two&lt;/a&gt;, Link &lt;a href="https://developer.android.com/about/versions/13?hl=pt-br"&gt;three &lt;/a&gt;and Link &lt;a href="https://developer.android.com/about/versions/13/behavior-changes-13?hl=pt-br#granular-media-permissions"&gt;four&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thank you for reading.&lt;/p&gt;

&lt;p&gt;You can reach and follow me on &lt;strong&gt;Marco4763&lt;/strong&gt;: &lt;a href="https://github.com/Marco4763"&gt;Github &lt;/a&gt;and &lt;a href="https://www.linkedin.com/in/marco-domingos-239756171/"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
