NEXSUM_LABS
  1. Home
  2. Work
  3. A passenger ferry operator gave commuters a timetable and ticket app that works when the harbor connection doesn't
Book a call

[ Case study ]

Passenger ferryKotlinJetpack ComposeWorkManagerRoom

A passenger ferry operator gave commuters a timetable and ticket app that works when the harbor connection doesn't

Timetables lived on a website that assumed good connectivity at the terminal — exactly where it failed — and seasonal ticket windows produced morning queues that pushed commuters toward driving.

CLIENT a regional passenger ferry operator — FOCUS Cache the day, stream the deltas

Android DevelopmentMobile AppsAndroid DevelopmentPassenger ferryRepresentative example
Client
a regional passenger ferry operator
Industry
Passenger ferry
Engagement
8 weeks — experience pod — mobile engineer + designer
Service
Mobile Apps / Android Development
Headline outcome
Share of digital tickets validated offline-capable at first launch of the trial season, read from the operator's validation logs: 62% → 89%, read from Operator validation logs

Representative examplesEvery case study in this library is an illustrative composite of the kind of engagement we deliver — written to show our method and standards, not to name clients.

Where they started

Between the mainland terminal and a chain of island stops runs a commuter ferry service with a seasonal tourist surge and a timetable that changes four times a year. The operator sells tickets from staffed windows and a small online store, and vessel positions come from a licensed tracking feed it pays per call. Its riders are commuters with annual passes, day-trippers with phones and no patience, and terminal staff on whatever handsets the company issued years ago.

What it was costing

Timetables lived on a website that assumed good connectivity at the terminal — exactly where it failed — and seasonal ticket windows produced morning queues that pushed commuters toward driving.

What they could see

  • The timetable page spun at the terminal precisely when commuters needed it, because coverage dies in the waiting hall's concrete corner.
  • Ticket windows produced queues before the two morning sailings, and the queue pushed regular commuters back into their cars.
  • Terminal staff called the office to confirm whether a sailing was on time, because the tracking page was no faster than the passengers' own guesses.
  • Summer day-trippers missed sailings trying to buy tickets on the pontoon, then complained publicly about the operator.

The constraints we worked inside

  • The vessel tracking feed is licensed and rate-limited; the app must not poll it naively.
  • Ticketing runs through the operator's existing payment provider; no new payment relationship was in scope.
  • The fleet includes older Android handsets used by crew-facing staff at the terminal.

What had been tried before

A contractor built a responsive timetable site with live tracking widgets that the operator promoted at the terminal.
It assumed the one thing the terminal lacks — connectivity — and every timetable change had to be re-published by the office, so the live page was routinely wrong.
The operator printed seasonal pocket timetables and sold books of paper tickets at convenience stores near the terminal.
Print went stale at every timetable change, paper tickets created their own validation queue, and neither did anything about the morning window queue.

What we proposed

We proposed an Android app that caches the full day's timetable at first launch and treats live tracking as a stream of compact deltas rather than repeated pulls, keeping the licensed feed inside its rate budget. Buying a ticket is two screens — route, then pay — reusing the operator's existing payment provider and saved passes, and the purchased ticket lives in a local pass store that validates offline. The performance bar came from the terminal's actual handsets, not our test phones.

Just as important is what we ruled out, and why:

  • A cross-platform framework to cover iOS commuters tooThe visible failures were Android-terminal problems and the build had to land before the season; native Kotlin met the old-handset performance bar without a framework's startup cost.
  • An operator-run payment and ticketing backendNo new payment relationship was in scope, and standing up PCI-scoped ticketing before the season would have consumed the entire engagement on infrastructure instead of the queue problem.
  • Polling the tracking feed on an interval with client-side throttlingPer-call billing means every idle commuter with the app open costs money; a delta subscription was the only shape that scaled with riders rather than against them.

How the work ran

01Cache the day, stream the deltas

