Cross-Platform Barcode Reader with B4X

B4X is a set of rapid development tools which can be used to develop apps for all the major mobile and desktop platforms.

It has four products: B4A, B4i, B4J and B4R, which aim at Android, iOS, Java (desktop with JavaFX, server and Raspberry Pi) and Arduino development.

Apart from multi-platform support, it has the following features:

  • An easy-to-use programming language.
  • A full-featured dedicated IDE.
  • An active and friendly community.

B4X can create native apps for Android and iOS. The B4X programming language is a modern version of Visual Basic. It will be compiled to Java for Android and Objective-C for iOS. This feature makes it easy to integrate Java/Objective-C libraries into B4X projects. The code can be shared across platforms, although we still have to write the platform-specific parts like Camera and file picker.

Dynamsoft Barcode Reader (DBR) is a barcode SDK written in C++ and provides JavaScript, Java and Objective-C packages. So it is easy to wrap it into a B4X library to develop Android, iOS and desktop barcode reader apps with B4X.

In this article, we are going to create a B4X wrapper and create demo apps for Android, iOS and desktop.

Create a Test Project

Let’s first create a new B4J project to create and test the barcode library.

  1. Create a new B4XPages project with B4J.

    new project

  2. Open the UI designer. Add a button to pick images, a button to decode, a panel to display the image and a label to show the results.

    designer

    After that, we click generate members to declare these controls and add event subs in the code.

    generate members

  3. Implement the image picker. Draw the image on the Panel with B4XCanvas.

     Private Sub btnLoadImage_Click Dim bm As B4XBitmap Dim fc As FileChooser fc.Initialize Dim path As String=fc.ShowOpen(B4XPages.GetNativeParent(Me)) If File.Exists(path,"") Then bm=xui.LoadBitmap(path,"") cvs.ClearRect(cvs.TargetRect) drawBitmap(bm) Panel1.Tag=bm End If End Sub 

Now, we have to implement the decoding part.

Wrap the Dynamsoft Barcode SDK

Let’s first write the wrapper.

Add Dependency

  1. Download Dynamsoft Barcode Reader’s jar file from its website.
  2. Config the path of the folder of additional libraries.

    Lib path

  3. Put the jar in the additional libraries folder.
  4. Reference the jar in Main by adding this line of code:

     #AdditionalJar: dynamsoft-barcodereader-8.2 

How a Wrapper is Made

Basically, there are two ways to create a wrapper.

One is directly writing in Java, which is illustrated here. Creating an iOS library is a bit harder which uses Objective-C.

The other is using JavaObject to directly call Java APIs using B4X based on Java’s reflection feature. NativeObject is the equivalence of JavaObject in B4i.

It can be used with the inline java code feature. Java code can be directly included in B4X’s source code, as shown below.

#If JAVA public String FirstMethod() { return "Hello World!"; } #End If 

Then it can be called using JavaObject.

Dim JO as JavaObject=Me Dim s As String = JO.RunMethod("FirstMethod", Null) Log(s) 'will print Hello World! 

Here, we choose JavaObject to create the wrapper.

Write the Wrapper

Create a class and name it DBR. It is implemented as below.

Sub Class_Globals Private reader As JavaObject End Sub 'Initializes the object. You can add parameters to this method if needed. Public Sub Initialize reader.InitializeNewInstance("com.dynamsoft.dbr.BarcodeReader",Null) End Sub public Sub initLicenseFromKey(license As String) 'request your license here: https://www.dynamsoft.com/customer/license/trialLicense/?product=dcv&package=cross-platform reader.RunMethod("initLicense",Array(license)) End Sub private Sub ConvertToTextResults(results() As Object) As List Dim list1 As List list1.Initialize For Each result As Object In results Dim tr As TextResult tr.Initialize(result) 'convert the TextResult Java object to a B4X object list1.Add(tr) Next Return list1 End Sub Sub decodeImage(bitmap As B4XBitmap) As List Dim results() As Object Dim SwingFXUtils As JavaObject SwingFXUtils.InitializeStatic("javafx.embed.swing.SwingFXUtils") Dim bufferedImage As Object=SwingFXUtils.RunMethod("fromFXImage",Array(bitmap,Null)) ' convert JavaFX image to bufferedImage results=reader.RunMethod("decodeBufferedImage",Array(bufferedImage,"")) Return ConvertToTextResults(results) End Sub 

A TextResult class is created to represent the decoding result. Here we just parse the text and localization result.

Sub Class_Globals Private mTextResult As JavaObject Private mText As String Private mResultPoints(4) As Point2D Type Point2D(x As Int,y As Int) End Sub 'Initializes the object. You can add parameters to this method if needed. Public Sub Initialize(result As Object) mTextResult=result Parse End Sub Private Sub Parse mText=mTextResult.GetField("barcodeText") Dim points() As Object=mTextResult.GetFieldJO("localizationResult").GetField("resultPoints") For i=0 To 3 Dim point As JavaObject=points(i) Dim p As Point2D p.Initialize p.x=point.GetField("x") p.y=point.GetField("y") mResultPoints(i)=p Next End Sub Public Sub getObject As Object Return mTextResult End Sub Public Sub getText As String Return mText End Sub Public Sub getResultPoints As Point2D() Return mResultPoints End Sub 

