How to Create a NuGet Package for Both Desktop and Mobile Development

In my previous article, I demonstrated how to package C++ libraries into a NuGet package. This week, I’ll be expanding the package to include Android and iOS binding libraries, enabling multi-platform development across Windows, Linux, macOS, Android and iOS. The advantage of an all-in-one NuGet package is that you can utilize unified .NET APIs to create barcode and QR Code scanning applications for both desktop and mobile platforms, eliminating the need for platform-specific code.

Download BarcodeQRCodeSDK

https://www.nuget.org/packages/BarcodeQRCodeSDK

What doe the All-in-One NuGet Package Look Like?

All-in-one NuGet package

  • The BarcodeQRCodeSDK.targets file contains the build instructions for Visual C++ projects.
  • The net6.0 folder contains the .NET binding libraries for Windows, Linux and macOS. The runtimes folder contains the C++ libraries (*.dll, *.so and *.dylib) invoked by the .NET binding libraries.
  • The net6.0-android31.0 folder contains the .NET binding libraries and the *.aar files for Android.
  • The net6.0-ios16.1 folder contains the .NET binding libraries and the .xcframework package for iOS.

How to Create Unified .NET APIs for Android and iOS Binding Libraries

To add new Android and iOS binding library projects to the BarcodeQRCodeSDK solution in Visual Studio, simply right-click on the solution and select “Add”. For detailed steps of building the Android and iOS libraries, please refer to https://www.dynamsoft.com/codepool/dotnet-android-ios-nuget-package.html.

.NET Android and iOS binding projects

The BarcodeQRCodeSDK solution now includes three sub-projects. You need to run Pair to Mac before building the iOS binding library on Windows. While attempting to build the solution on macOS, you will encounter the following error when importing the project into the Visual Studio for Mac. The workaround is to build the iOS binding project independently on macOS.

Visual Studio macOS error

Since our goal is to create an all-in-one NuGet package, it is best to ensure that the output names of the Android and iOS binding libraries are consistent with the .NET binding library. To achieve this, we can add the AssemblyName property to the *.csproj file of the Android and iOS binding libraries.

<AssemblyName>BarcodeQRCodeSDK</AssemblyName>

