Skip to content
sharnengPublic

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

spliit2go

CI

An unofficial, offline-capable mobile client for Spliit, the open-source Splitwise alternative. Built with Flutter for Android and iOS from one codebase; Android is the primary target.

Why

Spliit has an official iOS app but no Android client, and its offline support is limited to receipt scanning. spliit2go fills both gaps: a real Android client, with offline view of your groups/expenses and the ability to add an expense while offline, queued for sync as soon as you're back online.

Scope

  • View, offline: groups, expenses, balances and each server's expense categories are cached locally and available with no connection.
  • Add, offline: new expenses can be created while offline; they're queued locally and synced to the server automatically once connectivity returns.
  • No offline edit: editing or deleting an expense requires connectivity. This keeps the sync model simple — appends only, no conflict resolution — which fits how expense-splitting apps are actually used.

Architecture

  • API: Spliit's backend exposes tRPC only (no REST layer), at {baseURL}/api/trpc with the superjson transformer, using tRPC's batch wire format for every call. Rather than importing Spliit's server-side types for end-to-end type inference, lib/api/spliit_client.dart talks to it as plain HTTP+JSON and maps responses into this app's own DTOs (lib/models/). This keeps the client decoupled from Spliit's internal schema — an upstream change breaks one mapping function here, not the whole app. The request/response shapes are ported from splitwise2spliit's Python client, which has already exercised this API end-to-end (group fetch, paginated expense list, expense create with even/uneven splits and settlements) importing real Splitwise data — not guessed from the server source alone.
  • Local storage: drift (SQLite) holds the last-synced snapshot of groups and expenses, plus a pending flag on locally-added expenses that haven't synced yet. See lib/db/. Balances aren't stored — they're computed on-device from the cached expenses each time (lib/services/balance_calculator.dart), which is what keeps them correct offline and inclusive of pending ones.
  • Sync: lib/sync/outbox.dart — pending expenses are replayed against the API when connectivity returns. No merge logic: reads overwrite the local cache on each successful fetch, writes are append-only. The one exception is this device's own edits and deletes, which happen online: a refresh that was already downloading when one landed skips its cache write, so it can't bring back a deleted expense or briefly revert an edit.

Full reasoning for these choices, and for other design calls (multiple groups, date handling and date sections, the expense form, the split UX, the group list, expense rows, expense details and delete), is in docs/decisions/.

Features

  • Groups: join by pasting a group's link (from spliit.app or any self-hosted instance), or by opening a spliit.app group link on Android, once spliit.app is added under the app's Open by default setting (Spliit would have to host a file for Android to do that automatically, #110); or create a new group from the Join screen, on spliit.app, a server you already use, or any other. Share a group's link from the ⋯ menu on the group screen, offline too. Each group keeps its own server URL and active user, so groups on different Spliit instances can coexist; the list shows each group's date span and is sortable (first/last expense date, creation date, last opened). The list is organized into Favorites, Active, and Archived sections (hidden when empty); swipe a row for Favorite/Archive/Remove actions, or long-press (or tap the monogram) for the same actions as a menu. A full swipe favorites or archives a group outright, native-style; Remove always needs an explicit tap and confirmation, and only removes the group from this device, never the server. Joining a group also caches its expenses, so it can be viewed offline straight away. Each group asks "Who are you?" once, unless the default participant name saved on this device (set by your first answer) matches exactly one participant; changes you make are credited to that person in Spliit's activity log.
  • Expenses: offline-first list (cache first, live fetch in the background, pull-to-refresh), grouped by date, with search by title (offline too, pending expenses included). Each row shows its category's icon in its grouping's color, what you lent or owe, and who paid. Tap an expense to see its details, offline too: who paid, each person's share, notes, and its receipts, which open full screen; a receipt opened once stays viewable offline, and a favorite group's receipts download ahead (Wi-Fi only by default), with a 📎 showing their progress. Editing and deleting are explicit actions from there, and an edit keeps the receipts attached on the web or iOS. Add and edit with all four split modes (evenly, shares, percentage, amount), per-participant live amount preview, categories with search, date, a different "paid in" currency, reimbursements, recurrence, notes, receipt photos (camera or library, opening full screen from the form too; shrunk, with location data removed, and uploaded to the group's server; a new expense's photos taken offline, or whose upload failed, are kept on the phone and sync with it), receipt scanning (read on the phone, offline too: it fills in the title, total, date and category of a new expense where you haven't, shows what it read under a field it leaves alone, and keeps the photo with the expense; English, French and other Latin-script receipts built in; Chinese and Japanese downloaded on Android from the language picker beside Scan receipt, and the phone's language's automatically, and built in on the iPhone), and a remembered per-group default split. Expenses added offline show a pending badge and sync automatically on reconnect; the outbox stops retrying an expense the server rejects and marks it failed, and its details offer Retry or Discard.
  • Balances: who owes whom, computed on-device from the cached expenses (so it works offline and includes pending ones), with one-tap "mark as paid" reimbursements. A "You" section at the top shows your own balance and is where you change who you are in the group.
  • Stats and Activity: the group's total spending (reimbursements left out), then spending per participant (paid and share) and per category; the group's activity log in date sections, loading more as you scroll (online only), where tapping an entry opens that expense's details.
  • Group settings: rename the group, change its currency, add, rename, or remove participants (online only). The same form creates a group.
  • App settings: light, dark, or system theme; language; the space stored receipts use, with Clear; and About: the version, a notice that this is an unofficial client not affiliated with the Spliit project, links to Spliit, the source, support and the privacy policy, and the open-source licenses, Spliit's and spliit-ios's included.
  • Languages: English, French, and Simplified Chinese, with locale-aware amounts and dates, switchable in the app without a restart.

Status

An early, usable Android client, tested on a physical device against a real self-hosted Spliit instance. The iOS app (#79) is iPhone only (#105) and has been tested on the simulator and a physical iPhone. Feature and bug work is tracked in GitHub issues and the project board, which are the source of truth for what is done and what is next.

Not built yet:

  • An on-device model pass for what the receipt parser misses (#154, and #165 on the iPhone).
  • QR scanning when joining (#41).
  • CSV/JSON export (#7).
  • Stats charts, projections, and a date-range selector; Stats is "all time" only.
  • More languages and right-to-left support (#64, #65).

Tests

test/ covers the core logic and the screens without needing a device or a live server: SpliitClient response parsing, AppDatabase caching and migrations, Outbox sync behavior, the balance and stats calculators, formatting and localization, and widget tests for each screen. Run locally with:

flutter test --coverage

CI (.github/workflows/ci.yml) runs flutter analyze and this test suite on every push and PR, and uploads the coverage report as a build artifact. See SETUP.md to run the same checks locally (scripts/run_test).

Support and privacy

  • Help, bugs and questions: GitHub issues, or email support@sharneng.com.
  • Privacy policy: docs/privacy.md. In short: no accounts, ads or analytics; group data goes only to the group's Spliit server; on Android, Google's ML Kit (receipt scanning) sends Google diagnostic data; on the iPhone, scanning uses Apple's on-device Vision, which sends nothing. Keep it in step with the code and with the store privacy answers when either changes.

License

MIT — see LICENSE. Parts are adapted from Spliit and spliit-ios, both MIT; their notices are in THIRD_PARTY_NOTICES.md and on the app's licenses page.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages