This is part I of a series of posts I'll write explaining the progress of the development of a custom made POS app for Android using React Native.
Context
My wife just established a Neko Cafe where people gather to enjoy some coffee while cats are around playing and that kind of stuff. There's also a store where clients can buy cat related gear like pendants, t-shirts, etc.
After studying the different options for a POS on the market, I decided to do one of my own. A side project that took close to 200 hours of work during weekends and days off and is still on development while we add new features.
So, the main requirements were:
- Run on an Android tablet (Galaxy Tab A10)
- Print to a Bluetooth printer.
- Track time clients are with the cats (one hour, half hour, etc).
- Track number of clients on each ticket.
- Track money on the drawer with an open/close day flow.
- Take reservations.
- Allow to add one time products to a ticket.
- Manage long term products (coffee, tea, mugs, etc).
Pre-study
Of course, who would be crazy enough to write their own POS when there are many out there. Well, first, we tried to buy a whole solution from different companies but those were over budget and we were worried several of our requirements wouldn't be met. So we went to Amazon[1], and bought the Bluetooth printer and cash drawer for close to 100€. Since we already had several tablets at home we saved 200€ right there. Those Windows based POS with touchscreen are quite expensive.
I still didn't want to mess up doing a POS so off to the Google Play Store. There are many great apps which do the job, for example Loyverse. There were many others that, lets just say were so ugly and complicated that would take me more time than doing the app by myself. At the end, none of them matched our requirements, so off to the drawing board.
React Native
This is not my first Android or React Native app, so I knew what I was getting into and my major concern was with the Bluetooth printer. Luckily the one we got came with the source code for iOS, Android, Windows and Linux.
So, with most of the Bluetooth code there, it was a simple matter of cleaning that thing up so my eyes didn't bleed and connecting the Android side with the React Native one.
My next concern was the SQLite database, which I haven't used with React Native but only with Android, and after some investigation I found react-native-sqlite-storage which did the job, didn't require hundreds of dependencies and didn't get on my way. More on that on part II.
Another concern was with the handling of currencies and dates, but come on, it's 2017 and we have Moment and Big which work flawlessly.
This are all the dependencies used:
- react-native 0.47.2
- Big.js for decimal number handling.
- Moment for date handling.
- react-navigation (I love this one)
- react-native-vector-icons and Material Icons (don't start a project without this one)
- react-native-sqlite-storage
- react-native-fs
Why Not Android Native
This came to my mind, but after you've done some React Native apps you'll understand that the UI and business logic is so much easier to implement on JavaScript than on Java. Also, the development flow is much faster, since pressing rr
on the emulator refreshes all your changes without having to wait for any build process.
Specially the UI is so much easier to implement thanks to flex. Yes, there are some limitations, like the ones with TextInput
but those weren't a big concern for this project.
Bluetooth Printer
This how my Android source folder ended up like:
.
└── com
└── polinekopos
├── bt
│  ├── BTModule.java
│  ├── BTModulePackage.java
│  └── sdk
│  ├── BluetoothService.java
│  ├── Command.java
│  ├── PrinterCommand.java
│  └── PrintPicture.java
├── MainActivity.java
└── MainApplication.java
Under the sdk
package is everything the printer came with so it was only up for me to create the React Native interface with it. There's also a btsdk.jar
file with other dependencies on the libs
folder.
BluetoothService
is where all the magic happens and after going through the code and doing some refactoring (like adding a listening thread which would notify of disconnection events) I got a good grasp of how the communication with the Bluetooth printer magic worked. TL:DR it involves a lot of bytes and buffers.
BTModule
manages everything between this service and the React Native app:
public class BTModule extends ReactContextBaseJavaModule {
public BTModule(ReactApplicationContext reactContext) {
...
}
@ReactMethod
public void connect(String address, Promise promise) { ... }
@ReactMethod
public void disconnect(Promise promise) { ... }
@ReactMethod
public void print(String message, Promise promise) { ... }
@ReactMethod
public void getState(Promise promise) { ... }
private void sendEvent(String eventName) { ... }
}
These methods are quite straight forward and are available for React Native to use. Everything else is as explained on this section of the React Native documentation.
There's also some modifications on MainActivity
to suspend and resume the connection with the printer:
@Override
public synchronized void onResume() {
super.onResume();
if (mBluetoothAdapter == null) {
return;
}
if (BluetoothService.get().getState() == BluetoothService.STATE_NONE) {
BluetoothService.get().start();
}
}
On the React Native side it was very simple. A button to connect/disconnect on the top bar which also shows the status by listening to Bluetooth events and sending strings to the printer.
export class Banner extends React.Component {
state = {
bluetooth: 'bt-unknown'
};
componentWillMount() {
events.forEach((e) => DeviceEventEmitter.addListener(e, () => this.setState({bluetooth: e})));
}
componentWillUnmount() {
events.forEach((e) => DeviceEventEmitter.removeAllListeners());
}
getBluetoothIcon() {
let icon = null;
switch (this.state.bluetooth) {
case 'bt-unknown': icon = IconFactory.Icons.BTUnknown; break;
case 'bt-connecting': icon = IconFactory.Icons.BTConnecting; break;
case 'bt-connected': icon = IconFactory.Icons.BTConnected; break;
case 'bt-disconnect': icon = IconFactory.Icons.BTDisconnected; break;
}
return IconFactory.build(icon, styles.drawerIcon);
}
async onBluetooth() {
const bluetooth = await BTModule.getState();
this.setState({bluetooth: bluetooth});
if(bluetooth !== 'bt-connected'){
const id = ConfigManager.of().getBluetoothID();
try {
const deviceName = await BTModule.connect(id);
AlertFactory.info(Messages.get('message.bluetooth.connected'));
} catch (e) {
AlertFactory.error(Messages.get('error.bluetooth.connection'));
}
}
}
render() {
return (
// ...
<View style={styles.bannerRight}>
<TouchableHighlight
underlayColor='transparent'
style={styles.drawerButton}
onPress={this.onBluetooth.bind(this)}>
{ this.getBluetoothIcon() }
</TouchableHighlight>
</View>
);
}
}
Here DeviceEventEmitter
manages the events sent from the native side of the app from the sendEvent
method defined on BTModule
and renders the view with the state it received. This way the Bluetooth icon is always up to date.
The onBluetooth
function is binded to the button and checks if we're disconnected and tries to connect again. Whatever happens, an alert is shown to the user.
The IconFactory
object takes care of managing and rendering the icons provided by react-native-vector-icons.
Printing is rather easy, although it requires to use a lot of padStart
and padEnd
to align the content as desired. Anyway, after an array with all the lines is created, a call to the write
function is all the is required:
async print() {
// Some margin
await BTModule.print(' ');
await BTModule.print(' ');
// Now the ticket
this.lines.forEach((line) => BTModule.print(line));
// Cut margin
await BTModule.print(' ');
await BTModule.print(' ');
}
This is it on part I, hope you enjoyed and look forward to part II where I'll discuss the SQLite side of things and some nice to know things about react-navigation.
BTW, did you catch that bug on the last picture? Looks like more work!
Footnotes
- When buying this sort of stuff on Amazon, be careful with the "Buy them together" section. Big mistake on my side. The printer we bought didn't have any port to connect the drawer (it's a small telephone like port) so we have to manually open it and the thermal paper were the wrong ones too.
Top comments (28)
Can you provide git link?
Damn, that's pretty impressive. I've recently started working with RN and while it's been much easier than working with Java/Swift, it's been far from smooth so far.
Did you build the entire app without a central backend? And if so, aren't you afraid at some point the tablet might get nicked and you lose all the data you had?
Hi @stefan
Yes, there's no backend. I mean, Firebase was kind of an overkill, that's why on Part II I'll try to explain the backup policy and the whole DB thing. Once I get more time and the feature is required, I might use a DB backend, either Firebase or a Node API.
That's fair, can't wait for part 2 :)
ddd
Curious—which BT printer did you purchase?
+1
+1
Hi very nice design and concept. can you share source code on github?
did you get link ?
Hi.
Super tutorial. I want to do the same project to practice with React Native. Can you please tell me which printer you bought on Amazon?
Having a view on the source code would be great to understand!
When do you have time to write the second part please?
Thanks !
We made whole POS in react native, much bigger than yours and in many many more hours indeed :) figurepos.com available on app store.
My plan is to write post about it soon as well. If you want to connect and share some experience/ask whatever you want, feel free to reach out to me
hi @eldel$hell I am facing a similar problem my main concernt is relationed in generate barcode because I need to print the codes and after it I need to read that bar code and print a invoice depending of other things, my question is, can I use the same print(hadware) for invoices and barcodes?
Thanks in advance
Coud you share this module, please? Many thanks
I am a student, im working on my final project, just like what you've created, if you dont mind I want to ask you to show me how to make the application can print with bluetooth printer. I really need your help
Hi, What is ConfigManager?