imported Android Native Modules are always 'undefined'

2.5k views Asked by At

I have made native android modules for react-native. I ejected the app and added the native code to print using a blue-tooth thermal printer. I have tested the native android code using android studio and it printed well. After I have added the module to react-native android, it printed text a few times. Later on, it stopped printing. I always get the imported module as undefined in Javascript.

BlueToothUtils.java: This file is inside react-projectfolder\android\app\src\main\java\\thermal_printer\

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.util.Log;

import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Set;
import java.util.UUID;

public class BlueToothUtils extends ReactContextBaseJavaModule {

    public static BluetoothAdapter bluetoothAdapter=null;
    public static BluetoothDevice chosen_printer=null;
    BluetoothSocket bt_socket = null;
    public static UUID DEFAULT_UUID = null;
    public UUID device_uuid=null;
    public OutputStream output_stream=null;
    boolean bluetooth_status=false;
    String error_message="";


    @Override
    public String getName() {
        return "BlueToothUtils";
    }

    public BlueToothUtils(ReactApplicationContext reactContext) {
        super(reactContext);
        context=reactContext;
    }

    @ReactMethod
    public void initAdapter(Callback returnStatus)
    {
        try {
            DEFAULT_UUID = UUID.fromString("00000000-0000-1000-8000-00805F9B34FB");
            bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
            if (bluetoothAdapter == null) {
                bluetooth_status = false;
                error_message = "Cannot access Bluetooth adapter. Make sure bluetooth is turned ON.";
            } else if (bluetoothAdapter.isEnabled())
                bluetooth_status = true;
            else {
                bluetooth_status = false;
                error_message = "Bluetooth not enabled.";
            }
            if(bluetooth_status)
                returnStatus.invoke(true,"");
            else
                returnStatus.invoke(false,error_message);
        }catch (Exception ex){returnStatus.invoke(false,ex.getMessage());}
    }

    @ReactMethod
    public void getPairedDevices(Callback returnCallback)
    {
        try {
            if (!bluetooth_status)
                returnCallback.invoke(false, "[]");
            else {
                PairedDevice paired_device;
                Set<BluetoothDevice> bonded_devices = bluetoothAdapter.getBondedDevices();
                String json = "[";
                boolean found = false;
                for (BluetoothDevice device : bonded_devices) {
                    found = true;
                    paired_device = new PairedDevice(device.getName(), device.getAddress());
                    json += paired_device.toString() + ",";
                }
                if (found)
                    json = json.substring(0, json.length() - 1) + "]";
                else
                    json = "[]";
                returnCallback.invoke(true, json);
            }
        }catch (Exception ex){returnCallback.invoke(false,ex.getMessage());}
    }

    @ReactMethod
    public void chooseDevice(String address,Callback returnCallback)
    {
        try {
            chosen_printer = bluetoothAdapter.getRemoteDevice(address);
            returnCallback.invoke(true,"Chosen Successfully");
        }catch (Exception ex){returnCallback.invoke(false,ex.getMessage());}
    }

    public static OutputStream getOutputStream() throws IOException {
        BluetoothSocket socket=null;
        UUID uuid= chosen_printer.getUuids()[0].getUuid();
        try {
            socket = chosen_printer.createInsecureRfcommSocketToServiceRecord(uuid);
        }catch (Exception ex){
            try{
                socket=chosen_printer.createInsecureRfcommSocketToServiceRecord(DEFAULT_UUID);
            }catch (Exception exc){exc.printStackTrace();Log.d("ttt", exc.toString());}
        }
        if (socket==null)
        {
            return  null;
        }
        bluetoothAdapter.cancelDiscovery();
        socket.connect();
        OutputStream os=socket.getOutputStream();
        return os;
    }
}

The other module in the same package is PrintUtils.java:

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Callback;
import java.io.OutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.content.res.AssetManager;

public class PrintUtils extends ReactContextBaseJavaModule {

    public static final int ALIGN_LEFT = 0;
    public static final int ALIGN_CENTER = 1;
    public static final int ALIGN_RIGHT = 2;

    public static final int FONT_NORMAL = 0;
    public static final int FONT_BOLD_AND_NORMAL_SIZE = 1;
    public static final int FONT_BOLD_AND_MEDIUM_SIZE = 2;
    public static final int FONT_BOLD_AND_LARGE_SIZE = 3;

