Skip to content

React Native & Expo Guide

Mobile POS Integration Guide: React Native & Expo

Section titled “Mobile POS Integration Guide: React Native & Expo”

[!TIP] Looking for Flutter or Native Mobile?

papermint-mobile is a high-performance C-compatible foreign function interface (FFI) and receipt compilation engine. It enables mobile applications (React Native, Expo SDK 56+, Flutter, iOS Swift, and Android Kotlin) to generate pixel-perfect thermal receipts in microseconds, returning zero-copy raw byte buffers (Uint8Array / byte[]) ready to stream to portable thermal printers.


1. Why This Architecture Wins for Mobile POS

Section titled “1. Why This Architecture Wins for Mobile POS”

Portable thermal printers in food trucks, pop-up shops, and mobile retail connect over different hardware transports:

  • Bluetooth Classic (SPP / RFCOMM): Used by 80%+ of budget 58mm/80mm printers on Android.
  • Bluetooth Low Energy (BLE): Used on iPhones, iPads, and modern portable printers (e.g. Star SM-L200).
  • Wi-Fi / Ethernet: Used by kitchen and bar countertop printers (RAW Port 9100).
  • USB-C OTG: Used by fixed Android tablet stands.

Attempting to run a rigid Bluetooth stack inside Rust on mobile leads to severe permission bugs, battery drain, and iOS/Android lifecycle crashes.

Instead, papermint follows the Turbo Engine Pattern:

  1. Rust (papermint-mobile) does the heavy math: CJK/Unicode column layout, photo dithering, dynamic word wrapping, and ESC/POS or StarPRNT byte generation in ~10 microseconds.
  2. Mobile OS (React Native / Expo) handles the hardware: OS Bluetooth permissions, device-picker modals, and direct socket writing.

2. Native Binaries (Pre-compiled vs. Source)

Section titled “2. Native Binaries (Pre-compiled vs. Source)”

[!TIP] Using expo-papermint? If you are using the official expo-papermint npm package, pre-compiled Android .so libraries and iOS PapermintMobile.xcframework are already pre-bundled inside the package. You do not need to install Rust or compile anything!

If you are building a custom bare module or using other mobile frameworks:

  • Download Pre-built: Grab papermint-mobile-android-v*.zip or papermint-mobile-ios-v*.zip from GitHub Releases.
  • Build from Source: Use cargo-ndk (Android) or cargo build (iOS) as shown below:
Terminal window
# Android (arm64-v8a, armeabi-v7a, x86_64) via cargo-ndk
cargo ndk -t arm64-v8a -t armeabi-v7a -t x86_64 build --package papermint-mobile --release
# iOS (aarch64-apple-ios, aarch64-apple-ios-sim)
cargo build --package papermint-mobile --target aarch64-apple-ios --release
cargo build --package papermint-mobile --target aarch64-apple-ios-sim --release

The compiled output contains:

  • Header: crates/papermint-mobile/include/papermint.h
  • Static Library: libpapermint_mobile.a
  • Dynamic Library: libpapermint_mobile.so (Android) / libpapermint_mobile.dylib (iOS/macOS)

You can integrate Papermint into your mobile app using any of three approaches:

Section titled “Approach 1: Official Universal Module (expo-papermint) — Recommended”

The quickest and cleanest approach. No need to write or maintain native Kotlin or Swift files:

Terminal window
# In Expo apps:
npx expo install expo-papermint
# In Bare / Pure React Native apps:
npm install expo-papermint expo-modules-core
import { Receipt } from 'expo-papermint';
const wireBytes: Uint8Array = Receipt.create('80mm')
.title('BLUE CAFE & ROASTERY')
.subtitle('Order #4092')
.item('Iced Oat Latte', '$5.50', 1)
.item('Avocado Toast', '$18.00', 2, '$9.00')
.total('TOTAL', '$23.50')
.qr('https://pay.bluecafe.com/4092')
.cut()
.compile('escpos'); // or 'star'

Expo SDK 56 introduced Inline Modules, allowing you to write Swift and Kotlin directly in your project without maintaining separate npm packages.

{
"expo": {
"name": "MyMobilePOS",
"slug": "my-mobile-pos",
"version": "1.0.0",
"experiments": {
"inlineModules": {
"watchedDirectories": ["modules"]
}
}
}
}

Create modules/PapermintModule.swift:

import ExpoModulesCore
public class PapermintModule: Module {
public func definition() -> ModuleDefinition {
Name("Papermint")
Function("compileTicket") { (jsonString: String, dialect: Int) -> [UInt8] in
var outLen: Int = 0
guard let ptr = papermint_compile_json(jsonString, UInt8(dialect), &outLen) else {
return []
}
defer { papermint_bytes_free(ptr, outLen) }
let buffer = UnsafeBufferPointer(start: ptr, count: outLen)
return Array(buffer)
}
}
}

Create modules/PapermintModule.kt:

