KasyKasy

Drive (ride-hailing)

Passenger and driver rides with a Mapbox map. Server-side price estimate; real payments need Stripe later.

Drive is the kit's ride-hailing module: users request a ride as a passenger or switch to driver mode to accept nearby requests. The full flow runs on all 3 backends (Firebase, Supabase, REST API) with the same UI.

RoleWhat it does
PassengerPicks pickup and drop-off on the map, sees distance, duration, and an estimated fare, tracks the ride in real time
DriverRegisters a vehicle, goes online, accepts nearby requests, and drives through completion or cancel

The map uses Mapbox via flutter_map (HTTP tiles, works on native and web). Route and price are computed on the server (Mapbox Directions + your coefficients), not in the app.

The amount shown on screen is an estimate only. The kit does not charge the passenger or pay the driver. For real payments, wire in the Stripe (web) module or another provider on your backend.

Implementation

Via MCP: ask your assistant "add drive to my project". It runs add_feature for you. See Kasy MCP.

  1. Turn on the module (it doesn't ship in Quick mode by default):

    kasy add drive        # adds it (asks for Mapbox token and price coefficients)
    kasy remove drive     # removes it
    kasy add --list       # shows what's on

    kasy add drive writes MAPBOX_ACCESS_TOKEN to .env, seeds DRIVE_* coefficients, and on Firebase/Supabase tries to store the Mapbox token as a server secret.

  2. Create the Mapbox token:

    1. Create an account at mapbox.com
    2. Generate a public access token (pk.…)
    3. Set usage limits and URL/bundle restrictions in the Mapbox console
  3. Configure secrets and coefficients:

    kasy configure drive

    Or through the Drive (Ride-hailing) section of kasy configure:

    VariableWhere it livesPurpose
    MAPBOX_ACCESS_TOKENApp .envMap tiles in Flutter
    MAPBOX_ACCESS_TOKENServer secret (Firebase/Supabase)Directions and fare on the backend
    DRIVE_BASE_FAREfunctions/.env or Supabase secretsBase fare (default 5)
    DRIVE_PRICE_PER_KMsamePrice per km (default 1.5)
    DRIVE_PRICE_PER_MINsamePrice per minute (default 0.25)
    DRIVE_CURRENCYsameISO currency code (default USD)

    On REST API, coefficients live in your server .env. See the contract in docs/drive.md (shipped with the project) and the API patch README.

    Via MCP: paste the Mapbox token and the coefficients in chat and ask "set up Drive" — the assistant writes them with configure_project_keys (Firebase/Supabase). See Kasy MCP.

  4. Deploy the backend:

    BackendWhat to run
    Firebasekasy deploy (publishes driveFunctions + push triggers)
    Supabasesupabase db push (drive migration) + supabase functions deploy request-ride
    REST APIImplement POST /drive/rides, POST /drive/rides/{id}/accept, etc. (see patch README)

    Via MCP (Firebase): "deploy the backend" (deploy_backend).

  5. Confirm the native permissions. The app requests location at runtime (pickup, driver online). On iOS, Info.plist already includes usage strings when the module is on. Validate on a real device before release.

  6. Test the flow. Two ways:

    • Alone, without real GPS: outside production, turn on "Automated test mode" in Drive's side menu — it simulates the driver walking a fixed route, so a single account can test passenger and driver by switching roles. See Automated test mode below.

    • With two devices or accounts (or emulator + physical device), simulating passenger and driver at the same time:

      1. Open Settings
      2. Tap the Drive tile (car icon)
      3. Choose Request a ride (passenger) or Driver mode
  7. For real payments (outside the Drive module's scope), add Stripe (web) or another gateway and tie billing to the ride complete event on your backend.

Information

How the flow works

On first open (Settings → Drive), the app asks once whether the user wants to ride as a passenger or drive as a driver, and saves the choice. After that, Drive opens straight into the saved role's screen; switching roles is done from Drive's side menu.

The active ride is recognized from any browser or device signed into the same account — switching devices mid-ride never loses tracking or lets you accidentally request a duplicate ride.

Passenger:

  1. Searches the destination by text or adjusts the pickup/drop-off pins on the map
  2. Sees the route, distance, time, and the estimated price (calculated on the server)
  3. Requests the ride and tracks it live: driver position, vehicle and plate, live ETA
  4. If no driver accepts within about a minute, the screen shows "No drivers available" with a live countdown and a try again option — up to 2 times. After that, only canceling and retrying later is left
  5. Can cancel only while the ride is requested or accepted. Once on board (in_progress), cancellation is no longer available in the app

Driver:

  1. Must register the vehicle (onboarding) before going online. The switch opens the form automatically if it is missing
  2. Online starts continuous GPS: the position is published to the backend, and that is what the passenger sees on the map
  3. Nearby requests arrive as a full-screen overlay with sound and haptics, with Accept and Decline (declining only hides it for that driver). While on a ride, no new requests come in
  4. Accepting opens navigation to the pickup. The "Picked up passenger" button only unlocks when GPS is within 100 m of the pickup point
  5. Then navigation switches to the destination, and "Complete ride" only unlocks within 100 m of the drop-off. Outside the radius the button stays disabled with a hint on screen

Transition rules (validated on the server)

The unlocked button on screen is not the only guard. The backend validates every transition, with the same rules on all 3 backends:

ActionOnly accepted when
AcceptRide still requested (if two drivers accept, only the first wins)
Start (picked up)Ride accepted and the caller is the assigned driver
CompleteRide in_progress and the caller is the assigned driver
CancelRide not yet completed or canceled; only the ride's passenger or driver

Automated test mode (dev)

Outside production (any build with ENV other than prod), Drive's side menu has an "Automated test mode" item. Turned on, it lets you test the whole flow — request, accept, decline, pick up, complete — alone, with a single account, without needing two devices or real GPS:

  • Location becomes a fixed simulated route (a real stretch, routed through Mapbox); the driver "walks" that route alone, at an accelerated pace (the whole ride takes seconds, not minutes).
  • The back button during tracking does not cancel the ride — it switches your own account to the other role (turns you into the driver so you can accept/decline that same request, or back into the passenger), since it's the same person testing both sides.
  • Declining does not cancel the ride: it stays requested and visible to other drivers, same as in production.

Only in this mode: a hot restart (uppercase R in flutter run/kasy run, or reloading the tab from scratch) resets the simulated vehicle's position — it walks the route again from the start, even if it had already arrived. This is a limitation of the simulator (its position only lives in the app's memory, never saved anywhere), not a bug in the real flow. Production has no equivalent: the phone's GPS is an external source that keeps whatever position it was at — no restart ever "forgets" the driver's position.

This toggle never shows up in a production build (ENV=prod) — no end user ever sees it.

Where to look in the Firebase Console

Drive does not store driver or ride data under Authentication. That tab only shows the account and the User UID. Data lives in Firestore on the same project the app uses.

What you needWhereNote
Test accountAuthentication → UsersCopy the User UID
Driver registrationFirestore → drivers/{uid}Same {uid} as Authentication
RidesFirestore → rides/Created by the requestRide callable
Device push tokenFirestore → users/{uid}/devices/Not the driver's name

Automated test mode: only GPS is simulated. Request, accept, and status changes always hit the backend. If rides/ is empty, check function deploy, Mapbox secret, and that drive_api.dart matches functionsRegion in kit_setup.json.

On Supabase, use the drive schema (drivers, rides) in Table Editor.

Testing without the full setup

With withDrive on, the Drive screens open even without a Mapbox token (the map stays blank). For the full flow (request, accept, start, complete), you need the token in .env, the secrets configured, and the backend deployed.

SituationWhat works
Debug without Mapbox tokenScreens open; map without tiles
Debug with token + deployed backendFull passenger + driver flow
Release without tokenBroken map; do not ship like this

What's in the UI

  • Entry: Drive intro with role choice on first open (Settings → Drive); the role is saved
  • Passenger: full-screen map home, destination search, pin adjustment, estimate, and live tracking
  • Driver: vehicle onboarding, online/offline switch, nearby-request overlay, navigation with progressive route and ETA
  • Push: notifications when a ride is created or changes status (Firebase triggers / Supabase equivalent)

There is no separate role system: a user with a drivers/{uid} profile is a driver; any signed-in user can request a ride.

Last updated on 08/02/2026