Impressora Bematech MP-4200 TH USB no React Native para Android

Adriano Moreira da Silva
Codengage
Published in
7 min readJan 18, 2020

Pré requisitos:

  • Noções básicas de desenvolvimento em React Native e Android.
  • Ambiente react-native para android já configurado com Android Studio.

Primeiro passo é baixar a Lib da impressora para Android.

Atualmente o link é esse: http://bematechpartners.com.br/portalPartners/index.php/impressoras-termicas-android/

Acesse a aba Middleware.

Como meu objetivo é utilizar em uma impressora MP-4200 TH, fiz o download da versão da Lib para comunicação via USB.

Dentro do zip tem um projeto de exemplo apenas em Android Nativo, um PDF com algumas instruções, mas é o arquivo BemaLibAndroid-v1.0.0.aar que é oque mais nos interessa, ele é a Lib.

Crie um projeto react-native:

npx react-native init RNSampleWithBemaAndroidLib

Abra a pasta android do projeto criado com o Android Studio.

Espere o Android Studio terminar o Sync, para ter certeza que o ambiente e o projeto estão ok.

Importe o arquivo .aar como um modulo do projeto.

Abra a tela ‘Projec Structure’ no menu ‘File’, no painel a esquerda selecione a seção ‘Modules’.

Clique no botão ‘+’, selecione a opção ‘import .JAR/ AAR Package’.

Selecione o arquivo com final .arr do zip que baixamos no site da Bematech.

Clique em Finish e espere mais um Sync.

Agora na seção ‘Dependencies’, selecione o modulo app e adicione o modulo da Bematech como uma dependência.

Espere mais um Sync e finalmente tudo rodando lindo… só que não.

Infelizmente a Lib da Bematech e o projeto Android do React Native tem algumas incompatibilidades, como é possível ver na imagem abaixo, vamos precisar resolver isso.

Como é possível ver no log da aba Sync, ocorreu um erro durante o merge das configurações do Arquivo AndroidManifest.xml.

Abra esse esse aquivo, vá até a aba Merged Manifest, é possível ver os três erros:

O primeiro conflito é porque o projeto do React desabilita backup do App, e o modulo da Bematech não, para corrigir esse basta clicar no link de sugestão na própria mensagem de texto, com isso o Android Studio vai editar o AndroidManifest.xml do projeto React para sobrescrever algumas configurações o modulo da Bematech.

O segundo erro é que o React possui um “Application” customizado e a lib da Bematech também, vamos fazer o mesmo procedimento do primeiro erro e clicar na sugestão do Android Studio, isso vai sobrescrever a configuração para a lib da Bematech. Essa escolha vai nos gerar uma pouco mais de trabalho depois.

O terceiro é o mais simples, a versão minima atual do sdk android para o react é a 16, e para a lib da Bematech é 17, então vamos subir a versão minima do sdk. Abra o aquivo android/gradle.config e edite a variável minSdkVersion.

Após tudo ajustado vamos executar um Sync, no menu File clique em “Sync Project with Gradle Files”.

Finalmente temos o projeto sem erros:

Agora vamos implementar a bridge entre o código Android Nativo e o React.
Se você nunca fez isso antes, recomendo primeiro dar uma lida na documentação do ReactNative aqui: https://facebook.github.io/react-native/docs/native-modules-android.

Vamos criar nossa classe do nosso modulo, arquivo completo aqui, versão reduzida apenas com o mais importante:

package com.rnsamplewithbemaandroidlib;import androidx.annotation.NonNull;import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import br.com.bematech.android.usb.mp4200th.Printer;public class BemaAndroidLibModule extends ReactContextBaseJavaModule {public BemaAndroidLibModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@NonNull
@Override
public String getName() {
return "BemaAndroidLib";
}
private Printer printer;private synchronized Printer getPrinter() {
if (printer == null) {
printer = new Printer();
}
return printer;
}
@ReactMethod
public void findPrinter(Promise promise) {
try {
getPrinter().FindPrinter();
promise.resolve(null);
} catch (Exception ex) {
promise.reject(ex);
}
}
@ReactMethod
public void imprimirTexto(String texto, Promise promise) {
try {
int result = getPrinter().ImprimirTexto(texto);
promise.resolve(result);
} catch (Exception ex) {
promise.reject(ex);
}
}@ReactMethod
public void imprimirQRCode(String url, Promise promise) {
try {
int result = getPrinter().ImprimirQRCode(url);
promise.resolve(result);
} catch (Exception ex) {
promise.reject(ex);
}
}
@ReactMethod
public void getPrinterStatusCode(Promise promise) {
try {
int result = getPrinter().GetPrinterStatusCode();
promise.resolve(result);
} catch (Exception e) {
promise.reject(e);
}
}
@ReactMethod
public void getPrinterStatus(Promise promise) {
try {
String printerStatus = getPrinter().GetPrinterStatus();
promise.resolve(printerStatus);
} catch (Exception ex) {
promise.reject(ex);
}
}
}

Perceba a anotação @ReactMethod, que todos o métodos com essa anotação recebem uma promise como ultimo parâmetro e não retornam nada, o ReactNative vai fazer o meio de campo, e do lado do JavaScript esses métodos devolvem uma Promise, podemos trabalhar com Promises e usar async/await.