The full day's timetable is cached at first launch, and tracking updates arrive as compact deltas over a subscription — the app stays useful offline and the feed stays inside its limits.

02Buy tickets in two screens

Route, then pay: saved passes from the payment provider make the repeat commute a two-tap flow, with the ticket usable offline from a local pass store.

03Test on the terminal's worst devices

A device matrix built from the terminal's actual handsets set the performance bar, not the flagship phones the team owned.

Delivered by the experience pod — mobile engineer + designer over 8 weeks, with working increments reviewed with the client every week.

The stack, and the reasoning

Kotlin
Terminal staff handsets and commuter devices span old OEM builds; Kotlin native gave predictable background behavior where a framework's assumptions would have shown.
Jetpack Compose
The two-screen purchase flow and timetable views needed consistent state handling across many device sizes; Compose kept the UI declarative and testable with a small team.
WorkManager
Timetable refreshes, delta syncs, and pass cleanup are deferrable background work that must respect the feed's rate budget — WorkManager schedules it without a foreground service.
Room
The offline promise rests on a structured local store — cached timetable, pass state, sync queues — and Room's migrations keep that schema honest across releases.
Pass handoff to existing payment provider
The provider's saved-pass flow turned repeat commuting into two taps; building our own wallet would have added a payment relationship nobody asked for.

What went wrong

Obstacle

Mid-design, we learned the tracking feed's license capped concurrent subscribers far below our assumed headroom — the polling architecture we had sketched could never survive a summer weekend.

Handled: We spent the redesign week converting tracking to a delta subscription with shared server-side fan-out, and negotiated a pilot-season rate with the feed's vendor in parallel.

Obstacle

The payment provider's sandbox was offline for most of the integration window, which put ticket-purchase testing on a collision course with the launch date.

Handled: We built a contract mock from the provider's published API and tested the purchase flow against it daily, then swapped in the live provider for one verified rehearsal before launch.

How we worked together

Cadence
A Tuesday demo call with the operations manager, plus Thursday mornings at the terminal during the two busiest sailings to watch real commuters use the build.
Client side
The ticket-office supervisor ran window-side trials; one deckhand with the oldest issued handset became our performance reference; the operations manager owned scope and vendor conversations.
Decisions
The tracking feed's terms and any provider-facing change went to the operations manager in writing; everything else was decided at the demo or explicitly deferred.
They provided
Credentials for the tracking feed, an introduction to the payment provider's integration team, the terminal device list, and staffing the ticket office during trial sailings.

What changed

The headline: share of digital tickets validated offline-capable at first launch of the trial season, read from the operator's validation logs62% → 89%, read from Operator validation logs. A second check: ticket-window queue length at the two busiest sailings at −41%.

The terminal's morning changed character. Regulars board scanning a phone that works in the concrete corner where the website never did, and the ticket window's queue is now mostly tourists rather than a daily ritual the operator had budgeted around. Terminal staff stopped calling the office for sailing status — the app is the source they trust. The operations team also gained something structural: timetable changes propagate to phones the day they are decided, so seasonal reshuffles stopped being a communications exercise.

The result was read from Operator validation logs against the pre-engagement baseline over the stated window, with a guardrail check on ticket-window queue length at the two busiest sailings. Where platform-reported numbers and business outcomes differ, this record says which layer it is quoting.

What they own now

  • Google Play Console account, signing key custody, and the release checklist
  • A runbook for timetable publication and cache-refresh behavior across versions
  • The feed-rate budget document and monitoring dashboard for the delta subscription
  • The terminal device matrix test kit, including the oldest crew handset
  • A rehearsal script for payment-provider updates so future changes are tested the same way

What we would do differently

We would have negotiated the tracking-feed terms before design — the rate limit shaped the architecture more than any user requirement, and learning that late cost a redesign week.

Mobile AppsAndroid DevelopmentPassenger ferryKotlin

Next case study

A home-infusion pharmacy gave its delivery nurses a proof-of-delivery app that survives basement drops