React Native connects to Zebra printer and prints by sending CPCL instructions (common to Android and iOS)

Since its release in 2015, React Native has become one of the popular cross-platform mobile development frameworks used to build thousands of mobile apps. Often, we have developers asking how to integrate the Link-OS SDK with a React Native application to print labels on a Zebra printer. In this tutorial, we will walk through how to add the Link-OS SDK to your React Native project for Android and iOS so that your React Native app can perform label printing through the native API in the Link-OS SDK.

As we all know, React Native is written entirely in JavaScript, while the Link-OS SDK library is written in Java for Android and Objective-C for iOS. Is it possible to call Java or Objective-C native methods from React Native? Thanks to the Native Module system provided in the React Native framework, the answer is yes. The Native Module system in the React Native framework allows us to expose instances of Java or Objective-C classes to JavaScript as JS objects, so we can call and execute native methods from JavaScript. We will demonstrate the steps on how to expose native methods based on the Link-OS SDK library through the native module system.

In this tutorial, we will create a React Native application with a simple use case that does only two things:

  1. Scan nearby Bluetooth printers on the APP and list paired Bluetooth printers.
  2. Click Connect to set the default printer, and click Test to send CPCL commands to control printer printing.

Here are some screenshots of this demo application:

1. Environment settings

The environment setup for React Native is well documented. Simply follow the instructions in the environment setup documentation to set up your development environment. If you have the latest Android Studio and Xcode installed on your MacBook, you can add the Link-OS SDK to your React Native project and expose native methods through the Native Module system for Android and iOS on the same MacBook.

2. Create a React native application

Once the environment is set up, you can proceed to create a new React Native application. Open a terminal, go to the folder where you want the application to reside, and launch the following CLI command to create an application named birckdogApp. The CLI may take a few minutes to complete as it creates the folder structure and sets up dependencies.

npx react-native init birckdogApp --version 0.71.0

After creating birckdogApp, you will get the following folder structure containing  android  and  ios  subfolders, which are located in the corresponding projects of Android Studio and Xcode respectively. Link-OS SDK libraries for Android and iOS will be added to  the android  and  ios  subfolders respectively. Native Java and Objective-C methods for label printing will also be created and exposed from these two sub-folders respectively through the Native Module system.

 3. Add link operating system SDK library

Download and install the Link-OS Multi-Platform SDK if you have not already done so. In the root directory of the installed SDK (link_os_sdk), you will see the following folder structure, and you can find the corresponding Link-OS SDK libraries for Android and iOS under their respective lib materials. You need to add the library to the Android project and iOS project respectively.

3.1 Add the Android version of Link-OS SDK library to the Android project

  1. Open the Android project of birckdogApp in Android Studio.
  2. Click " Open existing Android Studio project " or click "File->Open" and then navigate to the android folder and click " Open ".
  3. Select the Project view in Android Studio and create a libraries subfolder under the application folder .
  4. Copy and paste the ZSDK_Android_API.jar file into the library folder.

Note : Do not copy and paste other *.jar files that come with ZSDK_Android_API.jar (in the same folder) into the birckdogApp project. Adding these *.jar files will cause duplicate definition errors because the birckdogApp project has already added these *.jar files during project creation via the CLI command.

  1. Right click on ZSDK_Android_API.jar in the project view and select the "Add as library..." option and navigate to the libs  folder you just copied  ZSDK_Android_API.jar to and click OK.

Now, the Link-OS SDK for Android library is added to the ZSDKRCTDevDemo Android project.

 

Note : Since this tutorial will be using Bluetooth and Bluetooth scanning, the following three permissions need to be added to the AndroidManifest.xml.

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

3.2 Add the iOS version of the linked operating system SDK library to the iOS project

  1. Open birckdogApp .xcworkspace in Xcode.
  2.  Add a new folder named  ZSDK and a  subfolder named include under the birckdogApp folder in the project navigator  .
  3. Copy   and paste  libZSDK_API.a into the ZSDK  folder. Copy and paste all other header files (*.h) from the include folder in the SDK into the newly created include subfolder. This can also be done via drag and drop.
  4. Right-click on the ZSDK folder in the project navigator and select the " Add files to "birckdogApp"... " option. Then navigate to the ZSDK  folder  where we just copied the library files  and select libZSDK_API.a and click Add.
  5. Repeat step 4 above for the header file.