package com.mymobilepos.modules
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
class PapermintModule : Module() {
companion object {
init {
System.loadLibrary("papermint_mobile")
}
}
private external fun papermintCompileJson(jsonStr: String, dialect: Int): ByteArray
override fun definition() = ModuleDefinition {
Name("Papermint")
Function("compileTicket") { jsonString: String, dialect: Int ->
papermintCompileJson(jsonString, dialect)
}
}
}

In your React Native / Expo application:

import { requireNativeModule } from 'expo-modules-core';
const Papermint = requireNativeModule('Papermint');
// 1. Define your receipt ticket
const ticket = {
paper_width: "80mm",
title: "BLUE CAFE & ROASTERY",
subtitle: "Order #4092",
address: "123 Main St, Austin TX",
metadata: [
{ label: "Date", value: new Date().toLocaleDateString() },
{ label: "Cashier", value: "Alex" }
],
items: [
{ qty: "1", description: "Iced Oat Latte", price: "$5.50", total: "$5.50" },
{ qty: "2", description: "Avocado Toast", price: "$9.00", total: "$18.00" }
],
divider_style: "=",
totals: [
{ label: "Subtotal", value: "$23.50" },
{ label: "Tax (8.25%)", value: "$1.94" },
{ label: "TOTAL", value: "$25.44" }
],
qr: "https://pay.bluecafe.com/4092",
barcode: "40922544",
footer: "Thank you for supporting local business!",
cut_mode: "full",
open_drawer: true,
beep: 1
};
// 2. Compile into wire bytes in ~10 microseconds!
// dialect: 0 = ESC/POS (Epson/Generic), 1 = StarPRNT (Star Micronics)
const rawBytes: number[] = Papermint.compileTicket(JSON.stringify(ticket), 0);
const wireBuffer = new Uint8Array(rawBytes);

Using react-native-bluetooth-classic:

import RNBluetoothClassic from 'react-native-bluetooth-classic';
async function printViaBluetoothClassic(address: string, data: Uint8Array) {
const device = await RNBluetoothClassic.connectToDevice(address);
// Base64 encode wire buffer for Bluetooth socket
const base64Data = Buffer.from(data).toString('base64');
await device.write(base64Data, 'base64');
console.log("✅ Printed successfully over Bluetooth Classic!");
}

B. Bluetooth Low Energy (BLE / iOS & Android)

Section titled “B. Bluetooth Low Energy (BLE / iOS & Android)”

Using react-native-ble-plx:

import { BleManager } from 'react-native-ble-plx';
const ble = new BleManager();
async function printViaBle(
deviceId: string,
serviceUuid: string,
characteristicUuid: string,
data: Uint8Array
) {
const device = await ble.connectToDevice(deviceId);
await device.discoverAllServicesAndCharacteristics();
// BLE MTU Chunking: Send in 256 or 512 byte packets
const chunkSize = 256;
for (let i = 0; i < data.length; i += chunkSize) {
const chunk = data.subarray(i, i + chunkSize);
const base64Chunk = Buffer.from(chunk).toString('base64');
await device.writeCharacteristicWithResponseForService(
serviceUuid,
characteristicUuid,
base64Chunk
);
}
console.log("✅ Printed successfully over BLE!");
}

Using react-native-tcp-socket:

import TcpSocket from 'react-native-tcp-socket';
function printViaNetwork(ip: string, port = 9100, data: Uint8Array) {
const client = TcpSocket.createConnection({ host: ip, port }, () => {
client.write(Buffer.from(data));
client.end();
});
}

If building custom C++ JSI TurboModules or using Flutter dart:ffi:

Function Description
papermint_receipt_create(paper_width) Creates handle (0 = 80mm, 1 = 58mm).
papermint_receipt_free(handle) Deallocates receipt handle.
papermint_receipt_init(handle) Appends hardware reset sequence (ESC @).
papermint_receipt_text_ln(handle, text) Appends text followed by newline.
papermint_receipt_align(handle, align) Sets alignment (0=Left, 1=Center, 2=Right).
papermint_receipt_bold(handle, enable) Enables/disables bold emphasis.
papermint_receipt_underline(handle, mode) Sets underline (0=Off, 1=Single, 2=Double).
papermint_receipt_divider(handle, style) Appends full-width divider line.
papermint_receipt_table_row(handle, cols, n) Appends synchronized CJK/Unicode multi-column row.
papermint_receipt_qr(handle, content) Appends 2D QR Code.
papermint_receipt_barcode(handle, content) Appends 1D Code 128 Barcode.
papermint_receipt_cut(handle, partial) Appends paper cut command.
papermint_receipt_beep(handle, count, dur) Triggers buzzer beep.
papermint_receipt_open_drawer(handle) Triggers cash drawer kickout pulse.
papermint_receipt_encode(handle, dialect, out_len) Compiles commands into raw byte buffer.
papermint_compile_json(json, dialect, out_len) Compiles JSON ticket into raw byte buffer.
papermint_bytes_free(ptr, len) Frees buffer returned by encode/compile functions.