At this point, both binding libraries are ready for use. However, the APIs are not yet unified. To achieve this, you need to create a BarcodeQRCodeReader.cs file in the Android and iOS binding library projects respectively to implement additional functionality. Referring to the existing BarcodeQRCodeReader.cs file in the .NET binding library project, you can write the following code for the Android and iOS binding libraries.

  1. Import the dependent namespaces.

    Android

     using System;
     using System.Collections.Generic;
     using System.Linq;
     using System.Runtime.InteropServices;
     using System.Text;
     using System.Threading.Tasks;
     using Android.Hardware.Lights;
     using Com.Dynamsoft.Dbr;
     using Java.Nio;
     using Java.Nio.FileNio.Attributes;
    

    iOS

     using System;
     using System.Collections.Generic;
     using System.Linq;
     using System.Text;
     using System.Threading.Tasks;
    
     using com.Dynamsoft.Dbr;
     using System.Runtime.InteropServices;
     using Foundation;
    
  2. Create a BarcodeQRCodeReader class, a Result class and an ImagePixelFormat enum:

    Android

     namespace Dynamsoft
     {
         public class BarcodeQRCodeReader
         {
             private BarcodeReader? reader;
    
             public class Result
             {
                 public string? Text { get; set; }
                 public int[]? Points { get; set; }
                 public string? Format1 { get; set; }
                 public string? Format2 { get; set; }
             }
    
             public enum ImagePixelFormat
             {
                 IPF_BINARY,
                 IPF_BINARYINVERTED,
                 IPF_GRAYSCALED,
                 IPF_NV21,
                 IPF_RGB_565,
                 IPF_RGB_555,
                 IPF_RGB_888,
                 IPF_ARGB_8888,
                 IPF_RGB_161616,
                 IPF_ARGB_16161616,
                 IPF_ABGR_8888,
                 IPF_ABGR_16161616,
                 IPF_BGR_888
             }
         }
     }
    

    iOS

     namespace Dynamsoft
     {
         public class BarcodeQRCodeReader
         {
             private DynamsoftBarcodeReader? reader;
    
             public class Result
             {
                 public string? Text { get; set; }
                 public int[]? Points { get; set; }
                 public string? Format1 { get; set; }
                 public string? Format2 { get; set; }
             }
    
             public enum ImagePixelFormat
             {
                 IPF_BINARY,
                 IPF_BINARYINVERTED,
                 IPF_GRAYSCALED,
                 IPF_NV21,
                 IPF_RGB_565,
                 IPF_RGB_555,
                 IPF_RGB_888,
                 IPF_ARGB_8888,
                 IPF_RGB_161616,
                 IPF_ARGB_16161616,
                 IPF_ABGR_8888,
                 IPF_ABGR_16161616,
                 IPF_BGR_888
             }
         }
     }
    
  3. Implement the InitLicense(), Create(), GetVersionInfo(), DecodeFile(), DecodeBuffer(), DecodeBase64() and SetParameters methods:

    Android

     public class DBRLicenseVerificationListener : Java.Lang.Object, IDBRLicenseVerificationListener
     {
         public void DBRLicenseVerificationCallback(bool isSuccess, Java.Lang.Exception error)
         {
             if (!isSuccess)
             {
                 System.Console.WriteLine(error.Message);
             }
         }
     }
    
     public static void InitLicense(string license)
     {
         BarcodeReader.InitLicense(license, new DBRLicenseVerificationListener());
     }
    
     private BarcodeQRCodeReader()
     {
         reader = new BarcodeReader();
     }
    
     public static BarcodeQRCodeReader Create()
     {
         return new BarcodeQRCodeReader();
     }
    
     ~BarcodeQRCodeReader()
     {
         Destroy();
     }
    
     public void Destroy()
     {
         reader = null;
     }
    
     public static string? GetVersionInfo()
     {
         return BarcodeReader.Version;
     }
    
     private Result[]? OutputResults(TextResult[]? results)
     {
         Result[]? output = null;
         if (results != null && results.Length > 0)
         {
             output = new Result[results.Length];
             for (int i = 0; i < results.Length; ++i)
             {
                 TextResult tmp = results[i];
                 Result r = new Result();
                 output[i] = r;
                 r.Text = tmp.BarcodeText;
                 r.Format1 = tmp.BarcodeFormatString;
                 r.Format2 = tmp.BarcodeFormatString;
                 if (tmp.LocalizationResult != null && tmp.LocalizationResult.ResultPoints != null)
                 {
                     IList<Point> points = tmp.LocalizationResult.ResultPoints;
                     r.Points = new int[8] { points[0].X, points[0].Y, points[1].X, points[1].Y, points[2].X, points[2].Y, points[3].X, points[3].Y };
                 }
                 else
                     r.Points = null;
             }
         }
    
         return output;
     }
    
     public Result[]? DecodeFile(string filename)
     {
         if (reader == null) { return null; }
    
         TextResult[]? results = reader.DecodeFile(filename);
         return OutputResults(results);
     }
    
     public Result[]? DecodeBuffer(byte[] buffer, int width, int height, int stride, ImagePixelFormat format)
     {
         if (reader == null) { return null; }
    
         TextResult[]? results = reader.DecodeBuffer(buffer, width, height, stride, (int)format);
    
         return OutputResults(results);
     }
    
     public Result[]? DecodeBase64(string base64string)
     {
         if (reader == null) { return null; }
         TextResult[]? results = reader.DecodeBase64String(base64string);
    
         return OutputResults(results);
     }
    
     public void SetParameters(string parameters)
     {
         if (reader == null) { return; }
    
         reader.InitRuntimeSettingsWithString(parameters, EnumConflictMode.CmOverwrite);
     }
    

    iOS

     public class Listener : DBRLicenseVerificationListener
     {
         public void DBRLicenseVerificationCallback(bool isSuccess, NSError error)
         {
             if (error != null)
             {
                 System.Console.WriteLine(error.UserInfo);
             }
         }
     }
    
     public static void InitLicense(string license)
     {
         DynamsoftBarcodeReader.InitLicense(license, new Listener());
     }
    
     private BarcodeQRCodeReader()
     {
         reader = new DynamsoftBarcodeReader();
     }
    
     public static BarcodeQRCodeReader Create()
     {
         return new BarcodeQRCodeReader();
     }
    
     ~BarcodeQRCodeReader()
     {
         Destroy();
     }
    
     public void Destroy()
     {
         reader = null;
     }
    
     public static string? GetVersionInfo()
     {
         return DynamsoftBarcodeReader.Version;
     }
    
     private Result[]? OutputResults(iTextResult[]? results)
     {
         Result[]? output = null;
         if (results != null && results.Length > 0)
         {
             output = new Result[results.Length];
             for (int i = 0; i < results.Length; ++i)
             {
                 iTextResult tmp = results[i];
                 Result r = new Result();
                 output[i] = r;
                 r.Text = tmp.BarcodeText;
                 r.Format1 = tmp.BarcodeFormatString;
                 r.Format2 = tmp.BarcodeFormatString;
                 if (tmp.LocalizationResult != null && tmp.LocalizationResult.ResultPoints != null)
                 {
                     NSObject[] points = tmp.LocalizationResult.ResultPoints;
                     r.Points = new int[8] { (int)((NSValue)points[0]).CGPointValue.X, (int)((NSValue)points[0]).CGPointValue.Y, (int)((NSValue)points[1]).CGPointValue.X, (int)((NSValue)points[1]).CGPointValue.Y, (int)((NSValue)points[2]).CGPointValue.X, (int)((NSValue)points[2]).CGPointValue.Y, (int)((NSValue)points[3]).CGPointValue.X, (int)((NSValue)points[3]).CGPointValue.Y };
                 }
                 else
                     r.Points = null;
             }
         }
    
         return output;
     }
    
     public Result[]? DecodeFile(string filename)
     {
         if (reader == null) { return null; }
    
         NSError error;
         iTextResult[]? results = reader.DecodeFileWithName(filename, out error);
         return OutputResults(results);
     }
    
     public Result[]? DecodeBuffer(byte[] myBytes, int width, int height, int stride, ImagePixelFormat format)
     {
         if (reader == null) { return null; }
    
         NSError error;
         IntPtr buffer = Marshal.AllocHGlobal(myBytes.Length);
         Marshal.Copy(myBytes, 0, buffer, myBytes.Length);
         NSData data = NSData.FromBytes(buffer, (nuint)myBytes.Length);
            
         iTextResult[]? results = reader.DecodeBuffer(data, width, height, stride, (EnumImagePixelFormat)format, out error);
         Marshal.FreeHGlobal(buffer);
    
         return OutputResults(results);
     }
    
     public Result[]? DecodeBase64(string base64string)
     {
         if (reader == null) { return null; }
    
         NSError error;
         iTextResult[]? results = reader.DecodeBase64(base64string, out error);
    
         return OutputResults(results);
     }
    
     public void SetParameters(string parameters)
     {
         if (reader == null) { return; }
    
         NSError error;
         reader.InitRuntimeSettingsWithString(parameters, EnumConflictMode.Overwrite, out error);
     }
    