Now, the library of Link-OS SDK for iOS and its header files have been added to ZSDKRCTDevDemo's iOS project, as shown in the screenshot below.

Note :

  1. ExternalAccessory.framework  needs to be added to the iOS project, because the iOS version of Link-OS SDK is based on this framework for Bluetooth connection.
  2. Additionally, the com.zebra.rawport protocol string needs to be added to  the info.plist  under the "Supported External Attachment Protocols" field. This is required for Apple MFi to connect to Bluetooth on Zebra printers.

The following two screenshots show where and how to add them.

Now, we have successfully added the SDK library to the Android and iOS projects. Let's continue by exposing (or exporting) the SDK-based native printing methods to JavaScript via the native module.

4. Expose (export) printing methods through native modules

Note that this demo does not expose the entire Link-OS SDK API to JavaScript, as it is impractical and unnecessary. The correct and practical way to use the native module system is to expose the required methods implemented using the Link-OS SDK API.

The Android  Native Modules and iOS Native  Modules sections on the React Native website   detail how to expose native methods through the Native Module system for Android and iOS. They will not be repeated in this tutorial. We will focus on the implementation of this tutorial.

As mentioned at the beginning, the demo app in this tutorial will only do two things:

  1. Discover nearby Bluetooth printers on Android, or list paired Bluetooth printers on iOS.
  2. Perform a test print.

Therefore, it is sufficient for this tutorial to expose (or export) the following two methods for JavaScript to call.

// 启动蓝牙发现,并在发现完成后通过回调通知React Native
zsdkPrinterDiscoveryBluetooth(callback);

// 第一个字符串是打印机的蓝牙MAC地址
// 第二字符串现是cpcl打印命令
zsdkWriteBluetooth(macAddress,cpcl); 

 Note : When exposing native methods through native modules, it is recommended to use asynchronous macros, i.e. @ReactMethod in Java or RCT_EXPORT_METHOD macro in Objective-c. The benefit is that native methods are guaranteed to always execute on a non-UI thread in Android or iOS. This is exactly what the Link-OS SDK requires. Therefore, there is no need to explicitly use background threads to call Link-OS SDK APIs in native methods.

4.1 For Android

4.1.1 Create Android native module

To expose native Java methods, we create a class that inherits the ReactContextBaseJavaModule class and implement the methods to be exposed in the subclass. In this tutorial, we create the ZSDKModule class as a subclass of ReactContextBaseJavaModule and then implement and expose these two methods (as mentioned above) in the ZSDKModule class.

1. The ZSDKModule class extends the ReactContextBaseJavaModule class and is initialized using the ReactApplicationContext object.

2. The name of this native module is defined by the return of getName(), not by the name of the class. In this tutorial, the name is called ZSDKModule and is returned by getName().

3.zsdkWriteBluetooth() and zsdkPrinterDiscoveryBluetooth() are exposed as asynchronous methods in React Native to the JavaScript @ReactMethod macro.

4. The zsdkPrinterDiscoveryBluetooth() method will be called by JavaScript using a callback function. It then calls BluetoothDiscoverer.findPrinters() (a native API in the Link-OS SDK) to initiate Bluetooth discovery.

5. The zsdkWriteBluetooth() method will be called by JavaScript and takes the printer’s MAC address and the print format’s CPCL command as input parameters. Android uses the printer's Bluetooth MAC address to connect. 

6. Callback from JavaScript via zsdkPrinterDiscoveryBluetooth(), which will pass the discovered printer back to JavaScript as a JSON string after the Bluetooth scan is completed. This is one of the common ways to pass data from native machine back to JavaScript.

4.1.2 Exposing Android native modules

The next step is to expose the ZSDKModule we just created above to React Native. To do this we need to add ZSDKModule to the native module system.

1. Create a class named ZSDKModulePackage, which implements the ReactPackage interface.

2. Instantiate the ZSDKModule class and add the instance to the module, then return it as a native module list to register with the native module system.

