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.

Top comments (28)

Collapse
 
pavanuec profile image
pavanuec

Can you provide git link?

Collapse
 
stefandorresteijn profile image
Stefan Dorresteijn

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?

Collapse
 
eldelshell profile image
eldel$hell

Hi @stefan

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?

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.

Collapse
 
stefandorresteijn profile image
Stefan Dorresteijn

That's fair, can't wait for part 2 :)

Thread Thread
 
jawhersghaierr profile image
jawhersghaierr

ddd

Collapse
 
ruff profile image
Andy Ruff

Curious—which BT printer did you purchase?

Collapse
 
theskay profile image
TheSkaÿ

+1

Collapse
 
cgul profile image
James

+1

Collapse
 
navneetccna profile image
Navneet Garg • Edited

Hi very nice design and concept. can you share source code on github?

Collapse
 
azim4gvm profile image
azim4gvm

did you get link ?

Collapse
 
theskay profile image
TheSkaÿ • Edited

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 !

Collapse
 
trubi profile image
trubi • Edited

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

Collapse
 
maomagico profile image
Mao • Edited

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

Collapse
 
manhvutien profile image
Manh Vu

Coud you share this module, please? Many thanks

Collapse
 
novyan_edwin profile image
Novyan Edwin

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

Collapse
 
fabiobetancourt profile image
Fabio Betancourt

Hi, What is ConfigManager?