Use the Wrapper

Now, we can implement the decoding part with the wrapper.

  1. Initialize the reader.

     Sub Class_Globals Private reader As DBR End Sub Public Sub Initialize reader.Initialize reader.initLicenseFromKey("<license key>") End Sub 
  2. Decode the image and display the result.

     Private Sub btnDecode_Click If (Panel1.Tag Is B4XBitmap)=False Then xui.MsgboxAsync("Please load an image first","") Return End If Dim bm As B4XBitmap=Panel1.Tag Dim results As List=reader.decodeImage(bm) Dim sb As StringBuilder sb.Initialize Dim color As Int=xui.Color_Red Dim stroke As Int=2 For Each result As TextResult In results sb.Append("Text: ").Append(result.Text).Append(CRLF) For i=0 To 2 Dim x1 As Int=result.ResultPoints(i).x*xPercent Dim y1 As Int=result.ResultPoints(i).y*yPercent Dim x2 As Int=result.ResultPoints(i+1).x*xPercent Dim y2 As Int=result.ResultPoints(i+1).y*yPercent cvs.DrawLine(x1,y1,x2,y2,color,stroke) Next cvs.DrawLine(result.ResultPoints(3).x*xPercent, _ result.ResultPoints(3).y*yPercent, _ result.ResultPoints(0).x*xPercent, _ result.ResultPoints(0).y*yPercent,color,stroke) Next cvs.Invalidate lblResult.Text=sb.ToString End Sub 

Here is a snapshot of the completed app:

Reader B4J

Make it Cross-Platform

We have finished the B4J application. Now, let’s modify it to make it work on Android and iOS.

Share Class Files and Layouts

Upon creation of a B4Xpage project, it will create three folders containing the platform-specific files and a Shared Files folder for shared asset files. Shared class files are stored in the root.

Open the previous project folder. We can see a folder structure like this:

│ B4XMainPage.bas │ ├─B4A │ │ BarcodeReader.b4a │ │ BarcodeReader.b4a.meta │ │ Starter.bas │ │ │ └─Files │ mainpage.bal │ ├─B4i │ │ BarcodeReader.b4i │ │ BarcodeReader.b4i.meta │ │ │ └─Files │ │ mainpage.bil │ │ │ └─Special ├─B4J │ │ BarcodeReader.b4j │ │ BarcodeReader.b4j.meta │ │ DBR.bas │ │ TextResult.bas │ │ │ └─Files │ MainPage.bjl │ └─Shared Files 

Now we want to share the files we just created in the three platforms’ projects.

  1. Move the DBR.bas and TextResult.bas to the root. Open the B4A, B4J and B4i projects and re-add them as new modules using relative path.

    Link Modules

  2. In B4J, open the designer. Select and copy all the controls. Open the layout files for B4i and B4A with their designer and paste.

    Paste Layout

Add Platform-Specific Code

Platform-specific code can exist in one source file using the #if statement. Let’s see it in practice.

Code for B4A

  1. Use aar instead of jar.

    Download the aar file from Dynamsoft, put it in the additional libraries folder and add the following line of code in Main:

     #AdditionalJar: DynamsoftBarcodeReaderAndroid.aar 
  2. The image picker is platform-specific. We add the code in the if b4a statement to use ContentChooser to pick images.

     Private Sub btnLoadImage_Click Dim bm As B4XBitmap #if b4a Dim cc As ContentChooser cc.Initialize("CC") cc.Show("image/*", "Choose image") Wait For CC_Result (Success As Boolean, Dir As String, FileName As String) If Success Then bm=LoadBitmap(Dir,FileName) Else ToastMessageShow("No image selected", True) End If #End If #if b4j Dim fc As FileChooser fc.Initialize Dim path As String=fc.ShowOpen(B4XPages.GetNativeParent(Me)) If File.Exists(path,"") Then bm=xui.LoadBitmap(path,"") End If #End If If bm.IsInitialized And bm<>Null Then cvs.ClearRect(cvs.TargetRect) drawBitmap(bm) Panel1.Tag=bm End If End Sub 
  3. You can have a trial of Dynamsoft Barcode Reader for Mobile by connecting to its License Tracking Server (LTS).

    Add the following inline java code to DBR.bas:

     #If b4a #if java import com.dynamsoft.dbr.BarcodeReader; import com.dynamsoft.dbr.BarcodeReaderException; import com.dynamsoft.dbr.DMLTSConnectionParameters; import com.dynamsoft.dbr.DBRLTSLicenseVerificationListener; public static void initLicenseFromLTS(BarcodeReader dbr,String organizationID){ DMLTSConnectionParameters parameters = new DMLTSConnectionParameters(); parameters.organizationID = organizationID; dbr.initLicenseFromLTS(parameters, new DBRLTSLicenseVerificationListener() { @Override public void LTSLicenseVerificationCallback(boolean isSuccess, Exception error) { if (!isSuccess) { error.printStackTrace(); } } }); } #End If #End If 

    Add a new sub to init license from LTS:

     public Sub initLicenseFromLTS(organizationID As String) Dim JO as JavaObject=Me JO.RunMethod("initLicenseFromLTS",Array(reader,organizationID)) End Sub 

    In B4XMainPage.bas, use initLicenseFromLTS if the platform is mobile.

     Public Sub Initialize reader.Initialize #if b4j reader.initLicenseFromKey("<license key>") #else reader.initLicenseFromLTS("200001") #End If End Sub 
  4. In B4J, the image has to be converted while the bitmap on Android can be directly used.

     Sub decodeImage(bitmap As B4XBitmap) As List Dim results() As Object #If b4j Dim SwingFXUtils As JavaObject SwingFXUtils.InitializeStatic("javafx.embed.swing.SwingFXUtils") Dim bufferedImage As Object=SwingFXUtils.RunMethod("fromFXImage",Array(bitmap,Null)) results=reader.RunMethod("decodeBufferedImage",Array(bufferedImage,"")) #else if b4a results=reader.RunMethod("decodeBufferedImage",Array(bitmap,"")) #End If Return ConvertToTextResults(results) End Sub 

