Offline-First POS & Ski Rental Management: Hardware Integration in Flutter

Ski resorts operate in harsh network conditions. Remote alpine locations often experience intermittent satellite drops, power fluctuations, and sudden surges of hundreds of tourists queuing at the rental counter between 8:00 AM and 9:30 AM.
For RENTSKIS—a specialized ski and snowboard equipment rental management platform designed for resort admins, cashiers, and ski technicians—system failure or cloud latency during peak hours is not an option.
As Tech Lead on the project, I was responsible for the entire technical architecture: cross-platform Flutter client, thermal receipt printers, USB/Bluetooth barcode scanners, and an offline-first synchronization engine.
The Workflow & Hardware Ecosystem
+----------------------------------------------+
| Customer Counter |
+----------------------------------------------+
|
v
+------------------+ +------------------+ +------------------+
| Admin Station | | Cashier Counter | | Repair Tech Bay |
| (Inventory, Logs)| | (Fast Checkout) | | (Tuning & Skis) |
+------------------+ +------------------+ +------------------+
\ | /
\ | /
+-------------------------+-------------------------+
|
Local SQLite + Background Sync Queue
|
v
+-------------------------------------+
| Hardware Drivers (ESC/POS & HID) |
| - Star/Epson Thermal Printers |
| - Zebra 2D Barcode Scanners |
| - RFID Boot Binding Scanners |
+-------------------------------------+
1. ESC/POS Thermal Printing in Pure Dart
Rather than relying on clunky third-party print spoolers, we wrote direct ESC/POS byte-command generators to talk to network and USB thermal printers over raw TCP sockets (port 9100):
import 'dart:io';
import 'dart:typed_data';
class EscPosTicketPrinter {
final String printerHost;
final int printerPort;
EscPosTicketPrinter({required this.printerHost, this.printerPort = 9100});
Future<void> printRentalTicket({
required String rentalId,
required String customerName,
required List<String> equipmentBarcodes,
required double totalDeposit,
}) async {
final socket = await Socket.connect(printerHost, printerPort, timeout: Duration(seconds: 3));
final builder = BytesBuilder();
// Initialize Printer
builder.add([0x1B, 0x40]);
// Center Align Header
builder.add([0x1B, 0x61, 0x01]);
builder.add([0x1B, 0x21, 0x30]); // Double height + double width
builder.add(Uint8List.fromList('RENTSKIS ALPS\n'.codeUnits));
// Normal Text
builder.add([0x1B, 0x21, 0x00]);
builder.add(Uint8List.fromList('Rental ID: $rentalId\nCustomer: $customerName\n'.codeUnits));
builder.add([0x1B, 0x61, 0x00]); // Left align
// Print Barcode (CODE128)
builder.add([0x1D, 0x6B, 0x49, rentalId.length]);
builder.add(Uint8List.fromList(rentalId.codeUnits));
// Feed and Cut
builder.add([0x1B, 0x64, 0x03]); // Feed 3 lines
builder.add([0x1D, 0x56, 0x41, 0x00]); // Partial Cut
socket.add(builder.takeBytes());
await socket.flush();
await socket.close();
}
}
2. Optimistic Offline-First Architecture
Every customer registration, gear scan, and payment intent is written immediately to a local encrypted SQLite database:
- Immediate UI Feedback: Cashiers never wait for network roundtrips when scanning skis. The transaction commits locally in under 3 milliseconds.
- Deterministic Conflict Resolution: Equipment status utilizes Last-Write-Wins (LWW) with logical vector clocks to resolve discrepancies if multiple repair bays service the same pair of skis concurrently.
- Background Sync Worker: A persistent background isolates queue drains pending records as soon as cloud connectivity is restored.
Summary
Designing software that interacts with real-world mechanical and physical gear demands absolute robustness against network drops and device disconnection. By engineering from first principles in Dart and Flutter, RENTSKIS handled entire winter seasons without a single second of counter downtime.