How to: Integrate Flutter and Datalogic SDK

This is an example that wants to show how a Flutter application could be integrated with Datalogic SDK.
The example is written in Kotlin for the native part.
We are assuming to have a Flutter application already been created by following the tutorial Install | Flutter .
The purpose of the app is to show how to:

  1. Receive a barcode content in the Android part and transmit it to Flutter part
    1.1 – through System Intent (by using the Datalogic Intent Wedge)
    1.2 – through code (by using SDK Read Listener)
  2. Enable/disable the Scan Engine’s Triggers

Overview
A Flutter application is made up of a Dart part common to several operating systems and a part specific to the operating system on which the application is running, in this case, Android.
(see more at: Writing custom platform-specific code | Flutter)

When a barcode is read, the value is passed from Android part of the app to the Flutter application via an EventChannel.
Upon reading the barcode, the content is received in the native Android part of the app and
the code will then be transmitted to the Flutter part using the mechanisms provided.

1 - Receive a barcode content in the Android part and transmit it to Flutter part

1.1 - Receive a barcode through Datalogic Intent Wedge

Android code:
A broadcast receiver is registered and is waiting for data coming from Datalogic Intent Wedge.
When a new code is received a Flutter EventChannel is used to pass the code from the Android code to Flutter code.

class MainActivity: FlutterActivity() {

      private val ACTION_BROADCAST_RECEIVER =  "com.datalogic.decodewedge.decode_action"
      private val CATEGORY_BROADCAST_RECEIVER = "com.datalogic.decodewedge.decode_category"
      private val EXTRA_DATA_STRING = "com.datalogic.decode.intentwedge.barcode_string"
      private val EVENT_CHANNEL_BARCODE_INTENT = "app.channel.event.barcode_intent"
      private var receiver: BroadcastReceiver? = null
      private var filter: IntentFilter? = null
  	
  	 override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
           super.configureFlutterEngine(flutterEngine)
  	
  		 EventChannel(flutterEngine.dartExecutor.binaryMessenger, EVENT_CHANNEL_BARCODE_INTENT).setStreamHandler(
  				object : EventChannel.StreamHandler {
  					override fun onListen(args: Any?, events: EventChannel.EventSink) {
  
  						receiver = BarcodeIntentReceiver(events)
  						filter = IntentFilter()
  						filter!!.addAction(ACTION_BROADCAST_RECEIVER)
  						filter!!.addCategory(CATEGORY_BROADCAST_RECEIVER)
  						registerReceiver(receiver, filter)
  
  
  					}
  
  					override fun onCancel(args: Any?) {
  						//Log.i(LOGTAG, "EVENT_CHANNEL_BARCODE_INTENT has been canceled")
  					}
  				}
  			)	
  	}
    private fun BarcodeIntentReceiver(events: EventSink?) : BroadcastReceiver? {
          return object : BroadcastReceiver() {
              override fun onReceive(context: Context, wedgeIntent: Intent) {
                  //Log.i(LOGTAG, "received" + wedgeIntent.action)
                  val action = wedgeIntent.action
  
                  if (action == ACTION_BROADCAST_RECEIVER) {
                      
                      barcode_text = wedgeIntent.getStringExtra(EXTRA_DATA_STRING)
                      events?.success(barcode_text)
                      
                      //Log.i(LOGTAG, "Decoding Broadcast Received")
                  }
              }
          }
      }
  }

Flutter code:
In the Flutter code we can use a EventChannel listener inside a StatefulWidget to wait for the barcode coming from the Android part:

 class IntentBarcodeReader extends StatefulWidget {
 	  const IntentBarcodeReader({Key? key}) : super(key: key);
  	  @override
  	  State<IntentBarcodeReader> createState() => _IntentBarcodeReaderState();
  	}
  
  	class _IntentBarcodeReaderState extends State<IntentBarcodeReader> {
  	  static const EventChannel scanChannel = EventChannel('app.channel.event.barcode_intent');
  	  String barcode = "no intent data received";
  
  	  @override
  	  void initState() {
  		super.initState();
  		print("init state ");
  		scanChannel.receiveBroadcastStream().listen(_onEvent, onError: _onError);
  	  }
  
  	  void _onError(dynamic data) {
  		print("Error");
  	  }
  
  	  void _onEvent(dynamic data) {
  
  		setState(() {
  		  barcode = data;
  		});  
  	  }
  
  	  @override
  	  Widget build(BuildContext context) {
  		return Text(barcode);
  	  }  
  	}

