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

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:

  1. Immediate UI Feedback: Cashiers never wait for network roundtrips when scanning skis. The transaction commits locally in under 3 milliseconds.
  2. 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.
  3. 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.

DX

Written by DX

Systems Engineer • Focused on high-performance distributed systems, low-level OS internals, and financial engineering.

← Back to all archives