3. Finally, we instantiate the ZSDKModulePackage and add the instance to the package list of the MainApplication class as shown in the screenshot below.

 

 Now, two native methods implemented in the ZSDKModule class have exposed JavaScript through the Native Module system.

4.2 For Apple

Creating and exposing native modules in iOS is much simpler than creating and exposing them in Android. It only needs to implement a class that implements the RCTBridgeModule interface. In this tutorial, we will create a class called RCTZSDKModule. See the screenshot of the code snippet below.

1. In RCTZSDKModule.h, it just defines compliance to the interface defined in RCTBridgeModule.

2. In RCTZSDKModule.m, it exposes (or exports) RCTZSDKModule as ZSDKModule to the Native Module system through the RCT_EXPORT_MODULE macro statement of RCT_EXPORT_MODULE (ZSDKModule).

Note : The module name exposed (exported) here needs to be the same as the module name in Android, since the front-end React Native uses the same name to reference native modules.

3. Then, it implements and exposes these two native methods through two RCT_EXPORT_METHOD macro statements.

  • RCT_EXPORT_METHOD(zsdkPrinterDiscoveryBluetooth:(RCTResponseSenderBlock)callback ){ ... }
  • RCT_EXPORT_METHOD(zsdkWriteBluetooth: (NSString *)printerSerialNumber data:(NSString *)data) { ... }

The zsdkWriteBluetooth method obtains the printer's serial number, establishes a Bluetooth connection with the serial number, and then prints a predefined test label. The zsdkPrinterDiscoveryBluetooth method finds a list of Zebra printer serial numbers that are paired and connected to the iOS device, and then passes the serial numbers back to JavaScript as a JSON string through the callback.

 

 

 That's all we need to do to set up a native module for iOS.

5. reactNative user interface

In the root folder of the birckdogApp project, there is an App.ts file. This is the file we will use to create the UI and its use case logic. Additionally, we created a new JavaScript file named ZSDKModule.ts in the same folder.

1.ZSDKModule.js is a file that imports ZSDKModule from a native module named "ZSDKModule". Any JavaScript file that wants to call methods in ZSDKModule only needs to import the ZSDKModule.js file.

2.PrintSettingScreen.ts is the main JavaScript file of this demo application.
The handlePrintTest function connects to the printer for printing through the MAC address and CPCL command.

The handleMatch function finds a printer that has been paired with Bluetooth

import React, { useState } from 'react';
import { View, StyleSheet, Alert, Modal, Text, TouchableOpacity, Button, ActivityIndicator } from 'react-native';
import BdButton from '../../../../src/components/bd-button';
import BdCell from '../../../../src/components/bd-cell';
import { NavigationProp, ParamListBase } from '@react-navigation/native';
import { BdStyles, BdStyleConfig } from '../../../../src/theme/bd-styles';
import BdTable, { BdTablePropsColumn } from '../../../../src/components/bd-table';
import BdElection from '../../../../src/components/bd-election';
import useAppInfo, { AppInfo } from '../../../../src/stores/appInfo';
import ZSDKModule from '../../../../src/nativeModules/ZSDKModule' 
import BdLoading from '../../../../src/components/bd-loading';
import BdToast from '../../../../src/components/bd-toast';