Building the Android binding library does not present any issues. However, when attempting to build the iOS binding library using Visual Studio on macOS, red squiggles may appear.

.NET ios binding issue

Don’t panic. You can safely ignore these red squiggles and build the project successfully.

How to Configure *.nuspec File to Include Android and iOS Binding Libraries

If you have successfully built the Android and iOS binding libraries, the next step is to configure the *.nuspec file to include them in the NuGet package.

<!-- Android -->
<file src="android\bin\Release\net6.0-android\**\*.*" target="lib\net6.0-android31.0" />

<!-- iOS -->
<file src="ios/manifest" target="lib/net6.0-ios16.1/BarcodeQRCodeSDK.resources" />
<file src="ios\bin\Release\net6.0-ios\BarcodeQRCodeSDK.dll" target="lib\net6.0-ios16.1" />
<file src="ios\bin\Release\net6.0-ios\BarcodeQRCodeSDK.resources\**\*.*" target="lib\net6.0-ios16.1\BarcodeQRCodeSDK.resources" />
<file src="ios/bin/Release/net6.0-ios/BarcodeQRCodeSDK.resources/DynamsoftBarcodeReader.xcframework/ios-arm64/DynamsoftBarcodeReader.framework/DynamsoftBarcodeReader" target="lib/net6.0-ios16.1/BarcodeQRCodeSDK.resources/DynamsoftBarcodeReader.xcframework/ios-arm64/DynamsoftBarcodeReader.framework" />
<file src="ios/bin/Release/net6.0-ios/BarcodeQRCodeSDK.resources/DynamsoftBarcodeReader.xcframework/ios-arm64_x86_64-simulator/DynamsoftBarcodeReader.framework/DynamsoftBarcodeReader" target="lib/net6.0-ios16.1/BarcodeQRCodeSDK.resources/DynamsoftBarcodeReader.xcframework/ios-arm64_x86_64-simulator/DynamsoftBarcodeReader.framework" />

