How to Create a NuGet Package for Both Desktop and Mobile Development
Tags: maui, dotnet, android, ios
Published: 2023-04-06 08:49:47

In my [previous article](https://www.dynamsoft.com/codepool/nuget-cpp-barcode-sdk-package.html), 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.


<video src="https://user-images.githubusercontent.com/2202306/178880069-4a50cd11-77e9-45b5-8c3a-bd9b63ff85fa.mp4" controls="controls" muted="muted" style="max-height:640px; min-height: 200px; max-width: 100%;"></video>

## Download BarcodeQRCodeSDK
[https://www.nuget.org/packages/BarcodeQRCodeSDK](https://www.nuget.org/packages/BarcodeQRCodeSDK)

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

![All-in-one NuGet package](https://www.dynamsoft.com/codepool/img/2023/04/nuget-package-all-in-one.png)

- 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](https://www.dynamsoft.com/codepool/dotnet-android-ios-nuget-package.html).

![.NET Android and iOS binding projects](https://www.dynamsoft.com/codepool/img/2023/04/dotnet-android-ios-binding.png)

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](https://www.dynamsoft.com/codepool/img/2023/04/visual-studio-macos-error.png)

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.

```xml
<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**
    ```csharp
    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**
    ```csharp
    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**
    ```csharp
    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**
    ```csharp
    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**
    ```csharp
    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**
    ```csharp
    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](https://www.dynamsoft.com/codepool/img/2023/04/dotnet-ios-binding-issue.png)

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.
```xml
<!-- 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/maui-barcode-qrcode-scanner](https://github.com/yushulx/maui-barcode-qrcode-scanner) 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](https://www.dynamsoft.com/codepool/img/2023/04/nuget-dotnet-barcode-qr-sdk.png)

2. Modify the platform-specific code as follows:
    ```diff
    - 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](https://www.dynamsoft.com/codepool/img/2023/04/maui-barcode-qrcode-scanner.png)


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

## Source Code
[https://github.com/yushulx/dotnet-barcode-qr-code-sdk](https://github.com/yushulx/dotnet-barcode-qr-code-sdk)