1.2 - Receive a barcode through Datalogic SDK

Android code:
A ReadListner is registered and is waiting for data coming from the Scan Engine.
When a new code is received a Flutter EventChannel is used to pass the code from the Android code to Flutter code.



class MainActivity: FlutterActivity() {

private var decoder: BarcodeManager? = null
private var listener: ReadListener? = null
private val EVENT_CHANNEL_BARCODE_LISTENER = "app.channel.event.barcode_listener"

override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
         super.configureFlutterEngine(flutterEngine)
 
         EventChannel(flutterEngine.dartExecutor.binaryMessenger, EVENT_CHANNEL_BARCODE_LISTNER).setStreamHandler(
 					object : EventChannel.StreamHandler {
 						override fun onListen(args: Any?, events: EventChannel.EventSink) {
 							try {
 								decoder = BarcodeManager()
 								// Create an anonymous class.
 								listener = ReadListener { decodeResult ->
 									events?.success(decodeResult.text)
 								}
 								decoder!!.addReadListener(listener)
 
 							} catch (e: DecodeException) {
 								//Log.e(LOGTAG, "Error while trying to bind a listener to BarcodeManager", e)
 							}
 						}
 						override fun onCancel(args: Any?) {
 							//Log.i(LOGTAG, "EVENT_CHANNEL_BARCODE_LISTNER has been canceled")
 						}
 					}
 
 				) 		
 
  }
}

Flutter code:
In the Flutter code, we can use an EventChannel listener inside a StatefulWidget to wait for the barcode coming from the Android part.
The Widget is programmed like the previous one (point 1.2) only the channel on which it is waiting is changed, this widget is waiting on channel ‘app.channel.event.barcode_listener’ :

class ListenerBarcodeReader extends StatefulWidget {
   const ListenerBarcodeReader({Key? key}) : super(key: key);
 
   @override
   State<ListenerBarcodeReader> createState() => _ListenerBarcodeReaderState();
 }
 ...

 
 class _ListenerBarcodeReaderState extends State<ListenerBarcodeReader> {
   static const EventChannel scanChannel = EventChannel('app.channel.event.barcode_listener');
 ...

The previous examples are showing a communication through ChannelEvent from Activity to Flutter, in the second part is shown Flutter that calls the Activity throught a MetodChannel:

2 - Enable/disable the Scan Engine’s Triggers

Android code:

class MainActivity: FlutterActivity() {

private var decoder: BarcodeManager? = null

 private val METHOD_CHANNEL_BARCODEMANAGER = "app.channel.method/barcodemanager"

 override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
 			super.configureFlutterEngine(flutterEngine)  
 			
 	   
 		   MethodChannel(flutterEngine.dartExecutor.binaryMessenger, METHOD_CHANNEL_BARCODEMANAGER).setMethodCallHandler {
 						call, result ->
 					if (call.method == "startTrigger") {
 						//Log.i(LOGTAG, "startTrigger MethodChannel Called")
 					   var bm = BarcodeManager()
 						bm.pressTrigger()
 					}
 
 					if (call.method == "stopTrigger") {
 						//Log.i(LOGTAG, "stopTrigger MethodChannel Called")
 						var bm = BarcodeManager()
 						bm.releaseTrigger()
 					}
 				}
 	}
}