Please note that while the wildcard **\*.* includes all files and subfolders in the designated folder, it will not account for the DynamsoftBarcodeReader assembly file. Therefore, you must add this file manually.

How to Update MAUI Barcode QR Code Scanner with the All-in-One NuGet Package

To try out the all-in-one NuGet package, you can download the https://github.com/yushulx/dotnet-barcode-qr-code-sdk/tree/main/example/maui project. This project was developed using the Android and iOS binding libraries of Dynamsoft Barcode Reader.

Here are the steps to update the project with the all-in-one NuGet package:

  1. Replace Barcode.NET.Mobile with BarcodeQRCodeSDK.

    NuGet .NET barcode QR SDK

  2. Modify the platform-specific code as follows:

     - using BarcodeQrScanner.Services;
     + using Dynamsoft;
    
     - BarcodeQRCodeService _barcodeQRCodeService;
     + BarcodeQRCodeReader _reader;
    
     - data = _barcodeQRCodeService.DecodeFile(path);
     + BarcodeQRCodeReader.Result[] results = _reader.DecodeFile(imagepath);
    
     - TextResult[] results = barcodeReader.DecodeBuffer(image.GetYuvData(), previewWidth, previewHeight, stridelist[0], EnumImagePixelFormat.IpfNv21);
     + BarcodeQRCodeReader.Result[] results = barcodeReader.DecodeBuffer(image.GetYuvData(), previewWidth, previewHeight, stridelist[0], BarcodeQRCodeReader.ImagePixelFormat.IPF_NV21);
    
     - results = reader.DecodeBuffer(buffer, width, height, bpr, EnumImagePixelFormat.Argb8888, out errorr);
     + byte[] bytearray = new byte[buffer.Length];
     + System.Runtime.InteropServices.Marshal.Copy(buffer.Bytes, bytearray, 0, Convert.ToInt32(buffer.Length));
     + results = reader.DecodeBuffer(bytearray, (int)width, (int)height, (int)bpr, BarcodeQRCodeReader.ImagePixelFormat.IPF_ARGB_8888);
    
  3. Run the MAUI Barcode QR Code Scanner on Android and iOS.

    MAUI barcode QR code scanner

The modified MAUI Barcode QR Code Scanner project is available at https://github.com/yushulx/dotnet-barcode-qr-code-sdk/tree/main/example/maui.

Source Code

https://github.com/yushulx/dotnet-barcode-qr-code-sdk