That’s it. We can now run the app on Android.

Android

Code for B4i

  1. Use framework instead of aar/jar.

    Download the Dynamsoft Barcode Reader framework from Dynamsoft, put it in the local mac builder’s library folder and add the following line of code in Main:

     #AdditionalLib: DynamsoftBarcodeReader.framework.3 #AdditionalLib: libc++.tbd 
  2. Use the Camera class to pick images.

     #if b4i Dim cam As Camera cam.Initialize("camera",B4XPages.GetNativeParent(Me)) cam.SelectFromSavedPhotos(Sender, cam.TYPE_IMAGE) Wait For camera_Complete (Success As Boolean, Image As Bitmap, VideoPath As String) If Success Then Dim NO as NativeObject=Me bm=NO.RunMethod("normalizedImage:",Array(Image)) End If #End If 

    Here, the image taken in iOS has to be normalized or the image may be rotated.

    The following Objective-C code is used to do this:

     #if b4i #if objc //https://stackoverflow.com/questions/8915630/ios-uiimageview-how-to-handle-uiimage-image-orientation - (UIImage *)normalizedImage: (UIImage*) image { if (image.imageOrientation == UIImageOrientationUp) return image; UIGraphicsBeginImageContextWithOptions(image.size, NO, image.scale); [image drawInRect:(CGRect){0, 0, image.size}]; UIImage *normalizedImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return normalizedImage; } #End If #End If 
  3. It is a bit different using NativeObject from JavaObject. We use inline Objective-C code to directly call DBR’s APIs and relevant B4X subs have to be modified a little.

     #if b4i #If ObjC #import <DynamsoftBarcodeReader/DynamsoftBarcodeReader.h> - (DynamsoftBarcodeReader*) initializeDBR: (NSString*) license { DynamsoftBarcodeReader *dbr; dbr = [[DynamsoftBarcodeReader alloc] initWithLicense:license]; NSLog(dbr.getVersion); return dbr; } - (DynamsoftBarcodeReader*) initializeDBRFromLTS: (NSString*) organizationID { DynamsoftBarcodeReader *dbr; iDMLTSConnectionParameters* lts = [[iDMLTSConnectionParameters alloc] init]; lts.organizationID = organizationID; dbr = [[DynamsoftBarcodeReader alloc] initLicenseFromLTS:lts verificationDelegate:self]; return dbr; } - (NSArray<iTextResult*>*) decodeImage: (UIImage*) image { NSError __autoreleasing * _Nullable error; DynamsoftBarcodeReader* dbr=self->__reader.object; NSArray<iTextResult*>* result = [dbr decodeImage:image withTemplate:@"" error:&error]; NSLog(@"%lu",(unsigned long)result.count); return result; } #end if #End If 

    The entire decodeImage method:

     Sub decodeImage(bitmap As B4XBitmap) As List #if b4i Dim results As List=asNO(Me).RunMethod("decodeImage:",Array(bitmap)) Return ConvertToTextResults2(results) #Else Dim results() As Object #If b4j Dim SwingFXUtils As JavaObject SwingFXUtils.InitializeStatic("javafx.embed.swing.SwingFXUtils") Dim bufferedImage As Object=SwingFXUtils.RunMethod("fromFXImage",Array(bitmap,Null)) results=reader.RunMethod("decodeBufferedImage",Array(bufferedImage,"")) #Else If b4a results=reader.RunMethod("decodeBufferedImage",Array(bitmap,"")) #End If Return ConvertToTextResults(results) #End if End Sub 

Now, we can run the barcode reader on iOS.

iOS

Conclusion

It is easy to use B4X to create a cross-platform barcode reader. The code and UI can be shared across platforms though we still need to know a bit about native development.

Source Code

https://github.com/xulihang/BarcodeReader-B4X

The library is published on the B4X forum.

More

They will be discussed in the following articles.

Disclaimer:

The wrappers and sample code on Dynamsoft Codepool are community editions, shared as-is and not fully tested. Dynamsoft is happy to provide technical support for users exploring these solutions but makes no guarantees.