Flutter code:
In the Flutter code, we can use a MethodChannel inside a StatefulWidget that through a GestureDetector appropriately invoke the methods of the Android part.


 	class DecodeButton extends StatefulWidget {
 	  const DecodeButton({Key? key}) : super(key: key);
 
 	  @override
 	  State<DecodeButton> createState() => _DecodeButtonState();
 	}
 
 	class _DecodeButtonState extends State<DecodeButton> {
 	  static const platform = MethodChannel('app.channel.method/barcodemanager');
 	  bool _hasBeenPressed = false;
 
 	  @override
 	  void initState() {
 		super.initState();
 		print("init state ");
 	  }
 
 	  @override
 	  Widget build(BuildContext context) {
 		return GestureDetector(
 		  onTapDown: (_) { startTrigger();},
 		  onTapUp: (_) { stopTrigger();},
         onPanEnd:(_){stopTrigger();},
 		  child: Container(
 			padding: const EdgeInsets.all(60.0),
 			decoration: BoxDecoration(
 			  color: _hasBeenPressed ? Colors.amber : Colors.lightBlue,
 			  borderRadius: BorderRadius.circular(8.0),
 			  border : Border.all(width: 5),
 			),
 			child: const Text('Scan'),
 		  ),
 		);
 	  }
 
 	  void startTrigger() async {
 		try {
 		  print('called startTrigger()');
 		  platform.invokeMethod('startTrigger');
 
 		  setState(() {
 			_hasBeenPressed=true;
 		  });
 
 		} on PlatformException catch (e) {
 		  "Failed to get startTrigger: '${e.message}'.";
 		}
 	  }
 
 	  void stopTrigger() async {
 		try {
 		  print('called stopTrigger()');
 		  platform.invokeMethod('stopTrigger');
 
 		  setState(() {
 			_hasBeenPressed=false;
 		  });
 
 		} on PlatformException catch (e) {
 		  "Failed to get stopTrigger : '${e.message}'.";
 		}
 	  }
 
 	}

This app is an example and it is not intended to be a complete guide to integrate Flutter with the Datalogic SDK.
At this link, it is possible to download the apk, note that before receiving the barcode via intent, the intent wedge must be active and the configuration parameters (action, category, etc.) must be at default values.

1 Like

Hi

I developed a Flutter App following these instructions, when i run in debug mode i can get the scan data perfectly, but when i compile an apk release, i do not get the scan data.

Therefore i can not use my App in producction.

My device is a Memor-10

What could be going on?

Thanks

Hi @Jorge_Escandell ,
welcome to the discussion board.