    public static final byte[] ESC_ALIGN_LEFT = new byte[]{0x1b, 'a', 0x00};
    public static final byte[] ESC_ALIGN_RIGHT = new byte[]{0x1b, 'a', 0x02};
    public static final byte[] ESC_ALIGN_CENTER = new byte[]{0x1b, 'a', 0x01};
    public static final byte LF = 0x0A;
    public static byte[] FEED_LINE = {10};
    private static String[] binaryArray = {"0000", "0001", "0010", "0011",
            "0100", "0101", "0110", "0111", "1000", "1001", "1010", "1011",
            "1100", "1101", "1110", "1111"};
    private static String hexStr = "0123456789ABCDEF";
    public OutputStream outputStream = null;
    public boolean is_connected = false;
    public ReactApplicationContext context=null;

    public PrintUtils(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    private static byte charToByte(char c) {
        return (byte) "0123456789ABCDEF".indexOf(c);
    }

    public static byte[] sysCopy(List<byte[]> srcArrays) {
        int len = 0;
        for (byte[] srcArray : srcArrays) {
            len += srcArray.length;
        }
        byte[] destArray = new byte[len];
        int destLen = 0;
        for (byte[] srcArray : srcArrays) {
            System.arraycopy(srcArray, 0, destArray, destLen, srcArray.length);
            destLen += srcArray.length;
        }
        return destArray;
    }

    public static byte[] hexStringToBytes(String hexString) {
        if (hexString == null || hexString.equals("")) {
            return null;
        }
        hexString = hexString.toUpperCase();
        int length = hexString.length() / 2;
        char[] hexChars = hexString.toCharArray();
        byte[] d = new byte[length];
        for (int i = 0; i < length; i++) {
            int pos = i * 2;
            d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
        }
        return d;
    }

    public static String myBinaryStrToHexString(String binaryStr) {
        String hex = "";
        String f4 = binaryStr.substring(0, 4);
        String b4 = binaryStr.substring(4, 8);
        for (int i = 0; i < binaryArray.length; i++) {
            if (f4.equals(binaryArray[i]))
                hex += hexStr.substring(i, i + 1);
        }
        for (int i = 0; i < binaryArray.length; i++) {
            if (b4.equals(binaryArray[i]))
                hex += hexStr.substring(i, i + 1);
        }

        return hex;
    }

    public static byte[] hexList2Byte(List<String> list) {
        List<byte[]> commandList = new ArrayList<byte[]>();

        for (String hexStr : list) {
            commandList.add(hexStringToBytes(hexStr));
        }
        byte[] bytes = sysCopy(commandList);
        return bytes;
    }

    public static List<String> binaryListToHexStringList(List<String> list) {
        List<String> hexList = new ArrayList<String>();
        for (String binaryStr : list) {
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < binaryStr.length(); i += 8) {
                String str = binaryStr.substring(i, i + 8);

                String hexString = myBinaryStrToHexString(str);
                sb.append(hexString);
            }
            hexList.add(sb.toString());
        }
        return hexList;

    }

    public static byte[] decodeBitmap(Bitmap bmp) {
        int bmpWidth = bmp.getWidth();
        int bmpHeight = bmp.getHeight();

        List<String> list = new ArrayList<String>(); //binaryString list
        StringBuffer sb;


        int bitLen = bmpWidth / 8;
        int zeroCount = bmpWidth % 8;

        String zeroStr = "";
        if (zeroCount > 0) {
            bitLen = bmpWidth / 8 + 1;
            for (int i = 0; i < (8 - zeroCount); i++) {
                zeroStr = zeroStr + "0";
            }
        }

        for (int i = 0; i < bmpHeight; i++) {
            sb = new StringBuffer();
            for (int j = 0; j < bmpWidth; j++) {
                int color = bmp.getPixel(j, i);

                int r = (color >> 16) & 0xff;
                int g = (color >> 8) & 0xff;
                int b = color & 0xff;

                // if color close to white,bit='0', else bit='1'
                if (r > 160 && g > 160 && b > 160)
                    sb.append("0");
                else
                    sb.append("1");
            }
            if (zeroCount > 0) {
                sb.append(zeroStr);
            }
            list.add(sb.toString());
        }

        List<String> bmpHexList = binaryListToHexStringList(list);
        String commandHexString = "1D763000";
        String widthHexString = Integer
                .toHexString(bmpWidth % 8 == 0 ? bmpWidth / 8
                        : (bmpWidth / 8 + 1));
        if (widthHexString.length() > 2) {
            Log.e("decodeBitmap error", " width is too large");
            return null;
        } else if (widthHexString.length() == 1) {
            widthHexString = "0" + widthHexString;
        }
        widthHexString = widthHexString + "00";

        String heightHexString = Integer.toHexString(bmpHeight);
        if (heightHexString.length() > 2) {
            Log.e("decodeBitmap error", " height is too large");
            return null;
        } else if (heightHexString.length() == 1) {
            heightHexString = "0" + heightHexString;
        }
        heightHexString = heightHexString + "00";

        List<String> commandList = new ArrayList<String>();
        commandList.add(commandHexString + widthHexString + heightHexString);
        commandList.addAll(bmpHexList);

        return hexList2Byte(commandList);
    }

