Back to Projects

iPokerPal

ipokerpal

An offline-first React Native poker bankroll and session tracker for live games, settlements, player stats, quick records, QR invites, and optional Firebase sync.

Role: Solo Developer / Product Engineer

Highlights

React NativeExpo SDK 54TypeScriptReact NavigationZustandSQLiteAsyncStorageFirebase AuthCloud FirestoreGoogle Sign-In
  • Designed and built a cross-platform Expo app for managing live poker sessions, including blinds, buy-ins, active players, chip counts, final settlements, and game history.
  • Implemented a local-first architecture using Zustand persistence, AsyncStorage, and expo-sqlite so core session tracking continues to work without authentication or network access.
  • Built deterministic settlement logic for chip-to-cash conversion, total buy-ins, final chip counts, cash profit, ROI, win/loss outcomes, and per-player summaries.
  • Modelled completed sessions as local action snapshots in an SQLite-backed actions table, enabling history reconstruction, offline review, retry flows, and future analytics.
  • Integrated Firebase Authentication with Google, Apple, and anonymous guest access while preserving local user restoration and guest-only offline workflows.

Problem

Live poker sessions are often tracked with notes, spreadsheets, or group chats, which makes buy-ins, chip conversion, player profit, and final settlement easy to miscalculate.

The app needed to support real playing environments where users may be offline, unauthenticated, switching devices, using guest mode, or dealing with unreliable cloud writes.

Hosts also needed a way to record sessions quickly, review historical results, and keep player-level performance stats without making every workflow depend on a backend.

Approach

I designed the app around a local-first model: local state and local snapshots are treated as the primary workflow, while Firebase sync is an optional enhancement for authenticated users.

I separated session state, player state, persistence, auth, cloud writers, and formatting utilities so settlement rules stay predictable and reusable across normal sessions, quick records, history, and cloud sync.

I normalized financial data at write boundaries, keeping cash values to two decimals, ROI to fixed precision, and chip counts as integers to avoid inconsistent summaries across screens.

I used role-aware permission checks so host-only behaviours, cloud updates, and settlement currency tools could be guarded without blocking local play.

Result

Delivered a production-oriented mobile app that can record live poker sessions offline, calculate player settlements, keep local history, and optionally sync authenticated sessions to Firebase.

Supported multiple usage modes including full live-game tracking, quick cash-session recording, guest-only local use, and authenticated cloud-backed player statistics.

Created a foundation for future analytics by storing normalized local action snapshots, per-player historical records, user counters, and graph-ready Firestore data.

Implementation

Session and settlement system

  • Built session setup flows for small blind, big blind, base cash amount, base chip amount, players, buy-ins, leave/return states, and final chip entry
  • Implemented player-level settlement calculations for total buy-in chips, total buy-in cash, final chips, cash amount, cash difference, chip difference, ROI, and finalized state
  • Created settlement summary UI that aggregates total buy-ins, ending chips, total cash difference, per-player profit/loss, and optional RMB conversion for host users
  • Added Quick Record mode for fast cash-session entry with blind validation, normalized number input, local snapshot creation, and optional cloud stat updates
  • Generated user-friendly game history items from raw local action payloads so completed sessions can be reconstructed into totals, rankings, and player summaries

Local-first data architecture

  • Implemented a SQLite schema for games, players, game_players, and actions, including an indexed actions table for session event snapshots
  • Built an execSql wrapper that supports native SQLite transaction APIs, alternate exec APIs, and an in-memory fallback when native database support is unavailable
  • Persisted the in-memory fallback store through AsyncStorage so degraded environments can still keep local players, games, and actions across reloads
  • Used AsyncStorage-backed services for local player/game lists, settings, current user restoration, unsaved game retry cache, and Zustand persisted stores
  • Stored finalized games as local action snapshots before or alongside cloud writes, reducing data-loss risk when Firebase writes fail

State management and performance

  • Structured core session data in a persisted Zustand game store with versioned migration, partial persistence, Firestore hydration, token state, and finalization state
  • Structured player state in a separate persisted Zustand store for adding players, buy-ins, final chip counts, active/inactive status, remote merges, and all-finalized checks
  • Kept financial mutations inside store actions such as addBuyIn and setEndingChips so UI components consume normalized state instead of duplicating settlement formulas
  • Used partial persistence and rehydration hooks to avoid serializing unnecessary runtime functions while keeping session recovery reliable after app restarts
  • Separated reusable helpers for number normalization, player snapshot formatting, profit calculation, history conversion, and summary export

Cloud integration and sync

  • Integrated Firebase Auth with Google Sign-In, Apple Sign-In with nonce handling, and local anonymous guest sign-in for offline-only usage
  • Created Firestore writer utilities for game documents, player subcollections, user profiles, user counters, email indexes, per-user game history, and graph data preparation
  • Built a BatchBuilder utility that automatically splits Firestore write batches before the 500-operation limit, using a safer 450-operation threshold
  • Preserved created timestamps by separating create payloads from update payloads and writing updated timestamps only during updates
  • Cached failed Firebase uploads in AsyncStorage so failed cloud sync does not destroy the local session result
  • Implemented host record registration and permission-aware flows around host game history and privileged write paths

Settings, localization, and reliability

  • Implemented a SettingsProvider for language, currency, exchange-rate state, formatting helpers, local settings migration, and global fallback access
  • Added lightweight i18n support through simpleT with runtime locale switching and persisted language selection
  • Implemented currency formatting with Intl.NumberFormat fallback and AUD/CNY exchange-rate support with a 24-hour cache policy
  • Handled edge cases such as zero base chip amount, anonymous users, missing Google configuration, cancelled sign-in flows, Firestore permission failures, and unavailable SQLite APIs
  • Added local database clear, local game deletion, account sign-out cleanup, raw action history loading, and fallback parsing paths to make recovery and debugging easier

Lessons

  • For financial workflows, the most important engineering work is making every number deterministic, normalized, and explainable at the point where it is stored.
  • Offline-first mobile design is not just a storage choice; it changes the product shape by making guest mode, failed sync, and unreliable network conditions first-class workflows.
  • Keeping local session state, local persistence, cloud writers, auth, and formatting logic separated makes the app easier to debug and reduces the risk of settlement regressions.
  • Cloud sync should be treated as a recoverable side effect rather than the core source of truth when the user is actively recording a live session.