Probably you need to configure the “Buildtype release” in your build.gradle file.
You can configure the file like this:

 buildTypes {
        release {
            signingConfig signingConfigs.debug
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

the contents of the file ‘proguard-rules.pro’ can be:

-keep class com.datalogic.cradle.** { *; }
-keep class com.datalogic.decode.** { *; }
-keep class com.datalogic.device.** { *; }
-keep class com.datalogic.extension.** { *; }
-keep class com.datalogic.softspot.** { *; }

please see also is-the-datalogic-sdk-compatible-with-proguard-obfuscation .

Another way to configure the gradle file can be found in the following answer: DataMemorLogic10 Intent Wedge - #14 by Mehmet_Akif_SAYRIM .

Donato Cataldo
L3 Mobile Computer Support Engineer

2 Likes

That solved the problem :slight_smile:

Thanks

1 Like

Thanks a bunch Donato_Cataldo for this intro to Flutter. I wanted to take a minute and share my experience actually making this work and cover some things that I had to do to make it work. I have never written an app with Dart and Flutter and only vaguely used Android Studio before now, so I was coming at this as “I want to assess Flutter as a viable option to building a new scanning app” without really knowing the minutia of that particular build pipeline.

Firstly, for my dev set up, I’m using Visual Studio Code. I installed Flutter/SDK per their website’s instructions. (I also have the latest Android Studio, and have the API 28 installed. 28 seems to be the highest common denominator between Memor K, 10, and Skorpio X5). I created a new default Flutter project using Visual Studio, again following the Flutter.dev instructions. Connected my debug Memor K to and issued “flutter run” in the terminal. It comes up and works. Great, proof of tool chain.

Next, I wanted to see what the integration layer looked like between the more native code Datalogic’s API talks on, and Flutter, and I found this post.

To do this part, its best to use Android Studio, since this is touching lower-level stuff. I’m sure it could be done with VS Code, but it seems to “just work” in Android Studio. Set Visual Studio Code aside for now, and open Android Studio.

Open the android folder (not the root flutter project folder) in Android Studio.

In MainActivity.kt, put the following:
(Note, I included the imports which were necessary to get this to compile)

android/app/src/main/kotlin/com/example/flutter_application_1/MainActivity.kt

package com.example.flutter_application_1

import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import com.datalogic.decode.*
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.EventChannel

class MainActivity : FlutterActivity()
{
   private var decoder: BarcodeManager? = null
    private var listener: ReadListener? = null
    private val EVENT_CHANNEL_BARCODE_LISTENER = "app.channel.event.barcode_listener"

    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
         super.configureFlutterEngine(flutterEngine)
 
         EventChannel(flutterEngine.dartExecutor.binaryMessenger, EVENT_CHANNEL_BARCODE_LISTENER)
				 .setStreamHandler(
 					object : EventChannel.StreamHandler {
 						override fun onListen(args: Any?, events: EventChannel.EventSink) {
 							try {
 								decoder = BarcodeManager()
 								// Create an anonymous class.
 								listener = ReadListener { decodeResult ->
 									events?.success(decodeResult.text)
 								}
 								decoder!!.addReadListener(listener)
 
 							} catch (e: DecodeException) {
 								//Log.e(LOGTAG, "Error while trying to bind a listener to BarcodeManager", e)
 							}
 						}
 						override fun onCancel(args: Any?) {
 							//Log.i(LOGTAG, "EVENT_CHANNEL_BARCODE_LISTNER has been canceled")
 						}
 					}
 				) 		
  }
}

There will probably be an error now with a couple of data logic classes not being found. You’ll need to muck with dependencies. Gradle is kind of a bear, and they keep changing things in different versions, making it more difficult to use and search for how-to’s on. So, here is the skinny on what I learned.

  1. Flutter only works with gradle 7.6 as of right now. Don’t use 8 or 9.
  2. There are 3 gradle files that each play a part in the build, and reading on where to put dependencies on stack overflow or whever, and how to do this is like pulling since best practices change between versions:

android/build.gradle

Note the maven url jitpack.io line:

allprojects {
    repositories {
        google()
        mavenCentral()
        maven { url "https://jitpack.io" }
    }
}

rootProject.buildDir = "../build"
subprojects {
    project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
    project.evaluationDependsOn(":app")
}

tasks.register("clean", Delete) {
    delete rootProject.buildDir
}

android/app/build.gradle

Note the dependencies section, inside of the android node. Your version might be newer if you’re targeting a newer datalogic device

plugins {
    id "com.android.application"
    id "kotlin-android"
    // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
    id "dev.flutter.flutter-gradle-plugin"
}

def localProperties = new Properties()
def localPropertiesFile = rootProject.file("local.properties")
if (localPropertiesFile.exists()) {
    localPropertiesFile.withReader("UTF-8") { reader ->
        localProperties.load(reader)
    }
}

def flutterVersionCode = localProperties.getProperty("flutter.versionCode")
if (flutterVersionCode == null) {
    flutterVersionCode = "1"
}

def flutterVersionName = localProperties.getProperty("flutter.versionName")
if (flutterVersionName == null) {
    flutterVersionName = "1.0"
}

android {
    namespace = "com.example.flutter_application_1"
    compileSdk = flutter.compileSdkVersion
    ndkVersion = flutter.ndkVersion

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }

    defaultConfig {
        // TODO: Specify your own unique Application ID 
        applicationId = "com.example.flutter_application_1"
        // You can update the following values to match your application needs.
        // For more information, see: docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
        minSdk = flutter.minSdkVersion
        targetSdk = flutter.targetSdkVersion
        versionCode = flutterVersionCode.toInteger()
        versionName = flutterVersionName
    }

    buildTypes {
        release {
            // TODO: Add your own signing config for the release build.
            // Signing with the debug keys for now, so `flutter run --release` works.
            signingConfig = signingConfigs.debug
        }
    }
    dependencies {
        implementation 'com.github.datalogic:datalogic-android-sdk:1.26'
    }
}

flutter {
    source = "../.."
}


android/settings.gradle

There’s really nothing to do in this file. DONT put a dependencyResolutionManagement node in here with FAIL_ON_PROJECT_REPOS to make jitpack work, which you might try after googling some errors. It just doesn’t work on this version or maybe with Flutter right now.

pluginManagement {
    def flutterSdkPath = {
        def properties = new Properties()
        file("local.properties").withInputStream { properties.load(it) }
        def flutterSdkPath = properties.getProperty("flutter.sdk")
        assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
        return flutterSdkPath
    }()

    includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")

    repositories {
        google()
        mavenCentral()
        gradlePluginPortal()
    }
}

plugins {
    id "dev.flutter.flutter-plugin-loader" version "1.0.0"
    id "com.android.application" version '7.4.2' apply false
    id "org.jetbrains.kotlin.android" version "1.7.10" apply false
}

include ":app"


app/src/main/AndroidManifest.xml

Next one, Android Manifest.

Add uses-library inside the <application> node.

 <uses-library
            android:name="com.datalogic.device"
            android:required="true" />