const PrintSettingScreen = ({ navigation }: { navigation: NavigationProp<ParamListBase> }) => {
  const { app,setAppInfo } = useAppInfo();
  const [printers, setPrinters] = useState<any[]>([]);
  const [selected, setSelected] = useState('');
  const [loading, setLoading] = useState(false);
  const toastRef = React.useRef(null) as React.MutableRefObject<any>;

  const handlePrintTest = () => {
    if(selected ==''){
      toastRef?.current?.show('请选择一个设备');
      return
    }
    var macAddress = selected;
    var cpcl ="! 0 200 200 406 1\r\n" +
    "B QR 10 80 M 2 U 8\r\n" +
    "MA,160_ML_60-000407\r\n" +
    "ENDQR\r\n" +
    "T 5 0 250 40 PartNo:160_ML_60-000407\r\n" +
    "T 5 0 250 100 QTY:2000\r\n" +
    "T 5 0 250 160 LotNo:20230604\r\n" +
    "T 5 0 250 220 D/C:2306\r\n" +
    "T 5 0 250 280 ReelID:0006\r\n" +
    "PRINT\r\n";
    ZSDKModule.zsdkWriteBluetooth(macAddress, cpcl); // Use MAC address on Android
  }

 
  const handleMatch= () => {
    // First, clear the listview
    var printersArray:any[] = [];
    setPrinters(printersArray)
    setLoading(true);

    ZSDKModule.zsdkPrinterDiscoveryBluetooth(
       // The callback to be called by the native module after Bluetooth discovery finishes.
       (error:any, discoveredPrinters:any) => {
        setLoading(false);

        if (error) {
          Alert.alert(`Error found! ${error}`);
          return;
        }

        var printersJson = JSON.parse(discoveredPrinters);
        var printersArray = []; 

         // We have both MAC address and the friendlyName on Android
         for (var i = 0; i < printersJson.length; i++) {
          printersArray.push({id: printersJson[i].address, name: `${printersJson[i].address}` + `, ` + `${printersJson[i].friendlyName}`});
        }

        // Update the listview
        setPrinters(printersArray);

      }
    );

  }
  
  const handleConnect = () =>{
    if(selected ==''){
      toastRef?.current?.show('请选择一个设备');
      return
    }
    const postData : AppInfo= {
      language:app?.language,
      ip:app?.ip,
      port:app?.port,
      bluetoothAddress:selected,
    };
    setAppInfo(postData);
   
    Alert.alert('消息','连接成功');
  }

  return (
    <View>
       <View style={styles.row}>
        <BdButton text="配对" style={styles.rowtem} onPress={ handleMatch } />
        <BdButton text="连接" style={styles.rowtem} onPress={ handleConnect }/>
        <BdButton text="测试" style={styles.rowtem} onPress={ handlePrintTest }/>
      </View>
    {
      printers.map((printer, index) => (
        <TouchableOpacity
          key = {printer.id}
          style = {selected==printer.id ?styles.selectedListItems:styles.listItems}
          onPress = {() => setSelected(printer.id)}
          >
          <Text style = {styles.listItemsText}>
            {printer.name}
          </Text>
        </TouchableOpacity>
      ))
    }

    <BdLoading visible={loading} /> 
    <BdToast ref={toastRef} />
  </View>
  );
};

6. Introduction to CPCL

CPCL refers to "Zebra Card Printer Command Language", which is a printer command language developed by Zebra and is used to control Zebra card printers for printing operations. The CPCL language provides a series of commands and parameters for setting printer configuration parameters, drawing graphics, printing text, barcodes and QR codes, etc. Through the CPCL language, users can flexibly control and customize the printer's behavior to meet different printing needs. CPCL language is usually sent to the printer in the form of ASCII text, and can communicate with the printer through serial port, network or Bluetooth.

! 0 200 200 406 1
B QR 10 80 M 2 U 8
MA,160_ML_60-000407
ENDQR
T 5 0 250 40 PartNo:160_ML_60-000407
T 5 0 250 100 QTY:2000
T 5 0 250 160 LotNo:20230604
T 5 0 250 220 D/C:2306
T 5 0 250 280 ReelID:0006
PRINT

The above code is a label printing command that will generate a label with a QR code and text information. The specific explanation is as follows:

First line! 0 200 200 406 1 Set the printer configuration parameters, including printing speed, printing density, etc.

Second line B QR 10 80 M 2 U 8 Set the format and position of the QR code, where QR means using the QR code format, 10 and 80 respectively represent the horizontal and vertical positions of the QR code, and M represents the rotation angle of the QR code is 0 degrees, 2 means that the size of the QR code is 2 units, and U means that the error correction level of the QR code is 8.

The third line MA,160_ML_60-000407 is the content of the QR code, that is, the information stored in the QR code.

The fourth to eighth lines starting with T represent the text information to be printed on the label, where 5 0 250 X is the specific position value).

The last line PRINT means printing the label.

Guess you like

Origin blog.csdn.net/qq243348167/article/details/132141477