    @Override
    public Map<String, Object> getConstants() {
        final Map<String, Object> constants = new HashMap<>();
        constants.put("ALIGN_LEFT", ALIGN_LEFT);
        constants.put("ALIGN_CENTER", ALIGN_CENTER);
        constants.put("ALIGN_RIGHT", ALIGN_RIGHT);
        constants.put("FONT_NORMAL", FONT_NORMAL);
        constants.put("FONT_BOLD_AND_NORMAL_SIZE", FONT_BOLD_AND_NORMAL_SIZE);
        constants.put("FONT_BOLD_AND_MEDIUM_SIZE", FONT_BOLD_AND_MEDIUM_SIZE);
        constants.put("FONT_BOLD_AND_LARGE_SIZE", FONT_BOLD_AND_LARGE_SIZE);
        constants.put("ESC_ALIGN_LEFT", ESC_ALIGN_LEFT);
        constants.put("ESC_ALIGN_CENTER", ESC_ALIGN_CENTER);
        constants.put("ESC_ALIGN_RIGHT", ESC_ALIGN_RIGHT);
        constants.put("LF", LF);


        return constants;
    }

    @Override
    public String getName() {
        return "PrintUtils";
    }

    @ReactMethod
    public void printText(String text, int size, int align, Callback returnCallback) {
        try {
            if (is_connected == false) {
                outputStream = BlueToothUtils.getOutputStream();
                is_connected = true;
            }
            //Print config "mode"
            byte[] cc = new byte[]{0x1B, 0x21, 0x03};  // 0- normal size text
            byte[] bb = new byte[]{0x1B, 0x21, 0x08};  // 1- only bold text
            byte[] bb2 = new byte[]{0x1B, 0x21, 0x20}; // 2- bold with medium text
            byte[] bb3 = new byte[]{0x1B, 0x21, 0x10}; // 3- bold with large text
            switch (size) {
                case FONT_NORMAL:
                    outputStream.write(cc);
                    break;
                case FONT_BOLD_AND_NORMAL_SIZE:
                    outputStream.write(bb);
                    break;
                case FONT_BOLD_AND_MEDIUM_SIZE:
                    outputStream.write(bb2);
                    break;
                case FONT_BOLD_AND_LARGE_SIZE:
                    outputStream.write(bb3);
                    break;
            }

            switch (align) {
                case ALIGN_LEFT:
                    //left align
                    outputStream.write(ESC_ALIGN_LEFT);
                    break;
                case ALIGN_CENTER:
                    //center align
                    outputStream.write(ESC_ALIGN_CENTER);
                    break;
                case ALIGN_RIGHT:
                    //right align
                    outputStream.write(ESC_ALIGN_RIGHT);
                    break;
            }
            outputStream.write(text.getBytes());
            outputStream.write(LF);
            outputStream.flush();
            returnCallback.invoke(true, "success");
        } catch (Exception e) {
            returnCallback.invoke(false, "error:" + e.getMessage());
        }
    }

    @ReactMethod
    public void printPhoto(Callback returnCallback) {
        try {
            AssetManager assetmgr=getReactApplicationContext().getAssets();
            InputStream inp=assetmgr.open("pics/logo.bmp");
            Bitmap bmp=BitmapFactory.decodeStream(inp);
            if (bmp != null)
                {
                if (!is_connected)
                    {
                    outputStream = BlueToothUtils.getOutputStream();
                    is_connected = true;
                    }
                byte[] command = PrintUtils.decodeBitmap(bmp);
                outputStream.write(new byte[]{0x1b, 'a', 0x01});
                outputStream.write(command);
                returnCallback.invoke(true, "success");
                }
            else
                {
                returnCallback.invoke(false, "error:" + "Image not found");
                }
            }
        catch (Exception e)
            {
            returnCallback.invoke(false, "error: Exception" + e.getMessage());
            }
    }

    @ReactMethod
    public void cutPaper(Callback returnCallback) {
        try {
            outputStream.write(LF);
            outputStream.write(LF);
            outputStream.write(LF);
            outputStream.flush();
            returnCallback.invoke(true, "Success");
        } catch (Exception ex) {
            returnCallback.invoke(false, "error:" + ex.getMessage());
        }
    }

