DEV Community

eldel$hell
eldel$hell

Posted on

I made: POS with React Native

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.

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.

Main POS Screen

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


Enter fullscreen mode Exit fullscreen mode

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) { ... }
}


Enter fullscreen mode Exit fullscreen mode

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();
    }
}


Enter fullscreen mode Exit fullscreen mode

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>
        );
    }
}


Enter fullscreen mode Exit fullscreen mode

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(' ');
    }


Enter fullscreen mode Exit fullscreen mode

This is how a ticket looks like after it was printed

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

  1. 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.

Latest comments (28)

Collapse
 
cutebubbletea23 profile image
littleblondie

Hi is there posabillity you provide git link? This is amazing

Collapse
 
jccb profile image
jccb

Is there a part 2 & 3? Lol

Collapse
 
azim4gvm profile image
azim4gvm

can you please provide full source code.

Collapse
 
sathyaprakash94 profile image
WebTechie94

Good tutorial, I'm a beginner loves this and as starting for me.

Collapse
 
miklosherald09 profile image
Miklos Herald Narra

How about the cash drawer? should it open when the bluetooth printer finished printing or through the printers API? but it seems like it doesn't have an API for that

Collapse
 
fabiobetancourt profile image
Fabio Betancourt

Hi, What is ConfigManager?

Collapse
 
kiahtolliver profile image
Kiah Tolliver

Can you share your source code?

Collapse
 
ukiqbal profile image
ukiqbal

where i can see demo?

Collapse
 
anddegs profile image
christopher anabo

can you share the code?

Collapse
 
manhvutien profile image
Manh Vu

Coud you share this module, please? Many thanks