Nosso modulo precisa ser registrado em um ReactPackage, então vamos criar um:

package com.rnsamplewithbemaandroidlib;

import androidx.annotation.NonNull;

import com.facebook.react.ReactPackage;
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;

public class BemaAndroidLibPackage implements ReactPackage {
@NonNull
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
List<NativeModule> nativeModules = new ArrayList<>();
nativeModules.add(new BemaAndroidLibModule(reactContext));
return nativeModules;
}

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

O nosso pacote precisar estar registrado no MainApplication.java do react,
edite o método getPackages da classe anonima ReactNativeHost para adicionar o nosso pacote, algo parecido com isso:

protected List<ReactPackage> getPackages() {
//...
packages.add(new BemaAndroidLibPackage());
return packages;
}

Agora uma das partes mais importantes, carregar o ApplicationContextProvider da lib da Bematech, lembram do segundo item que deu conflito.

Uma aplicação Android só pode carregar uma instancia do Application, e precisamos carregar a do React, então não é possível carregar a da Bematech.

Essa é a implementação da Lib da Bematech descompilada:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package br.com.bematech.android.usb.mp4200th;

import android.app.Application;
import android.content.Context;

public class ApplicationContextProvider extends Application {
private static Context sContext;

public ApplicationContextProvider() {
}

public void onCreate() {
super.onCreate();
sContext = this.getApplicationContext();
}

public static Context getContext() {
return sContext;
}
}

Podemos ver que a unica coisa que ela faz é guardar uma referencia do Context e expor ele via um método estático, essa abordagem tornou mais simples o uso da classe Printer, mas nos complicou agora. Sem inicializar essa variável sContext ocorre um belo de um NullPointerException durante o construtor da classe Printer.

A unica solução para contornar esse problema que encontrei até o momento foi adicionar o seguinte trecho no método onCreate da classe MainApplication do React:

new ApplicationContextProvider() {
{
attachBaseContext(MainApplication.this.getBaseContext());
}
}.onCreate();

Este trecho não é muito trivial, vou tentar explicar. Nele nós instanciamos a classe ApplicationContextProvider de forma anonima e chamamos seu onCreate para inicializar aquele variável estática. Uma instancia setando coisas estáticas, meio estranho mas beleza. Demos muita sorte de existir esse método attachBaseContext com visibilidade protected no SDK do Android, com ele podemos inicializar o context que é usado no onCreate do ApplicationContextProvider da Lib da Bematech. OSOLID mandou lembranças, mas beleza.

Agora por ultimo e não menos importante, consumir nosso modulo no React Native.

Vamos criar um aquivo BemaAndroidLib.js para ser um wrapper:

import {NativeModules} from 'react-native'
export default NativeModules.BemaAndroidLib

Agora um exemplo de App.js consumindo nosso modulo:

import React, {useState} from 'react';
import {
SafeAreaView,
StyleSheet,
ScrollView,
View,
Text,
StatusBar,
Button,
ToastAndroid,
} from 'react-native';
import {
Colors,
} from 'react-native/Libraries/NewAppScreen'
import BemaAndroidLib from './BemaAndroidLib'const App: () => React$Node = () => {const [printerState, setPrinertState] = useState('')const site = 'https://codengage.com'

const showSuccess = (msg) => ToastAndroid.show(msg, ToastAndroid.SHORT)
const showError = (e) => {
console.error(e)
ToastAndroid.show('error:' + (e.message || e), ToastAndroid.LONG)
};
const syncPrint = async () => {
try {
await BemaAndroidLib.findPrinter();
const state = await BemaAndroidLib.getPrinterStatus()
setPrinertState(state)
showSuccess(`status: ${status}`)
} catch {
showError(e)
}
}
const printText = async () => {
try {
await BemaAndroidLib.imprimirTexto(site)
await BemaAndroidLib.cortarTotal()
showSuccess("texto impresso!")
} catch (e) {
showError(e)
}
}
const printQRCode = async () => {
try {
await BemaAndroidLib.imprimirQRCode(site)
await BemaAndroidLib.cortarTotal()
showSuccess("qrcode impresso!")
} catch {
showError(e)
}
}
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={styles.scrollView}>
<View style={styles.body}>
<Button title='Sync Printer' onPress={syncPrint}></Button>
<Text>
Status: {printerState}
</Text>
<Button title='print text' onPress={printText}></Button>
<Button title='print qrcode' onPress={printQRCode}></Button>
</View>
</ScrollView>
</SafeAreaView>
</>
);
};
const styles = StyleSheet.create({
scrollView: {
backgroundColor: Colors.lighter,
},
engine: {
position: 'absolute',
right: 0,
},
body: {
backgroundColor: Colors.white,
},
});
export default App;

Agora a parte mais legal, testar em um aparelho de verdade.

Para gerar o apk no Linux ou MacOS dentro da pasta android, execute o comando:

./gradlew assembleRelease

no caso do cmd no Windows fica:

gradlew.bat assembleRelease

ou para PowerShell no Windows:

./gradlew.bat assembleRelease

Alguns screenshots do app rodando em um Moto G:

Todo código fonte pode ser encontrado em meu github https://github.com/adriano-moreira/RNSampleWithBemaAndroidLib

--

--