← all work

Dinamo

Egyptian ride-hailing and delivery. Two apps, two brands from one codebase, and a driver app whose one job is to notice a trip exists while Android tries to kill it. I owned that reliability layer from 2023.

50K+ downloadsrider app, 3.8 from 516+ reviews

Role
Android developer — reliability and background systems
Span
Aug ’23 – Jan ’25
Kind
Client work · product
Status
archived
Stack
Kotlin · WorkManager · Foreground Services · Firebase · FCM · Product Flavors · RTL

The system outlived every assumption in it

Dinamo ran in production from a first commit in December 2020 to a last one in January 2025. I wasn’t there for all of it: I joined the codebase in August 2023 and my work runs to the final commit, concentrated on the parts that decide whether a driver actually receives a trip.

Two apps, driver and rider. Both are still on Google Play: the rider app (v10.3.7, versionCode 116) at 50K+ downloads and 3.8/5 across 516+ reviews, the driver app (v8.2.2, versionCode 83) at 5K+ downloads and 4.5/5 across 190+ reviews. Real usage that outlasted the team that built the reliability layer.

One source tree, eight builds

Two brands, “go” and “wasalni”, were built from one codebase via product flavours rather than forked. A second flavour dimension, dev and prod, crosses it. Worth being exact about what is shared: the code is shared across brands, the backend is not.

Product flavours: two brands, each with its own package ids and backend
FlavourPackages · driver / riderBackend
gocom.starbugs.go_drivercom.starbugs.go_userwessalegy.com
wasalnicom.starbugs.wasalni_drivercom.starbugs.wasalni_riderdashboard.dinamo-app.com

x dev / prod environment flavours = eight builds, one source tree

The backend column is the one that matters. Driver and rider of the same brand meet on that brand's domain; the two brands never share a server.

That split has a cost nobody sees until it bites: every bug fix ships to both brands, but every bug report comes from one. A crash only wasalni users hit is still a crash in go’s binary, waiting for the conditions that trigger it.

The part that actually mattered

A ride-hailing driver app has one job that cannot fail: notice that a trip exists. On Android that is genuinely hard, because the OS is actively trying to stop you. Battery optimisation, Doze, and OEM process-killers all exist to end exactly the kind of long-running background work a driver app is made of.

The September 2024 rebuild of the driver app’s trip fetching (ba47be4, with 7ddefbb and 19d84ba the same day) answers that with separate pieces that never call each other:

The accented file is the design. Location and fetching share a preferences file, not a process, so either side can be killed and the other keeps its last good state.
  • A foreground service drives the 30-second cadence and returns START_STICKY, so Android tries to restart it if it is killed.
  • Each tick’s real work runs as a WorkManager job holding a wake lock and a Wi-Fi lock (ten-minute ceiling), so one fetch completes or retries whether or not the service lived long enough to see it through.
  • The location service is its own foreground service, restarted with START_REDELIVER_INTENT so it comes back with its last instructions.
  • A boot receiver brings location back after a reboot.

One trip, four ticks

The fetch loop is easier to read running than described. The worker offers the first trip in the response. A trip shown and not accepted goes into an ignored-orders map with a timestamp, and is not shown again for 30 seconds, because offering the same trip on every tick is how a driver learns to ignore notifications.

  1. Fetch near 30.0444, 31.2357

    Trip 4127 returned first

    Offer shown

  2. Fetch again

    Trip 4127 still open

    Suppressed: in ignored map, 30 s not up

  3. Fetch again

    Trip 4127 still open, cooldown expired, entry cleaned

    Offer shown again

  4. Fetch again

    Trip 4127 gone, taken by another driver

    Nothing to show

The rules are the worker's: FIFO, a 30-second ignored-orders cooldown, expired entries cleaned. The trip number, coordinates and times are illustrative.

The second row is the one users never see, and the one they would complain about most if it were missing.

What this is not

START_STICKY is a request to the operating system, not a guarantee. Aggressive OEM process-killers, common on the devices this shipped to, can still prevent a restart, and there is no battery-optimisation whitelist prompt in this codebase. The architecture is honest about its intent; it isn’t bulletproof, and no amount of foreground-service discipline makes it so.

What the version history actually shows

It is tempting to describe the app’s growth as accumulated battery engineering. It isn’t. Reading the commit history back, the recurring theme is state after death: crashes on relaunch, stale caches producing “not available in your area”, notification handling that dropped work when the process was recycled.

That’s a less glamorous story than battery tuning and a more useful one. The bugs that survive in production are the ones about what your app remembers when the OS decides it no longer exists.

What replaced it

The Kotlin apps ended with January 2025. The platform did not: another team rebuilt it in Flutter on a new Laravel backend, and the rebuild answers this page’s central problem differently.

The accent is the arrow that changed direction. In 2025 the server initiates; the phone only has to stay connected.

In the new backend, TripCreated runs CaptainFilterService::findCaptainsForTrip() and broadcasts to one captain.{id}.trips private channel per eligible captain. InviteExpired withdraws the offer on the same channel. That is the ignored-orders map, moved to the server where it belongs.

The same problem, one layer up

Push does not remove the problem, it relocates it. A phone that is not polling still has to keep a socket alive through Doze, and the new driver app ships flutter_background_service to do it. The 2024 question, “is the driver still reachable?”, became a connection-health question instead of a scheduling one. Better question, same operating system.