Okay, that’s in on the meta files and stuff. Flutter parameterizes the AndroidManifest and SDK version choices, so you can’t really do anything else in Android Studio. If you poke around in Project Structure, you’ll see this which tells the story.

At this point just save everything and close out of Android Studio. Switch back to VS Code.


Create a new dart file. This will be a widget that can handle the barcode reading.

lib/listener_barcode_reader.dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class ListenerBarcodeReader extends StatefulWidget {
  const ListenerBarcodeReader({Key? key}) : super(key: key);
  @override
  State<ListenerBarcodeReader> createState() => _ListenerBarcodeReaderState();
}

class _ListenerBarcodeReaderState extends State<ListenerBarcodeReader> {
  static const EventChannel scanChannel =
      EventChannel('app.channel.event.barcode_listener');
  String barcode = "no intent data received";

  @override
  void initState() {
    super.initState();
    print("init state ");
    scanChannel.receiveBroadcastStream().listen(_onEvent, onError: _onError);
  }

  void _onError(dynamic data) {
    print("Error");
  }

Now, go to main.dart, and import the listener barcode reader dart file and then add the widget to the UI. I added it after the text element that stores the counter variable.

lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_application_1/listener_barcode_reader.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(
        // This is the theme of your application.
        //
        // TRY THIS: Try running your application with "flutter run". You'll see
        // the application has a purple toolbar. Then, without quitting the app,
        // try changing the seedColor in the colorScheme below to Colors.green
        // and then invoke "hot reload" (save your changes or press the "hot
        // reload" button in a Flutter-supported IDE, or press "r" if you used
        // the command line to start the app).
        //
        // Notice that the counter didn't reset back to zero; the application
        // state is not lost during the reload. To reset the state, use hot
        // restart instead.
        //
        // This works for code too, not just values: Most code changes can be
        // tested with just a hot reload.
        colorScheme: ColorScheme.fromSeed(seedColor: const Color.fromARGB(255, 255, 123, 0)),
        useMaterial3: true,

      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),

    );
  }
}

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

  // This widget is the home page of your application. It is stateful, meaning
  // that it has a State object (defined below) that contains fields that affect
  // how it looks.

  // This class is the configuration for the state. It holds the values (in this
  // case the title) provided by the parent (in this case the App widget) and
  // used by the build method of the State. Fields in a Widget subclass are
  // always marked "final".

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      // This call to setState tells the Flutter framework that something has
      // changed in this State, which causes it to rerun the build method below
      // so that the display can reflect the updated values. If we changed
      // _counter without calling setState(), then the build method would not be
      // called again, and so nothing would appear to happen.
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Scaffold(
      appBar: AppBar(
        // TRY THIS: Try changing the color here to a specific color (to
        // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
        // change color while the other colors stay the same.
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        //foregroundColor: Colors.amber,
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      body: Center(
        // Center is a layout widget. It takes a single child and positions it
        // in the middle of the parent.
        child: Column(
          // Column is also a layout widget. It takes a list of children and
          // arranges them vertically. By default, it sizes itself to fit its
          // children horizontally, and tries to be as tall as its parent.
          //
          // Column has various properties to control how it sizes itself and
          // how it positions its children. Here we use mainAxisAlignment to
          // center the children vertically; the main axis here is the vertical
          // axis because Columns are vertical (the cross axis would be
          // horizontal).
          //
          // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint"
          // action in the IDE, or press "p" in the console), to see the
          // wireframe for each widget.
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
            ListenerBarcodeReader( ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

Connect your debug android device, and then in the terminal, do “flutter run”

Scan a barcode, and it will show on the screen
image

I hope this helps someone, as a more you dont know how any of this works yet expansion on the original post.

2 Likes

Salve ho incluso la libreria dal sito Pub.dev, ma sto avendo grosse difficolta in quanto ci sono pochi chiarimenti in merito.
sarebbe possibile parlare con qualcuno?

Buongiorno @Michele_De_Mitri

Per avrere una assistenza più specifica può mettersi in contatto anche in italiano con il nostro servizio di supporto tecnico aprendo un ticket tramite la pagina dedicata: Supporto tecnico - Datalogic, e spiegando bene la sua necessità.
Altrimenti per infomazioni di carattere più genrarle o per chiedere il supporto di altri utenti può avviare una nuova discussione anche qui su questo forum.

Simone Callegari
Datalogic Mobile Products - L3 Specialist SW Engineer