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.