    @ReactMethod
    public void closeConnection(Callback returnCallback) {
        try {
            outputStream.close();
            is_connected = false;
            returnCallback.invoke(true, "Success");
        } catch (Exception e) {
            returnCallback.invoke(false, "error:" + e.getMessage());
        }
    }

}

There is another class PairedDevice PairedDevice.java in same directory:

public class PairedDevice {
    public String name="Noname";
    public String address=null;

    public PairedDevice(String dev_name, String dev_address)    {
        name=dev_name;
        address=dev_address;
    }


    public String toString()
    {
        return "{\"name\":\""+name+"\",\"address\":\""+address+"\"}";
    }

}

The package class in same directory is ThermalPrinterPackage :

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import android.content.Context;

public class ThermalPrinterPackage implements ReactPackage {

    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext)
        {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new BlueToothUtils(reactContext));
        modules.add(new PrintUtils(reactContext));
        return modules;
        }

    @Override
    public List<Class<? extends JavaScriptModule>> createJSModules() {
        return Collections.emptyList();
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
}

The MainApplication.java file is now as follows:

import dcloud.codesap.com.thermal_printer.*;
import android.support.multidex.MultiDexApplication;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.react.ReactPackage;

import java.util.Arrays;
import java.util.List;

// Needed for `react-native link`

import com.facebook.react.ReactApplication;

public class MainApplication extends MultiDexApplication {

  // Needed for `react-native link`

  public List<ReactPackage> getPackages() {

    return Arrays.<ReactPackage>asList(

        // Add your own packages here!
        // TODO: add cool native modules
        // Needed for `react-native link`
        new MainReactPackage(),
        new ThermalPrinterPackage(),
    );

}
}

I am importing and using it as follows:

import BlueToothUtils from '../../BluetoothUtils';
import PrintUtils from '../../PrintUtils';
class Signin extends Component {

  constructor(props) {
    super(props);
    this.state = { username: 'demo',
                  password: 'demo',
                  token: '1234'
                };
    this.getToken = this.getToken.bind(this);
    this.navIn = this.navIn.bind(this);
  }

  async componentDidMount() {
    try{
      await console.log(BlueToothUtils);
      await BlueToothUtils.initAdapter((boo,msg)=>{console.log(boo,msg);});
      await BlueToothUtils.getPairedDevices((boo,msg)=>{console.log(boo,'Paired devices:'+msg);});
      await BlueToothUtils.chooseDevice("0F:02:17:42:55:C7",(boo,msg)=>{console.log(boo,msg);});
      await PrintUtils.printText("Normal Text", PrintUtils.FONT_NORMAL, PrintUtils.ALIGN_LEFT,(boo,msg)=>{console.log(boo,msg);});
      await PrintUtils.printText("BOLD Normal text", PrintUtils.FONT_BOLD_AND_NORMAL_SIZE, PrintUtils.ALIGN_CENTER,(boo,msg)=>{console.log(boo,msg);});
      await PrintUtils.printText("BOLD Large text", PrintUtils.FONT_BOLD_AND_LARGE_SIZE, PrintUtils.ALIGN_RIGHT,(boo,msg)=>{console.log(boo,msg);});
      await PrintUtils.printPhoto((boo,msg)=>{console.log(boo,msg);});
      await PrintUtils.cutPaper();
    }catch(err){console.log('Error in index.js:componentWillMount: '+err.message)};
  }

...

BlueToothUtils.js:

import { NativeModules } from 'react-native';
var bu= NativeModules.BlueToothUtils;
export default bu;

PrintUtils.js:

import { NativeModules } from 'react-native';
var pu=NativeModules.PrintUtils;
export default pu;

It has worked a few times. It did not print image after i added module to react-native. But it did print most of the time. But now it is not printing at all, not even text. Can anyone help? Thanks in advance.

1

There are 1 answers

0
Mohammed Shareef C On BEST ANSWER

At last I found that the issue was with the export of native module. Instead of exporting the instance of the native java object, i exported each method and constant of the object individually. And instead of export default, i used export const

import { NativeModules } from 'react-native';
var pu=NativeModules.PrintUtils;
export default pu;

The code beccame like this:

import { NativeModules } from 'react-native'


export const getPairedDevices = (callback) => {
  NativeModules.PrintUtils.getPairedDevices(callback);
}

export const printText = (text,size,align,mac_address,returnCallback) => {
  NativeModules.PrintUtils.printText(text,size,align,mac_address,returnCallback);
}

export const ALIGN_LEFT=NativeModules.PrintUtils.ALIGN_LEFT;

I don't know what difference it makes with react-native. But things began to work when i got rid of export default. Hope this will help someone.