RideX Documentation

Introduction

RideX is a white-label ride-hailing platform: a Laravel backend with an admin panel, a Flutter Rider app and a Flutter Driver app. Riders book and pay for trips; drivers go online, accept offers and earn; you run the platform from the admin panel.

This documentation is also online, always in its latest version, at shamimcse1.github.io/ridex-docs.

What's in the package

Folder What it is
ridex_backend/ REST API, admin panel and web installer (Laravel 12, PHP 8.4.1+)
ridex_rider/ Rider app (Flutter, Android and iOS)
ridex_driver/ Driver app (Flutter, Android and iOS)
packages/ridex_core/ in each app Code shared by both apps: an identical copy inside each app (Shared app code)
Documentation/ This documentation, its screenshots (assets/) and the Postman collection of the API (api/)

The backend comes without the folders Composer and npm create (vendor/ and public/build/). The first setup step adds them.

Setup order

  1. Backend. In ridex_backend/, run composer install --no-dev --optimize-autoloader and npm ci && npm run build (on your computer if your host has no terminal). Then upload it, point the web server at its public/ folder and open /install, or use the terminal steps. See Backend and Deployment.
  2. Admin panel. Sign in, then set your branding, zones, vehicle types and fares (First-time setup).
  3. Keys. Add your Google Maps, Firebase, Twilio and payment gateway keys (Third-party services).
  4. Apps. Set the backend URL, Maps keys and Firebase files, then build both apps (Mobile app setup).
  5. Go live. Work through the going live checklist. Once your apps are in the stores, enforce Firebase App Check so only your genuine apps can use the live-tracking database and Firebase sign-in (Firebase App Check).

Requirements

  • Server: PHP 8.4.1+, MySQL 8+ or MariaDB 10.4+, a cron job and a queue worker. To install, Composer 2 and Node.js 20.19+ or 22.12+ (with npm), on the server or on your computer: they add the PHP packages and build the admin panel's CSS and JS.
  • Apps: Flutter 3.44+ (Dart 3.12+), with Android Studio for Android and Xcode for iOS. They run on Android 7.0 (API 24) or newer, built for API 36, and on iOS 15.0 or newer (details).
  • Accounts: Google Cloud (Maps), Twilio for sign-in codes (or Firebase Phone Auth), and a payment gateway for card or online payments (cash and wallet payments need no gateway). Firebase is optional: it adds push notifications and instant ride updates. To publish the apps: Google Play Console and a paid Apple Developer account, which Firebase App Check also uses for Play Integrity and App Attest.

Backend & admin panel

Laravel 12 REST API for the RideX Rider and RideX Driver Flutter apps, plus the Blade admin panel.

RideX/
├── ridex_backend/   ← this project (API + admin panel)
├── ridex_rider/     ← Flutter rider app (shared app code in packages/ridex_core)
└── ridex_driver/    ← Flutter driver app (an identical copy of packages/ridex_core)

Documentation: docs/index.html is the complete guide (backend, deployment, both apps and the API reference) as one page with its screenshots in docs/assets/, so it can be opened offline or uploaded to any static host. It's built from this README, DEPLOYMENT.md, the app READMEs and the pages in docs/src/ (the version number comes from CHANGELOG.md), so rebuild it after editing any of them:

php artisan ridex:docs

The CodeCanyon package carries the same page in its top-level Documentation/ folder, with the screenshots (assets/) and the Postman collection (api/). The command refreshes that folder whenever it sits next to this project. Keep screenshots in docs/assets/screenshots/ and show them with ![What it shows](docs/assets/screenshots/<file>.png), which works both in this README and in the page.

Requirements

  • PHP 8.4.1+ (extensions: ctype, curl, dom, fileinfo, json, mbstring, openssl, pdo_mysql, tokenizer, xml, xmlreader, zip)
  • Composer 2
  • Node.js 20.19+ or 22.12+ with npm, to build the admin panel's CSS and JS
  • MySQL 8+ or MariaDB 10.4+ (XAMPP works for the database; its bundled PHP 8.2 is too old to run RideX)

Installation

For a live server (Apache/Nginx, shared hosting, Supervisor, Google Maps, Firebase, Twilio and payment gateway keys, troubleshooting), follow DEPLOYMENT.md.

Web installer: the package comes without the two folders Composer and npm create, vendor/ and public/build/. First run composer install --no-dev --optimize-autoloader and npm ci && npm run build in this folder (on your computer if the host has no terminal), then upload the files, including those two folders. Don't create .env; open {APP_URL}/install. Until both folders are in place, every page shows the two commands instead. It checks the server, asks for the database and the super admin, then migrates, seeds and writes .env. It switches itself off afterwards: the app counts as installed once .env has an APP_KEY.

Locally, with a terminal:

composer install
npm ci && npm run build
cp .env.example .env
php artisan key:generate
# create an empty database named "ridex" (utf8mb4), then set DB_* in .env
php artisan migrate --seed
php artisan storage:link
php artisan serve

Background processes (both are required for dispatch):

php artisan queue:work --queue=dispatch,notifications,default   # offer timeouts, search retries, push notifications, card charges
php artisan schedule:work                                       # local; in production add the cron below

Production cron: * * * * * cd /path/to/ridex_backend && php artisan schedule:run >> /dev/null 2>&1.

  • Every minute it runs ridex:dispatch-tick, which releases scheduled rides that are due and expires overdue offers if a timeout job was lost.
  • Once a day (documents.reminder_time) it runs ridex:remind-expiring-documents: drivers are told once before a document expires and once after.
  • Every minute it runs ridex:send-push-campaigns, which queues the push campaigns whose scheduled time has come.

Local testing and demo mode

.env.example ships production-safe defaults: APP_ENV=production, APP_DEBUG=false, RIDEX_DEMO_MODE=false and OTP_PROVIDER=twilio. To try every flow on your machine without SMS or payment keys, change these in .env before php artisan migrate --seed:

APP_ENV=local
APP_DEBUG=true
RIDEX_DEMO_MODE=true   # demo accounts, demo OTP and the Demo payment gateway
OTP_PROVIDER=log       # codes go to storage/logs/laravel.log and come back as debug_code

Going live checklist

  • APP_ENV=production, APP_DEBUG=false, and APP_URL set to your https:// domain.
  • SESSION_SECURE_COOKIE=true once the site runs on https, and TRUSTED_PROXIES if it sits behind a load balancer or CDN such as Cloudflare (see DEPLOYMENT.md).
  • RIDEX_DEMO_MODE=false. Otherwise the demo numbers log in with the demo OTP and the Demo payment gateway adds wallet money for free.
  • OTP_PROVIDER=twilio with TWILIO_SID, TWILIO_AUTH_TOKEN and TWILIO_FROM set. Without them, sending a code fails with sms_not_configured. Alternatively, send codes with Firebase Phone Auth (see DEPLOYMENT.md).
  • MAIL_* set to your mail service, or users' emails are only written to the log.
  • Firebase rules deployed: press Deploy rules in Admin → Firebase Configuration, and check that Realtime Database → Rules doesn't show ".read": true or ".write": true (Firebase).
  • Firebase App Check registered for both apps, and enforced for Realtime Database and Authentication once the store builds show up as verified (Firebase App Check).
  • Set ADMIN_EMAIL / ADMIN_PASSWORD before seeding, or change the password in Admin → Profile right after the first login.
  • Run php artisan optimize after every .env change, and keep the queue worker and cron running.

Admin panel

URL: {APP_URL}/admin. It is built with Blade, Tailwind CSS v4 and Alpine.js.

The admin panel's sign-in page, with the demo accounts shown in demo mode
Figure 1: The admin panel's sign-in page, with the demo accounts shown in demo mode
The dashboard after the first sign-in, with the menu on the left
Figure 2: The dashboard after the first sign-in, with the menu on the left
  • Assets: npm ci && npm run build compiles the admin panel's CSS and JS into public/build (Vite). Run npm run build again after changing the styles or scripts.
  • White-label: the brand color comes from the branding.primary_color setting. It is applied as a CSS variable, and every brand-* shade is derived from it with color-mix(), so rebranding needs no rebuild.
  • Dark mode: follows the system setting and can be toggled from the top bar. The choice is saved in the browser.
  • Roles and permissions: every route is guarded by a permission slug (->can('drivers.approve')). The sidebar (app/Support/AdminMenu.php) only shows modules whose route exists and that the admin is allowed to open.
  • Admins & Roles: add admin accounts, and build roles from a permission grid (each module's view, create, edit and other actions).
    • Admins only hand out access they hold themselves: roles within their own permissions, and accounts that use such roles. No one can change their own role, status or account, so the signed-in Super Admin always remains. The Super Admin role holds every permission and can't be edited.
    • The built-in Operations, Finance and Support roles can be edited but not deleted. Re-running AdminRolePermissionSeeder keeps those edits and only grants permissions it adds. A role without the dashboard opens its first allowed page after sign-in.
  • Super Admin → Firebase Configuration: upload the Firebase service-account key (Firebase console → Project settings → Service accounts → Generate new private key). Only Super Admins can open it; it cannot be granted to other roles.
    • The file must be a service_account key with a valid project ID, service-account email and RSA private key. Anything else is rejected with the reason.
    • It is stored encrypted with APP_KEY in storage/app/private/firebase/, never in the database or cache, and the private key is never shown again. After changing APP_KEY, upload it again.
    • An uploaded key takes precedence over the FIREBASE_CREDENTIALS file. Uploading never changes other settings. The page warns when the key's project differs from FIREBASE_PROJECT_ID or no Realtime Database URL is set.
    • Connected apps shows, for each app, how many signed-in phones registered for push. Deploy rules uploads firebase/database.rules.json to the Realtime Database with the same key, so no Firebase CLI is needed.
  • KYC review:
    • Drivers → Review shows the approval checklist, every document with a preview, and approve/reject actions. A rejection reason is required and is shown to the driver.
    • KYC Documents is a first-come-first-served queue, with tabs for expiring, expired and rejected documents.
    • A driver can only be approved once every required document and the default vehicle are approved. The panel lists exactly what is still missing.
    • Every decision is written to the activity log and sent to the driver's in-app inbox.
  • Driver performance: each driver's page has a Performance card for today, this week or this month, in the driver's local time: trips, earnings (fares and commission), tips and bonuses, cash collected, online time and the acceptance rate. These are the figures the driver app shows. The acceptance rate counts the offers a driver accepted, declined or let expire. Withdrawn offers don't count against the driver: another driver accepted first (broadcast mode), or the rider cancelled or the search ended. The page lists all four. The drivers list and the driver app show the lifetime rate on the same terms.
  • Account status: suspending or banning a user needs a reason. It revokes their app tokens immediately and takes drivers offline.
  • Super Admin → Payment Configuration: everything the apps offer at checkout is decided here, so no payment method is hard-coded in the apps.
    • Switch the rider payment methods on or off: cash, wallet, card (needs Stripe) and pay online (needs any gateway).
    • Connect Stripe, Razorpay, PayPal and Flutterwave. Each card is built from the gateway's own field list, so adding a gateway class adds its settings form.
    • Keys are encrypted with APP_KEY and never shown again (only the last 4 characters). Leave a key blank to keep it; tick "Remove" to delete it.
    • Wallet limits (minimum top-up, maximum balance, minimum withdrawal, driver dues limit), tips and the maximum tip.
    • Each gateway shows its webhook URL. Webhooks are optional: the app also confirms a payment when the rider returns from the hosted page.
    • With RIDEX_DEMO_MODE=true a Demo payment gateway appears, so every flow can be tried without keys. Turn demo mode off before going live.
  • Finance:
    • Wallet transactions: the full ledger with filters, and manual credit/debit adjustments (with a reason, audit-logged).
    • Withdrawals: approve, mark as paid with the transfer reference, or reject with a reason. A rejection returns the held amount to the driver's wallet.
    • Payments: every gateway payment, with full or partial refunds. A refunded top-up is first taken back out of the wallet.
  • Incentives: daily, weekly or monthly targets (trips, earnings or online hours), optionally limited to a vehicle type or zone.
  • Zones: draw each service zone on a map (click to add a point, drag to move it, click a point to remove it), with a priority and an airport fee. Cities (map centre, time zone) and countries (dialling code, currency) are managed next to them. Anything still in use, such as a zone with fare plans or a city with drivers, can't be deleted.
  • Promo Codes: a percentage (with an optional cap) or fixed discount, minimum fare, total and per-rider limits, first ride only, vehicle type and zone targeting, and start/end dates. Each code lists its redemptions. A deleted code stays reserved, so it can't be reused.
  • Content (CMS): pages such as About, Terms and Privacy (rich-text editor), FAQs, home-screen banners (image, tap action, zone, schedule), chat quick messages and cancellation reasons, each for the rider app, the driver app or both. The apps list the pages under Settings → About & legal and the FAQs in Help & support. Each page is also public at https://your-domain/pages/{slug} (View in the pages list; ?lang=ar for a translation): use /pages/privacy-policy as the privacy policy link in the App Store and Google Play listings.
    • Pages, FAQs, quick messages and cancellation reasons can be translated into each active language; the apps receive the version for their X-Locale header. A banner's text is part of its image.
    • Banners show on both apps' home screens as swipeable 3:1 images (1200 × 400 pixels works well). A tap opens a link, applies a promo code to the rider's next booking (rider banners only), or opens an app screen picked from a list (Banner::SCREENS; each app maps the keys to its own screens). A banner with a zone shows only where the rider's pickup or the driver's GPS position is in that zone.
    • Page HTML is cleaned when saved (app/Support/SafeHtml.php): only basic formatting and http, https, mailto and tel links are kept.
  • Settings: General (platform and app names, logo, favicon, colours, support contact), Localization & currency, Rides & pricing (ride types, ride PIN, stops, scheduling window, service-zone requirement, surge limits), Safety & support, and App versions. Changes apply at once and reach the apps through GET /config. Each save is audit-logged with the old and new values (never keys or files).
    • The fields come from app/Support/SettingsCatalog.php (label, type, validation), so adding a setting there, plus its row in SettingSeeder, adds it to the page. Long lists, such as the currency and the default phone country, use 'input' => 'searchable-select': a dropdown with a search box (<x-admin.searchable-select>, also used for time zones and a city's country). The currency symbol's options follow the chosen currency ('options_from' and 'option_sets'), and 'creatable' => true also accepts a typed value.
    • App versions (force update): each app's minimum version and its Google Play and App Store links. An older app stops at launch with an Update now button, so keep 1.0.0 until a release really needs everyone on it.
  • Languages (Super Admin): the languages the apps offer, with the native name, text direction (right-to-left for Arabic, Hebrew, Persian, Urdu) and an on/off switch. The apps start in the phone's language when it is offered, and in the default language otherwise; the default can't be switched off. English, Arabic, Spanish, French and Bengali ship fully translated. To add another, see Adding a language below.
  • Dispatch Settings (Operations): sequential or broadcast mode, time to accept, drivers per broadcast, starting and maximum radius and growth per round, rounds, the give-up time, when scheduled rides start searching, how often drivers report their location, and destination mode (on/off, destinations per driver per day).
  • Referrals (Finance): who invited whom, rewards paid, and the reward amounts and on/off switch. New amounts apply to new sign-ups.
  • Push Notifications: send an announcement or offer to all users, riders or drivers, optionally in one zone (drivers based there and riders who have ridden there), now or at a scheduled time.
    • Every message lands in the in-app inbox; the push respects each user's "promotions" preference. An optional image is shown on Android.
    • Delivery runs on the queue in batches of 500, so large audiences never block the panel. A scheduled campaign can be cancelled until it starts.
  • Super Admin → Maps, SMS & Sign-in: the Google Maps server key, a note on where the apps' Maps keys go (Google reads them only from the app builds), the map tiles and attribution for the admin and trip-share maps, the Sign in with Google / Apple switches, how sign-in codes are sent (Twilio SMS, Firebase Phone Auth or, outside production, the server log) and the Twilio credentials. Server keys are encrypted and write-only; a blank key falls back to .env. Switch a sign-in method on only after its set-up (Sign in with Google and Apple in DEPLOYMENT.md) is done: the apps show the button as soon as it is on.
  • Safety & support:
    • SOS Alerts: open alerts first, active ones in red, with a dashboard banner and a menu badge. Each shows the user, the ride, a map link and the emergency contacts. Acknowledge while helping, then close it as resolved or a false alarm, with notes.
    • Support Tickets: the queue is sorted by priority (safety reports are urgent), with category/priority filters, search and "assigned to me". Reply in the thread (the user gets a push), set the priority and agent, and resolve or close the ticket.
    • Lost & Found: follow up open reports and record the outcome; the rider is notified.
    • The dashboard shows the support queue and open lost items. A ride's page shows both ratings, the chat transcript and any related SOS alerts, tickets and lost items.
  • Reports: rides per day and how they ended, revenue (rider payments, driver earnings, platform share, commission, tax, promo discounts, cancellation fees) and per-driver totals, for any range up to a year, with charts.
    • Download as CSV, Excel or PDF, or print, with the reports.export permission (Finance; Operations can only view). Every download and print is audit-logged.
    • Text that a spreadsheet would run as a formula (such as a driver named =…) is exported as plain text. PDFs list the first 1,000 rows; CSV and Excel include every row.
    • PDFs can't join Arabic letters or show Bengali script (a dompdf limitation). For those, use Print: it opens the report as a print-ready page, and the browser's "Save as PDF" keeps every script intact. The reports page points this out when a report needs it.
  • Activity Log: every admin action and important app event, with who, when, the IP address and the details, filtered by person, action, record type and date. A deleted admin's entries keep their name.

First-time setup

A new install comes with sample cities and zones (New York, London and Dhaka), five vehicle types (Moto, Economy, Comfort, XL and Premium) and their fare plans. Before going live:

  1. Settings → General: your platform and app names, logo, colours and support contact.

    Settings → General on a new install
    Figure 3: Settings → General on a new install
  2. Zones: add the cities you serve and draw their zones (Cities and Countries are tabs on the same page). Switch off or delete the sample zones you don't need. Then, in Settings → Localization & currency, choose your currency (its usual symbol is filled in; pick another or type your own) and your Default phone country: the apps' phone number fields start on it, and riders can still pick any other country.

    The zone editor: click the map to add a point, drag a point to move it
    Figure 4: The zone editor: click the map to add a point, drag a point to move it
  3. Vehicle Types and Fare Plans: the categories riders choose from and what each costs. A default plan applies everywhere; a zone plan overrides it inside that zone.

    The five sample vehicle types
    Figure 5: The five sample vehicle types
    The sample fare plans: base fare, per km, per minute, minimum fare and commission
    Figure 6: The sample fare plans: base fare, per km, per minute, minimum fare and commission
  4. Dispatch Settings: how ride requests reach drivers, how long a driver has to accept, and how far the search reaches.

    Dispatch Settings with the default values
    Figure 7: Dispatch Settings with the default values
  5. Settings → App versions: add your Google Play and App Store links once the apps are live. Keep the minimum version at 1.0.0 until a release needs everyone on it.

    Settings → App versions
    Figure 8: Settings → App versions

Then add your keys in Maps, SMS & Sign-in, Firebase Configuration and Payment Configuration (DEPLOYMENT.md, section 5).

Demo credentials

Account Login Secret
Super Admin admin@ridex.test password (set ADMIN_EMAIL / ADMIN_PASSWORD in .env before seeding)
Operations / Finance / Support admins operations@ridex.test, finance@ridex.test, support@ridex.test password
Rider (app) +1 5550000001 OTP 123456 when RIDEX_DEMO_MODE=true
Driver (app) +1 5550000002 OTP 123456; approved, online in Manhattan, Economy vehicle
Driver (app) +1 5550000003 OTP 123456; application under review with sample documents, for trying the admin KYC flow

Demo numbers are listed in RIDEX_DEMO_PHONES. They accept RIDEX_DEMO_OTP without sending an SMS; always call /auth/otp/send before /auth/otp/verify. The apps show one box per code digit (OTP_LENGTH), so keep RIDEX_DEMO_OTP that long. With Firebase Phone Auth, the apps verify every number through Firebase, so add the demo numbers and code under Phone numbers for testing in the Firebase console (Authentication → Sign-in method → Phone).

With APP_ENV=production (the default), the demo riders and drivers are seeded only when RIDEX_DEMO_MODE=true, and the Operations / Finance / Support admins are never seeded.

White-labeling

Configuration is resolved in this order: Admin → Settings (the settings table), then .env, then config/ridex.php.

  • App name, colors, support contacts: RIDEX_* in .env, or edit them in the admin panel.
  • Logo and favicon: replace public/images/logo.png, favicon.png and public/favicon.ico, or upload them in Admin → Settings → General. The vector original is resources/images/brand/logo.svg.
  • Vehicle icons: the seeder copies resources/images/vehicle-types/{slug}.png to the uploads disk (next to their .svg originals), and each type's map marker from resources/images/vehicle-types/map/{slug}.png. Markers are seen from above with the front at the top, 72 × 144 px on a transparent background; the apps turn them with the driver. Replace either before installing, or upload new ones in Admin → Vehicle types.
  • Currency: RIDEX_CURRENCY, RIDEX_CURRENCY_SYMBOL, RIDEX_CURRENCY_POSITION.
  • Both apps call GET /api/v1/config on launch, so branding changes reach them without a new app build.

In code, read a value with setting('branding.app_name'), which is cached and falls back to config automatically.

Adding a language

English, Arabic (right-to-left), Spanish, French and Bengali are translated everywhere: both apps, API messages, notifications and emails. To add another, for example German:

  1. Admin → Languages → Add language: code de (or pt_BR for a regional variant), the English and native names, and the text direction.
  2. Backend texts (API messages, pushes, emails): copy lang/es.json to lang/de.json and lang/es/ to lang/de/, then translate the values. Keep every :placeholder unchanged. php artisan test --filter=LanguageFilesTest fails while a key or placeholder is missing.
  3. App texts: add a _de map with the same keys as _en to packages/ridex_core/lib/src/localization/core_translations.dart (in both apps: the two copies stay identical), ridex_rider/lib/localization/rider_translations.dart and ridex_driver/lib/localization/driver_translations.dart, and register it next to the others ('de': _de). Keep every @placeholder. flutter test fails while one is missing. Country names in the phone number's country list stay in English until you add the language's code to PhoneCountry.languages and its name to every country in packages/ridex_core/lib/src/localization/phone_countries.dart. Then release new app builds.
  4. Translate your pages, FAQs, quick messages and cancellation reasons in Content (CMS).

Until new app builds are out, the apps show a new language with English app texts, while API messages and content already appear translated.

Architecture

Path Purpose
app/Enums Backed enums for every status/type. DB columns are strings; models cast to enums. RideStatus owns the ride state machine.
app/Models 46 Eloquent models with relationships, scopes and domain helpers.
app/Http/Controllers/Api/V1/{Auth,Common,Rider,Driver,Webhook} Mobile API controllers.
app/Http/Middleware user.type:{rider|driver}, driver.approved, API locale.
app/Http/Traits/ApiResponse.php Uniform JSON envelope {success, message, data, meta, errors, error_code}.
app/Services Dispatch, Pricing, Wallet, Notification services (steps 4–9).
config/ridex.php Platform defaults (branding, dispatch, surge, wallet, OTP).
routes/api.php The complete v1 API map (116 routes).

Key design decisions

  • One users table for riders and drivers (user_type). A driver also has a drivers profile row. The same phone number can hold one rider and one driver account.
  • Admins are separate (admins table, admin session guard) with role-based permissions. Permission slugs such as withdrawals.approve are Gate abilities.
  • Zones are JSON polygons with indexed bounding-box columns: an SQL pre-filter, then an exact ray-casting test in PHP. This is portable across MySQL, MariaDB and SQLite. Airport zones win over city zones by priority.
  • Dispatch lookups: the Driver::available(), withinBoundingBox() and selectDistanceFrom() scopes combine an indexed box query with a Haversine sort.
  • Fare snapshots: rides.fare_breakdown (JSON) freezes the full calculation, so later tariff changes never alter past rides.
  • Wallet ledger: wallet_transactions is append-only and stores balance_before/balance_after for every entry.
  • Secrets: payment, SMS and map server keys in settings are encrypted at rest (is_encrypted).

API

Base URL: {APP_URL}/api/v1. Authenticate with Authorization: Bearer {sanctum-token}, and set the language with X-Locale: ar.

API reference: import docs/api/RideX.postman_collection.json into Postman. It lists every endpoint with its fields, rules and example bodies that work against the demo data. It's generated from the routes and their validation, so regenerate it after changing either:

php artisan ridex:api-collection

Authentication (both apps)

Every login method returns the same payload:

{ "success": true, "data": { "token": "1|…", "token_type": "Bearer", "is_new_user": true, "user": { "…": "…", "is_profile_complete": false } } }
Method Flow
Server OTP (OTP_PROVIDER=twilio or log) POST /auth/otp/send {country_code, phone}, then POST /auth/otp/verify {country_code, phone, code, user_type}
Firebase Phone Auth (OTP_PROVIDER=firebase) The app verifies the phone with Firebase, then calls POST /auth/firebase {id_token, country_code, phone, user_type}
Google / Apple POST /auth/social {provider, id_token, user_type, name?}
  • user_type is rider or driver. The same phone number may hold one account of each type.
  • Every login accepts optional device fields: fcm_token, device_type, device_name, app_version. It also accepts referral_code, which is only used on sign-up.
  • The apps read settings.auth.otp_provider from GET /config to choose the phone flow, and settings.auth.otp_length (OTP_LENGTH, 6 by default) to show one box per digit of a texted code. Firebase codes always have 6 digits.
  • Phone number fields start on localization.default_country (an ISO code such as BD, set in Admin → Settings → Localization & currency) with its calling code, localization.default_country_code. Users can pick any other country.
  • Google and Apple sign-ups have no phone number, so is_profile_complete stays false until they call POST /profile/phone with an OTP (purpose=change_phone) or a Firebase token. The apps ask for the number right after such a sign-up.
  • Google and Apple sign-in only work while switched on in Admin → Maps, SMS & Sign-in (social_disabled otherwise). GET /config returns the switches as settings.auth.social_google_enabled and social_apple_enabled, and the apps show only those buttons.
  • With OTP_PROVIDER=log, APP_DEBUG=true and an APP_ENV other than production, /auth/otp/send returns debug_code, and the SMS text is written to storage/logs/laravel.log.
  • Drivers are limited to one device (DRIVER_SINGLE_DEVICE=true): a new login revokes older tokens.
  • PUT /profile/language {language} saves the app language, used for that user's pushes, emails and API messages.
  • DELETE /auth/account deletes the account (required by the App Store), in the app under Settings → Delete account. It is refused during a ride (active_ride). The name, phone, email, photo, saved places, emergency contacts and saved cards are removed; trips and payments stay, without personal data, for the accounts.

OTP security:

  • Codes are stored hashed and expire after OTP_EXPIRY_MINUTES.
  • Each code allows OTP_MAX_ATTEMPTS wrong tries.
  • Requesting a new code invalidates the old one.
  • Rate limits: 3 sends per minute per phone and 20 per hour per IP; 10 verifies per minute per phone.

Social tokens are verified server-side against the issuer's public keys (signature, expiry, issuer, audience).

  • Configure FIREBASE_PROJECT_ID, GOOGLE_CLIENT_IDS and APPLE_CLIENT_IDS.
  • The Google and Apple values can list several client IDs (Android, iOS, web).

Driver onboarding (KYC)

  1. GET /driver/onboarding returns the live checklist: profile, documents and vehicle, each with a status.
  2. POST /driver/onboarding/profile saves the name, date of birth (18+) and city.
  3. POST /driver/documents uploads a file (multipart). GET /driver/documents/requirements describes every document type.
  4. POST /driver/vehicles, then upload its documents with vehicle_id.
  5. POST /driver/onboarding/submit moves the application to under_review.

Rejected or expiring documents are replaced with POST /driver/documents/{id}/reupload. The old file is soft-deleted, so admins keep the history.

KYC files are stored on the private disk. The API only returns signed URLs that expire after RIDEX_PRIVATE_URL_TTL minutes and are served under /private-files/….

Booking & dispatch

  1. The rider calls POST /rider/fare-estimate, then POST /rider/rides with the chosen quote_id, payment_method and addresses. The quote fixes the price (upfront pricing).
  2. DispatchService offers the ride to drivers. Search rounds widen the radius (dispatch.* settings).
    • Sequential mode offers the ride to one driver at a time; broadcast mode offers a batch, and the first to accept wins.
    • Candidate order: nearest first; among drivers about equally close, the longest idle goes first.
    • Excluded: offline or busy drivers, stale GPS, an unapproved vehicle or wrong vehicle type, and drivers whose cash debt is below wallet.driver_min_balance.
    • Destination mode: a driver who set a destination (PUT /driver/destination, e.g. home) only gets rides that end closer to it than they are now; rentals, which have no drop-off, are skipped. It ends when they clear it, go offline or finish a trip within 1 km of it, and counts against dispatch.destination_daily_limit.
  3. The driver app reads GET /driver/ride-requests/current (it includes a countdown), then calls …/accept or …/reject. Accepting is race-safe: only one driver can win.
  4. Trip flow: trips/{uuid}/arrived (the driver must be within ride.arrival_radius_meters) → start with the rider's PIN (ride.otp_length digits, 4 by default; the driver app reads it as settings.ride.otp_length from GET /config and shows one box per digit) → stops/{id}/arrived → complete with distance_km and optional tolls → collect-cash.
  5. Final fare:
    • The upfront trip price is kept while the actual distance stays within pricing.upfront_tolerance_percent of the estimate. Waiting time and tolls are always added.
    • Outside that tolerance, the fare is recalculated from actual distance and time, using the surge multiplier locked at booking.
    • Reported distance is capped at the estimate × pricing.max_reported_distance_factor.
  6. Cancellation:
    • Rider: free before a driver is assigned and during free_cancellation_minutes. After that, or once the driver has arrived, the plan's cancellation fee applies.
    • Driver dropping out: the ride is dispatched again. Drivers who were already offered it are never offered it again.
    • Rider no-show: the driver can report it only after the plan's free waiting minutes. The rider is then charged the fee.
  7. Rides that find no driver end as no_driver_found. Every status change is recorded in ride_status_logs and fires RideStatusChanged.

Ride types (ride_type on the estimate); each one can be switched off in Admin → Settings → Rides & pricing:

  • instant: a ride now.
  • scheduled: scheduled_at at least ride.scheduled_min_minutes_ahead minutes and at most ride.scheduled_max_days_ahead days ahead. The ride waits as scheduled and is released to dispatch dispatch.scheduled_lead_minutes before pickup. Until a driver accepts, the rider can cancel it for free.
  • rental: rental_hours from GET /rider/rental-packages?lat&lng (the packages on the pickup zone's fare plans); the drop-off is optional. Kilometres and minutes beyond the package are billed at its extra rates.
  • outstation: an intercity trip, one way or is_round_trip with outstation_days. Round trips bill both ways with a daily minimum distance, plus the plan's driver allowance per day and night charge.

Booking also accepts pickup_note (a gate or landmark, shown to the driver) and, while features.book_for_others is on, passenger_name and passenger_phone for someone else's ride: the driver sees the passenger and calls them. Airport pickups and drop-offs (zone.is_airport / drop_zone.is_airport in the estimate) include the zone's airport fee, and the rider app says so before booking.

Admin → Rides shows every ride's timeline and full dispatch log (each offer, the driver, the distance and the response time). Operations admins can also cancel rides there.

Real-time tracking

  • Driver status: POST /driver/status {online}. Going online re-checks every document for expiry, the vehicle's approval and the cash-debt limit, and each online session is recorded.
  • Driver location: POST /driver/location, sent every tracking.location_interval_seconds.
    • Accepts one {lat, lng, heading} or an offline-buffered points[].
    • Mock locations (is_mock) are rejected and counted on the driver (gps_spoof_flags). Simulators and emulators always report mocked GPS, so set TRACKING_BLOCK_MOCK_LOCATIONS=false only while testing on them.
    • Jumps faster than tracking.max_speed_kmh are dropped.
  • Trip distance: during a trip the points form a GPS trail. At completion, the server-measured trail distance is billed. The driver-reported distance is used only when the trail has fewer than tracking.min_trail_points points.
  • Live updates (Firebase Realtime Database): the server is the only writer.
    • Configure FIREBASE_DATABASE_URL, and upload the service-account key in Admin → Firebase Configuration (or place the JSON at FIREBASE_CREDENTIALS).
    • Deploy firebase/database.rules.json (the apps can only read their own nodes) with Deploy rules in Admin → Firebase Configuration, or the Firebase CLI. Until then Firebase refuses the apps' listeners and they poll.
    • The apps call POST /realtime/token, sign in with the custom token, and listen to rides/{uuid} and, for drivers, drivers/{uid}/offers.
    • Booking and accepting write rides/{uuid} before they respond, so the new participant's app can listen straight away. Every other change is published by the queue worker.
    • If Firebase is not configured, nothing is published, and the apps keep polling GET /rider/rides/active and GET /driver/ride-requests/current.
  • Live trip sharing: POST /rider/rides/{uuid}/share returns a public /trip/{token} page, a live OpenStreetMap view. It stops working when the ride ends.
  • Admin → Live Map shows available, on-trip and no-GPS drivers, plus active rides and those waiting for a driver. It refreshes every 10 seconds, with a countdown to the next update. If an update fails (for example, the admin session expired), the page says so and keeps retrying.

Wallet & payments

  • Wallet: GET /wallet (balance, dues) and GET /wallet/transactions. The ledger is append-only; a negative balance is money owed.
    • Riders: a negative balance (unpaid cancellation fees) blocks new bookings with outstanding_dues, and so does a completed card/online ride that was never paid (unpaid_ride, with data.ride_uuid).
    • Drivers: cash rides charge the platform's share to the wallet. Below wallet.driver_min_balance, dispatch skips the driver and going online is refused. GET /driver/home returns wallet.balance and wallet.min_balance so the app can warn early.
  • Payment methods: GET /payments/methods returns only what is switched on in Admin → Payment Configuration, with the wallet balance, saved cards, online gateways, top-up gateways, top-up limits and tip settings. Booking validates the method against the same rules.
  • Hosted checkout (every gateway works the same way):
    1. Start a payment: POST /payments/wallet/top-up {amount, gateway}, POST /rider/payments/rides/{uuid}/pay {gateway} or POST /rider/rides/{uuid}/tip {amount, method, gateway}.
    2. The app opens checkout_url in a web view and closes it when the page reaches return_url.
    3. POST /payments/{uuid}/confirm asks the gateway for the outcome. The gateway's answer is the only source of truth; webhooks trigger the same check.
    4. Money moves exactly once (row lock + idempotent ledger entries). A second payment for an already paid ride becomes wallet credit.
  • Settlement: cash rides charge commission to the driver; wallet rides are paid from the rider's wallet at completion; card rides are charged to the default saved card right after the trip (the rider pays on the hosted page if it is declined); online rides are paid by the rider after the trip.
  • Top-ups: riders can top up while the wallet method is on. Drivers (settling commission) and anyone who owes money can always top up.
  • Saved cards (Stripe): GET/POST/DELETE /rider/saved-cards, POST /rider/saved-cards/{id}/default. Adding a card opens Stripe's page; only the brand, last digits and expiry are stored here.
  • Receipts: GET /rider/rides/{uuid}/receipt. PUT /rider/rides/{uuid}/payment-method changes the method before the trip, or settles an unpaid card/online ride from the wallet after it.

Earnings, payouts & incentives (driver)

  • GET /driver/earnings?period=today|week|month: gross, commission, net, tips, bonuses, cash collected, online time and a per-day breakdown, all in the driver's local time zone. GET /driver/earnings/trips lists completed trips.
  • GET /driver/withdrawals: balance, minimum withdrawal, masked payout details, the open request and recent history. PUT /driver/payout-details (bank transfer, mobile money or PayPal; stored encrypted). POST /driver/withdrawals {amount} holds the amount until an admin pays or rejects it.
  • GET /driver/incentives: running targets with this period's progress. Progress is recomputed from the source data, and a reached target is paid once to the wallet.

Referrals & promotions

  • GET /referrals (riders and drivers): the code to share, both rewards, the total earned and who joined. Both sides are paid when the referred user completes their first ride or trip.
  • GET /rider/promo-codes?lat&lng lists the offers this rider can use at a pickup point; POST /rider/promo-codes/validate {code} checks one before booking.

Notifications

  • Every notification is stored in the in-app inbox: GET /notifications (meta unread_count), GET /notifications/unread-count, POST /notifications/{id}/read, POST /notifications/read-all.
  • Push (FCM HTTP v1) uses the service-account key from Admin → Firebase Configuration. The apps register their token with POST /auth/fcm-token. Pushes are queued, and a token that Firebase reports as unregistered is removed.
  • PUT /profile/notification-preferences {preferences: {ride_updates, chat, payments, promotions, news}} mutes push categories. Safety and account alerts are always sent, and muted notifications still reach the inbox.
  • iOS pushes also need an APNs key uploaded in the Firebase console (Project settings → Cloud Messaging).
  • Email: users with an email address also get a short email for a completed trip (with the fare), payments, withdrawals, account status changes, driver application decisions and support replies. They can turn this off with Emails in the app's settings (the email preference). Emails are queued, sent in the user's language with the platform name from Admin → Settings, and use the MAIL_* settings in .env (see DEPLOYMENT.md). With MAIL_MAILER=log, they are only written to the log.
  • Force update: GET /config returns each app's minimum version and store links (settings.app.rider_min_version, rider_store_url_android, rider_store_url_ios, and the same for driver_). An older build stops at launch and offers Update now.

Safety, chat & ratings

  • SOS: POST /sos {ride_uuid?, lat?, lng?} (riders and drivers) opens an alert in Admin → SOS Alerts and texts the user's emergency contacts a live trip link (or a map link). Pressing again within SAFETY_SOS_REPEAT_MINUTES updates the open alert instead of texting everyone again.
  • Emergency contacts: GET/POST/PUT/DELETE /rider/emergency-contacts, up to SAFETY_MAX_EMERGENCY_CONTACTS. Contacts with auto_share_trips get the live trip link by SMS whenever a trip starts.
  • SMS goes out from the queue through Twilio when TWILIO_* is set; otherwise the text is written to the log.
  • In-ride chat: GET /rides/{uuid}/chat?after_id=, POST /rides/{uuid}/chat {message, type}, POST /rides/{uuid}/chat/read. It is open while the driver is on the way or driving. The receiver gets a push, and the ride's Firebase node is touched so the other app re-reads the ride (unread_messages). Quick replies come from GET /content/quick-messages?app=.
  • Ratings: POST /rider/rides/{uuid}/rate and POST /driver/trips/{uuid}/rate {rating, review?, tags[]}, once per ride after completion. The rated user's average is recomputed from all their ratings. Compliment tags come from GET /content/rating-tags?app=rider|driver (config/ridex.php → ratings.tags). Rides include my_rating.

Support & Lost and Found

  • Tickets: GET/POST /support-tickets, GET /support-tickets/{id}, POST /support-tickets/{id}/reply. Fare disputes need ride_uuid (optional disputed_amount); safety reports are urgent. A support reply notifies the user; a user reply puts the ticket back in the queue; closed tickets take no replies.
  • Lost items: POST /rider/rides/{uuid}/lost-items {item_name, description?, contact_phone?} within LOST_ITEMS_REPORT_DAYS of a completed trip, and GET /rider/lost-items. The driver sees them at GET /driver/lost-items and answers with PUT /driver/lost-items/{id} {status: found|returned|not_found, response?}. The rider is notified of every change.

Tests

php artisan test

The suite runs on in-memory SQLite. It covers OTP login and its abuse cases, Firebase and social login, ID-token cryptography (forged, expired, wrong-audience and rotated keys), profile management, account deletion, the full driver KYC journey, pricing, dispatch, the trip flow, real-time tracking, the wallet ledger and ride settlement, every gateway's checkout/status/webhook handling, the demo payment flow end to end, withdrawals, incentives, referrals, the admin finance and payment configuration pages, push notifications, SOS and emergency contacts, trip sharing by SMS, chat, ratings, support tickets, lost items, the support admin pages, admins and roles (including privilege-escalation attempts), the activity log, reports and their CSV/Excel/PDF downloads, zones, promo codes, the CMS with its HTML cleaning, the settings pages, driver performance, push campaigns, emails, the Languages page and every translation file (no missing keys or placeholders).

Credits

  • Laravel, Laravel Sanctum and Laravel Tinker (MIT); firebase/php-jwt (BSD-3-Clause); Heroicons via blade-heroicons (MIT)
  • Tailwind CSS, Alpine.js, Chart.js and the Trix editor (MIT); Leaflet (BSD-2-Clause)
  • OpenSpout (MIT) for Excel exports; dompdf (LGPL-2.1, with php-font-lib LGPL-2.1 and php-svg-lib LGPL-3.0) via barryvdh/laravel-dompdf (MIT) for PDF exports
  • Map data © OpenStreetMap contributors (ODbL)
  • Inter font (SIL Open Font License 1.1, served by Bunny Fonts)
  • Flutter packages: GetX, Dio, get_storage, geolocator and sign_in_with_apple (MIT); google_maps_flutter, FlutterFire, google_sign_in, package_info_plus, webview_flutter, url_launcher and intl (BSD-3-Clause); image_picker (Apache-2.0); Material Icons (Apache-2.0)

Every package with its version and license, plus notes on the LGPL and dual-licensed ones, is listed in Credits & licenses.

Deployment

This guide takes the backend from a fresh server to a live platform. Read README.md first for requirements, local installation and the Going live checklist.

A live RideX backend needs four things running:

  1. A web server whose document root is ridex_backend/public.
  2. PHP 8.4.1+ with the extensions listed in the README, plus MySQL 8+ or MariaDB 10.4+.
  3. A cron entry for the scheduler (every minute).
  4. A queue worker (Supervisor on a VPS, or the cron fallback on shared hosting).

1. Install on a VPS

cd /var/www
# upload or clone ridex_backend here, then:
cd ridex_backend
composer install --no-dev --optimize-autoloader   # the PHP packages (vendor/)
npm ci && npm run build                           # the admin panel's CSS and JS (public/build/)
cp .env.example .env                              # then edit APP_URL, DB_*, TWILIO_*, FIREBASE_* …
php artisan key:generate
php artisan migrate --seed --force
php artisan storage:link
php artisan optimize

The package comes without vendor/ and public/build/: the first two commands create them. Node.js 20.19+ (with npm) is only needed for the build. Without it on the server, run npm ci && npm run build on your computer and upload public/build/.

Permissions: the web server user (usually www-data) must be able to write to storage/ and bootstrap/cache/.

sudo chown -R www-data:www-data storage bootstrap/cache
sudo chmod -R ug+rwX storage bootstrap/cache

Uploads: drivers send up to two photos per document (RIDEX_UPLOAD_MAX_KB, 5 MB each by default). Set PHP's upload_max_filesize to at least 8M and post_max_size to at least 12M.

2. Web server

Apache

public/.htaccess is included. Enable mod_rewrite, point the virtual host's DocumentRoot at ridex_backend/public and allow the override:

<VirtualHost *:80>
    ServerName api.example.com
    DocumentRoot /var/www/ridex_backend/public
    <Directory /var/www/ridex_backend/public>
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

Nginx

server {
    listen 80;
    server_name api.example.com;
    root /var/www/ridex_backend/public;
    index index.php;
    charset utf-8;
    client_max_body_size 12M;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    error_page 404 /index.php;

    location ~ ^/index\.php(/|$) {
        fastcgi_pass unix:/var/run/php/php8.4-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_hide_header X-Powered-By;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

Then add HTTPS (for example with Certbot) and set APP_URL=https://api.example.com and SESSION_SECURE_COOKIE=true. The apps, payment pages and trip-share links are all built from APP_URL.

Behind a load balancer or CDN (Cloudflare, AWS ELB, a reverse proxy), set TRUSTED_PROXIES to * or to the proxy's IPs / CIDR ranges. Otherwise every visitor appears to come from the proxy's IP, so the per-IP limits (for example 20 sign-in codes per IP per hour) apply to all users together, and https links can fail their signature check.

App sign-in lifetime: the apps' tokens expire after API_TOKEN_EXPIRATION_DAYS (90 by default, 0 = never). Users then sign in again, and expired tokens are pruned daily by the scheduler.

3. Shared hosting (cPanel)

  1. In Select PHP Version (or MultiPHP Manager), choose PHP 8.4 and enable the required extensions.
  2. On your computer (or in cPanel's Terminal), run composer install --no-dev --optimize-autoloader and npm ci && npm run build in ridex_backend/. The package comes without vendor/ and public/build/; these commands create them.
  3. Upload ridex_backend/, with its new vendor/ and public/build/ folders, next to public_html, not inside it, so .env and storage/ are never reachable from the web.
  4. Point the domain or subdomain's Document Root at ridex_backend/public (Domains → Manage). Use a subdomain such as api.example.com if the main domain's root can't be changed.
  5. Create a MySQL database and user (MySQL Databases), and give the user all privileges on it.
  6. Open https://your-domain/install. The web installer checks the server, then asks for the platform name and URL, the database and the super admin. It migrates, seeds, creates public/storage and writes .env, then switches itself off.
    • If it reports that public/storage couldn't be created (some hosts disable symlinks), ask your host to link public/storage to storage/app/public.
    • With Terminal (or SSH) you can run the commands from section 1 instead. The Terminal PHP may differ from the web PHP. Use the 8.4 binary your host gives, for example /opt/cpanel/ea-php84/root/usr/bin/php artisan migrate --seed --force.
  7. Add the cron entries from section 4 under Cron Jobs, using the PHP 8.4 binary.

The installer's screens, in order. Its last page also lists the cron job and queue worker for your server (section 4).

The installer's server check: the PHP version, the extensions RideX needs and the folders it writes to
Figure 9: The installer's server check: the PHP version, the extensions RideX needs and the folders it writes to
Platform and Database: the platform's name and URL, and the database and user from step 5
Figure 10: Platform and Database: the platform's name and URL, and the database and user from step 5
Super admin: the first admin account, and Demo mode, which stays off on a live platform
Figure 11: Super admin: the first admin account, and Demo mode, which stays off on a live platform
The installer's last page, with the link to the admin panel
Figure 12: The installer's last page, with the link to the admin panel

4. Scheduler and queue worker

Scheduler (every server):

* * * * * cd /var/www/ridex_backend && php artisan schedule:run >> /dev/null 2>&1

Queue worker. Dispatch offers, offer timeouts, search retries, push notifications, live ride updates, SMS and card charges all run on the queue. Without a worker, rides never reach drivers, and riders and drivers don't see each other's actions. Keep the queue order dispatch,notifications,default: ride pushes and live ride updates use the notifications queue, so driver locations and push campaigns on default never hold them up.

On a VPS, keep it running with Supervisor (/etc/supervisor/conf.d/ridex-worker.conf):

[program:ridex-worker]
command=php /var/www/ridex_backend/artisan queue:work --queue=dispatch,notifications,default --sleep=1 --max-time=3600
user=www-data
numprocs=1
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
stopwaitsecs=3600
redirect_stderr=true
stdout_logfile=/var/www/ridex_backend/storage/logs/worker.log
sudo supervisorctl reread && sudo supervisorctl update && sudo supervisorctl start ridex-worker:*

On shared hosting without Supervisor, add a second cron entry that drains the queue every minute:

* * * * * cd /home/USER/ridex_backend && php artisan queue:work --queue=dispatch,notifications,default --stop-when-empty --max-time=50 >> /dev/null 2>&1

This works, but queued work can wait up to a minute: offers expire late and dispatch feels slower. Use a VPS for a busy platform.

After every deployment, run php artisan queue:restart so the worker loads the new code.

5. Third-party services

These are external providers. Google Maps, Firebase, Twilio, the payment gateways and the other services below are run by other companies, not by RideX. You sign up with each one yourself and accept its terms, and you're responsible for any fees it charges for your usage, such as Maps requests, SMS messages or payment processing.

Google Maps

RideX uses three keys. Create them in Google Cloud Console → APIs & Services → Credentials, and restrict each one.

Key Where it goes Enable these APIs Restrict it to
Server key Admin → Maps, SMS & Sign-in, or GOOGLE_MAPS_SERVER_KEY in .env Routes API, Places API (New), Geocoding API Your server's IP address, and those three APIs
Android key android/secrets.properties in each app Maps SDK for Android Android apps: package name + SHA-1 of your upload key and of Play's app signing key (Play Console → App integrity)
iOS key ios/Flutter/Secrets.xcconfig in each app Maps SDK for iOS iOS apps: the bundle IDs

The app keys can't be set in the admin panel: Google reads them only from the app builds, so they go in secrets.properties and Secrets.xcconfig, and a new key needs new builds.

Admin → Maps, SMS & Sign-in: the Google Maps server key, the map tiles and the sign-in switches; the Twilio settings follow further down
Figure 13: Admin → Maps, SMS & Sign-in: the Google Maps server key, the map tiles and the sign-in switches; the Twilio settings follow further down

Enable Places API (New), not the one called just "Places API": that is Google's legacy version, which RideX doesn't use and new projects can't enable.

Without a server key, or when a Routes API call fails, routes and fares use the straight-line estimate (MAPS_FALLBACK_*) and place search is empty. Reverse geocoding (the "Current location" address) only needs the Geocoding API.

Map tiles (admin live map and trip-share page)

These pages use Leaflet with OpenStreetMap tiles by default. OpenStreetMap's own tile servers are for light use only, so a live platform should use a tile provider such as MapTiler, Stadia Maps or Thunderforest:

MAPS_TILE_URL=https://…/{z}/{x}/{y}.png?key=YOUR_KEY           # the provider's raster XYZ URL
MAPS_TILE_ATTRIBUTION='&copy; Provider &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'

Keep the attribution your provider requires. Run php artisan optimize after changing .env.

Firebase (push notifications and live tracking)

Firebase is optional: the apps also build and run without it. They then poll for ride updates, and notifications stay in the in-app inbox. Push notifications and instant updates start once each app has its Firebase configuration files (see the Mobile app setup guide) and the server key from step 5 is uploaded.

  1. Create a project in the Firebase console.
  2. Add an Android app and an iOS app for each RideX app, using your own package names and bundle IDs. Put the downloaded google-services.json in android/app/ and GoogleService-Info.plist in ios/Runner/ of that app.
  3. Build → Realtime Database → Create database. Choose Start in locked mode. Copy its URL to FIREBASE_DATABASE_URL and the project ID to FIREBASE_PROJECT_ID.
  4. Build → Authentication → Get started. Live tracking signs the apps in with custom tokens from the backend, so it needs no sign-in provider. Phone codes and Google sign-in (below) each need theirs.
  5. Project settings → Service accounts → Generate new private key. Upload the JSON file in Admin → Firebase Configuration (Super Admin), then press Deploy rules. The rules (firebase/database.rules.json) let a signed-in rider or driver read only their own rides and ride offers, and let no app write: only the backend writes, with the server key. If Realtime Database → Rules ever shows ".read": true or ".write": true, press Deploy rules again, because open rules let anyone read every trip and change the data.
  6. iOS push: Project settings → Cloud Messaging → Apple app configuration, upload an APNs authentication key (needs a paid Apple developer account), then add the Push Notifications capability in Xcode.
  7. Optional: App Check, so Firebase accepts database and sign-in requests only from your own apps (see Firebase App Check (optional) below).
Admin → Firebase Configuration before a key is uploaded: upload the service-account JSON file on the right
Figure 14: Admin → Firebase Configuration before a key is uploaded: upload the service-account JSON file on the right

Admin → Firebase Configuration → Connected apps shows how many phones registered for push, which confirms the setup works end to end.

Firebase App Check (optional)

App Check lets Firebase accept Realtime Database and sign-in requests only from your genuine apps. Both apps turn it on at start-up: release builds attest with Play Integrity on Android and App Attest on iOS (DeviceCheck before iOS 14), and debug builds use a debug token. There's nothing to set on the server, and nothing is blocked until you enforce it.

  1. Android: in the Google Play Console, open each app, go to Release → App integrity → Play Integrity API and click Link Cloud project to link your Firebase project. In Firebase, add the SHA-256 fingerprint of Google Play's app signing key to each Android app (Project settings → Your apps), then register both apps under Security → App Check → Apps with Play Integrity.
  2. iOS (paid Apple Developer team): in Xcode, add the App Attest capability to each app and set App Attest Environment to production in Runner/Runner.entitlements. Create a DeviceCheck key in your Apple Developer account (Certificates, Identifiers & Profiles → Keys → +), enter your Team ID in each iOS app's Firebase settings, then register both iOS apps with App Attest and with DeviceCheck (the .p8 key and its Key ID).
  3. Debug builds: add a debug token (for example from uuidgen) under Security → App Check → Apps → ⋮ → Manage debug tokens, and run the apps with --dart-define=APP_CHECK_DEBUG_TOKEN=YOUR_DEBUG_TOKEN. Never pass it to a release build.
  4. Enforce: under Security → App Check → APIs, enforce Realtime Database and Authentication once the metrics show your store builds' requests as verified. It takes up to 15 minutes. Then book a test ride: the rider's trip screen must still update instantly, and a new offer must reach an online driver at once.

An app that App Check rejects still signs in to the backend and polls for updates. Only Firebase phone sign-in codes (see Sign-in codes with Firebase Phone Auth below) stop working once Authentication is enforced. The Xcode screens and more detail are in section 10.3 of the Mobile app setup guide.

SMS (Twilio)

Login codes (OTP_PROVIDER=twilio), SOS alerts and trip-sharing texts are sent through Twilio.

  1. In the Twilio Console, copy the Account SID and Auth Token.
  2. Buy a phone number with SMS capability (or use an approved sender ID) and set it as TWILIO_FROM in E.164 format, for example +15551234567.
  3. Set TWILIO_SID, TWILIO_AUTH_TOKEN and TWILIO_FROM in .env, then run php artisan optimize.

Trial accounts only deliver to verified numbers. Some countries require sender registration (for example A2P 10DLC for US numbers) before texts are delivered.

Without Twilio, a live server (APP_ENV=production) sends no SOS or trip-sharing texts: the SOS screen then tells the user only the safety team was alerted, and nothing about the text is written to the log. Outside production, texts are written to storage/logs/laravel.log for testing.

Sign-in codes with Firebase Phone Auth (optional)

Instead of your Twilio account, Firebase can text the sign-in codes. The apps then send the backend a Firebase ID token that proves the number, so both apps need their Firebase files (see Firebase above). Firebase charges per text in most countries, so check its pricing first.

  1. Firebase console → Authentication → Sign-in method → Phone → Enable.
  2. Android: add the SHA-1 and SHA-256 fingerprints of your debug key, upload key and Play's app signing key (Play Console → App integrity) to each Android app (Project settings → Your apps), then download google-services.json again.
  3. iOS: Firebase checks the app with a silent push, which needs the APNs key from the Firebase section. Without it, Firebase shows a reCAPTCHA page that returns to the app through a URL scheme. In ios/Flutter/Secrets.xcconfig of each app, set FIREBASE_ENCODED_APP_ID to the iOS app's Encoded App ID (Project settings → Your apps).
  4. In Admin → Maps, SMS & Sign-in, set Send sign-in codes with to Firebase Phone Auth, or set OTP_PROVIDER=firebase before installing.
  5. For testing and App Store review, add fictional numbers with fixed codes under Phone numbers for testing on the same Firebase page. The demo accounts need this too.

SOS alerts and trip-sharing texts still go through Twilio.

Sign in with Google and Apple

Both are optional and switched off by default. The apps show a button as soon as it is switched on in Admin → Maps, SMS & Sign-in, so finish the set-up first. After a Google or Apple sign-up, the app asks for a phone number and verifies it with a code.

Google (uses the Firebase project from above):

  1. Firebase console → Authentication → Sign-in method → Google → Enable. This creates the OAuth clients.
  2. Android: add the SHA-1 fingerprints of your debug key, upload key and Play's app signing key to each Android app (Project settings → Your apps), then download google-services.json again. The file must contain a web client ("client_type": 3); the app asks Google for an ID token for it.
  3. iOS: download GoogleService-Info.plist again. In ios/Flutter/Secrets.xcconfig of each app, set GOOGLE_IOS_CLIENT_ID to its CLIENT_ID and GOOGLE_REVERSED_CLIENT_ID to its REVERSED_CLIENT_ID.
  4. Backend: set GOOGLE_CLIENT_IDS to the web client ID and each app's iOS client ID, comma-separated (Google Cloud Console → APIs & Services → Credentials). Android sign-ins carry the web client ID, iOS sign-ins the app's own.
  5. Switch on Sign in with Google.

Apple (iPhone only). Apple's App Review guideline 4.8 asks for Sign in with Apple, or a similar privacy-focused option, when an iOS app offers Google sign-in.

  1. In your Apple developer account (a paid team), enable Sign in with Apple for each app ID. In Xcode, add the Sign in with Apple capability (Runner → Signing & Capabilities).
  2. Backend: set APPLE_CLIENT_IDS to the apps' bundle IDs, comma-separated.
  3. Switch on Sign in with Apple.

Run php artisan optimize after changing .env. The backend checks every Google and Apple token's signature, issuer, expiry and audience, so a token issued to another app is refused.

Payment gateways

Enter keys in Admin → Payment Configuration (Super Admin). Values in .env are only the install-time defaults. Every gateway uses a hosted checkout page, and each card on that page shows its webhook URL:

{APP_URL}/api/v1/webhooks/payments/{stripe|razorpay|paypal|flutterwave}

Webhooks are optional, because the app also confirms a payment when the rider returns from the checkout page. They do catch payments whose rider closed the page early. Test with sandbox keys first, and check that the gateway supports your RIDEX_CURRENCY.

Admin → Payment Configuration: the payment methods riders are offered, wallet limits and tips, then one card per gateway
Figure 15: Admin → Payment Configuration: the payment methods riders are offered, wallet limits and tips, then one card per gateway
Gateway Keys (dashboard location) Webhook events
Stripe Publishable and secret key (Developers → API keys). Needed for card payments and saved cards. checkout.session.completed, checkout.session.async_payment_succeeded, checkout.session.async_payment_failed, checkout.session.expired. Paste the endpoint's signing secret (whsec_…) as Webhook signing secret.
Razorpay Key ID and key secret (Account & Settings → API Keys). Payments use Payment Links. payment_link.paid, payment_link.cancelled, payment_link.expired. The secret you type when adding the webhook is the Webhook secret.
PayPal Client ID and secret of a REST app (developer.paypal.com → Apps & Credentials). Sandbox and live apps have different credentials, so set Mode to match. CHECKOUT.ORDER.APPROVED, PAYMENT.CAPTURE.COMPLETED, PAYMENT.CAPTURE.DENIED. Copy the webhook's Webhook ID; without it PayPal webhooks are ignored.
Flutterwave Public and secret key (Settings → API Keys). charge.completed (the webhook URL under Settings → Webhooks). The Secret hash you set there goes in Webhook secret hash.

With RIDEX_DEMO_MODE=true, a Demo payment gateway appears so every flow can be tried without keys. Never leave it on in production.

Email

Riders and drivers who have an email address get short emails for a completed trip (with the fare), payments, withdrawals, account status changes, driver application decisions and support replies. Each user can turn them off with Emails in the app's settings. Emails go out through the queue worker, in the user's language.

Set your SMTP server or mail service in .env, then run php artisan optimize:

MAIL_MAILER=smtp
MAIL_HOST=smtp.yourprovider.com
MAIL_PORT=587
MAIL_USERNAME=your-username
MAIL_PASSWORD=your-password
MAIL_FROM_ADDRESS="no-reply@yourdomain.com"
MAIL_FROM_NAME="Your platform name"

With the default MAIL_MAILER=log, emails are only written to storage/logs/laravel.log. Set up SPF and DKIM for the sending domain (your mail provider explains how), or the emails may land in spam.

Certificate pinning (optional)

The apps always require HTTPS in release builds and trust only the phone's built-in certificate authorities. Pinning adds one more check: the app only talks to a server whose certificate carries one of the public keys you list, so even a wrongly issued certificate can't be used to read the traffic. The check runs before any data, including the sign-in token, is sent.

  1. Get the pin of the key your server uses now:

    openssl s_client -connect api.yourdomain.com:443 -servername api.yourdomain.com </dev/null 2>/dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
    
  2. Create a spare key for emergencies and get its pin too. Keep the key offline; you only need it if the current key is lost or leaked:

    openssl ecparam -name prime256v1 -genkey -noout -out backup-key.pem
    openssl pkey -in backup-key.pem -pubout -outform der | openssl dgst -sha256 -binary | openssl enc -base64
    
  3. Build both apps with the two pins: --dart-define=API_CERT_PINS=<current pin>,<backup pin>.

Keep the key when certificates renew, or every installed app stops connecting until users update. With Let's Encrypt, renew with certbot renew --reuse-key. To change keys, first publish app versions that include the new pin, wait until most users have updated, and only then switch the server. When the pins don't match, the apps show the "no connection" message. Leave API_CERT_PINS out if you can't commit to this; HTTPS alone still protects the traffic.

Pinning covers the RideX API. Google Maps, Firebase and the payment pages opened in the app use the phone's normal HTTPS checks.

Device security checks (optional)

Both apps can check the phone with freeRASP for root or jailbreak, hooking tools such as Frida, a modified or cloned app, an attached debugger and emulators. The apps never block anyone themselves: they send the findings with each request, and Admin → Settings → Safety & support → Compromised phones decides what happens:

  • Ignore – findings are not stored.
  • Flag (default) – findings show under Device security on the rider or driver profile, and each new finding is written to the activity log.
  • Restrict – also blocks drivers on a rooted, jailbroken, hooked, modified, cloned or emulated phone from going online, accepting rides and requesting payouts. Developer mode, USB debugging, an unlocked bootloader, sideloading and mock-location apps are only flagged, because many honest users with custom ROMs have them.

Without any set-up, release builds run a basic check with flutter_jailbreak_detection: root or jailbreak, plus developer options on Android and the iOS simulator. Its Android check (RootBeer) also counts ROMs signed with test keys as rooted, so some custom ROMs are reported as rooted too, and Restrict mode blocks those drivers. For the full checks and fewer false alarms, build the apps with the freeRASP values; the basic check then stays off:

flutter build appbundle --release --dart-define=API_BASE_URL=https://api.yourdomain.com \
  --dart-define=RASP_WATCHER_EMAIL=security@yourdomain.com \
  --dart-define=RASP_ANDROID_SIGNING_HASHES=<base64 SHA-256 of your signing certificate>
flutter build ipa --release --dart-define=API_BASE_URL=https://api.yourdomain.com \
  --dart-define=RASP_WATCHER_EMAIL=security@yourdomain.com \
  --dart-define=RASP_IOS_TEAM_ID=<your Apple Developer team ID>
  • Android signing hash: with Play App Signing, use the app signing key certificate's SHA-256 from Play Console → App integrity, not your upload key. freeRASP accepts it in hex (AB:CD:…) or base64; separate several with commas.
  • Watcher email: Talsec sends a weekly security report to this address.
  • Debug builds skip the debugger, emulator and signature checks, and never run the basic check, so development is unaffected.

freeRASP is free under Talsec's fair-usage policy and sends anonymous check results to Talsec; mention this in your privacy policy. The findings are reported by the app, so a skilled attacker can hide them: treat them as a fraud signal, not proof.

6. Updating

php artisan down
# replace the code, keeping .env and storage/
composer install --no-dev --optimize-autoloader   # the PHP packages (vendor/)
npm ci && npm run build                           # the admin panel's CSS and JS (public/build/)
php artisan migrate --force
php artisan optimize
php artisan queue:restart
php artisan up

CHANGELOG.md in the backend folder lists what changed in each version.

7. Troubleshooting

Start with storage/logs/laravel.log: it records every error with its cause.

Symptom Fix
"Composer detected issues in your platform: … PHP version >= 8.4.1" The web or CLI PHP is older than 8.4.1. Switch both to 8.4+.
HTTP 500 right after install, or "…/storage/logs/laravel.log could not be opened … Permission denied" Run php artisan key:generate, check the DB_* values and make storage/ and bootstrap/cache/ writable.
"SQLSTATE[HY000] [2002] Connection refused" or "[1045] Access denied for user" The DB_* values in .env don't match your database. Check DB_HOST (on shared hosting usually localhost), DB_PORT, DB_DATABASE, DB_USERNAME and DB_PASSWORD, then run php artisan optimize.
Every page says "One more step before installing" The Composer packages (vendor/) or the built admin assets (public/build/) are missing: a fresh download has neither. Run the commands the page lists in the backend folder, or on your computer and then upload the folder again.
"…/vendor/autoload.php: Failed to open stream" or Class "…" not found vendor/ is missing or incomplete. Run composer install --no-dev --optimize-autoloader in the backend folder, or run it on your computer and upload vendor/ again.
Admin panel has no styling, or "Vite manifest not found" The document root isn't public/, or public/build/ is missing. Point the root at public/, and run npm ci && npm run build (on your computer if the server has no Node.js, then upload public/build/).
"419 Page Expired" when you sign in to the admin panel The browser didn't keep the session cookie. With SESSION_SECURE_COOKIE=true the site must open over https://, and APP_URL and SESSION_DOMAIN (or null) must match the address you open. Run php artisan optimize, then reload the sign-in page.
Uploaded photos or documents return 404 Run php artisan storage:link.
.env changes have no effect Run php artisan optimize (it rebuilds the cached config).
Rides stay "Finding your driver" forever The queue worker isn't running, or no online driver is within the search radius. Check php artisan queue:work, the cron entries and Admin → Live Map.
Chat and ride notifications arrive late or never, or one side doesn't see the other's action The queue worker isn't running, or doesn't work the notifications queue. Run it with --queue=dispatch,notifications,default.
OTP never arrives / sms_not_configured Set TWILIO_* (see SMS above). A Twilio trial only texts verified numbers.
"Too many requests" for many users at once (behind Cloudflare or a load balancer) Set TRUSTED_PROXIES (see Web server), so each visitor is counted by their own IP.
A driver can't go online on an emulator Emulators report mock GPS, which is blocked by default. For testing only, set TRACKING_BLOCK_MOCK_LOCATIONS=false.
The rider app's map shows the whole world and Where to? stays grey The phone has no position yet: location is off, the signal is weak indoors, or a simulator has no location set (iOS Simulator: Features → Location; Android emulator: Extended controls → Location). After 20 seconds the app says the location isn't available; tap that line to try again.
A demo account's code doesn't fit the code boxes The apps show one box per digit of OTP_LENGTH. Give RIDEX_DEMO_OTP that many digits.
Apps poll instead of updating live Deploy the database rules (Admin → Firebase Configuration → Deploy rules) and check FIREBASE_DATABASE_URL.
No push notifications on iOS Upload an APNs key in Firebase and add the Push Notifications capability in Xcode.
Live updates stop, or Firebase sign-in codes fail, after you enforce App Check That build isn't verified. Debug builds need a registered debug token, release builds need Play Integrity or App Attest set up (Firebase App Check above). Security → App Check → APIs shows how many requests were refused.
App Check shows your release builds as unverified Android: link the Play Integrity API, add the SHA-256 of Google Play's app signing key, and test with a build from Google Play (internal testing). iOS: add the App Attest capability with the production environment, and the DeviceCheck key and Team ID in Firebase. Enforce only once they show up as verified.
"Google sign-in failed" in the app Android: the app's SHA-1 is missing in Firebase, or google-services.json has no web client. iOS: GOOGLE_IOS_CLIENT_ID / GOOGLE_REVERSED_CLIENT_ID aren't set. The device log (flutter logs) shows the exact cause after "RideX: social sign-in failed". A token refused with invalid_token_audience means GOOGLE_CLIENT_IDS or APPLE_CLIENT_IDS lacks that app's client ID.
Firebase sign-in codes never arrive Check that the Phone provider is on, the Android SHA-1/SHA-256 fingerprints are added, and on iOS the APNs key or FIREBASE_ENCODED_APP_ID is set.
An app says it must be updated Its version is below the minimum in Admin → Settings → App versions. Lower the minimum or publish the new build.
No emails arrive Set MAIL_* (see Email above) and keep the queue worker running; users also need an email address and Emails on in the app.
Payment gateways or push stopped working after changing APP_KEY Secrets saved in the admin panel are encrypted with APP_KEY. Enter the gateway keys again in Admin → Payment Configuration and upload the Firebase key again.
Place search shows no results, or fares look like straight-line estimates Check GOOGLE_MAPS_SERVER_KEY and that Places API (New) and Routes API are enabled for it. The log names the failing call ("Google Places failed" / "Google Routes failed").

Mobile app setup

This guide takes you from a fresh computer to store-ready builds of both RideX apps: RideX Rider (ridex_rider/) and RideX Driver (ridex_driver/). Both apps are set up the same way. Where they differ, the guide says so. Do every step once for each app.

Set up the backend first (Backend & admin panel, Deployment). The apps load their branding, languages and settings from it, so they stop at the splash screen until they can reach it.

1. Introduction

1.1 What you will do

  1. Install Flutter, Android Studio and, on a Mac, Xcode (sections 2, 3, 5 and 7).
  2. Open the apps, install their packages and add your configuration files (sections 4, 6 and 8).
  3. Point the apps at your backend and connect them to Firebase (sections 9 and 10).
  4. Put your own name, package name, icon and colours on them (section 11).
  5. Run them on emulators, simulators and phones (section 12).
  6. Build signed releases for Google Play and the App Store (sections 13 and 14).
  7. Once the store builds are out, enforce Firebase App Check, set up in section 10.3, so only your genuine apps can use Firebase.

1.2 The two apps

RideX Rider RideX Driver
Folder ridex_rider/ ridex_driver/
Android package name and iOS bundle ID com.codercampapp.ridex.rider com.codercampapp.ridex.driver
Name under the icon RideX RideX Driver
Icon and splash colour #0F9D58 (green) #111827 (dark grey)
Location While the app is open Also while the app is in the background, as long as the driver is online

Each app contains an identical copy of the code they share, in packages/ridex_core/ (Shared app code).

Choose your own package name and bundle ID before you register the apps in Firebase (11.2). Firebase's configuration files belong to one package name and bundle ID, so changing them later means downloading new files.

1.3 Conventions

  • Run commands from the app's folder (ridex_rider/ or ridex_driver/) unless a step says otherwise.
  • Commands are for macOS and Linux. Windows versions follow where they differ. Everything iOS needs a Mac.
  • Replace the placeholders, such as https://your-domain.com, YOUR_ANDROID_MAPS_KEY and com.yourcompany.taxi, with your own values.
  • "Tested" means the version this guide was checked with.
  • Steps that change a file show it in both apps: the Rider app first, then the Driver app.

2. Requirements

2.1 Software

Tool Version Notes
Flutter SDK 3.44 or newer, stable channel (tested: 3.44.4) The apps need Dart 3.12.2 or newer (sdk: ^3.12.2 in pubspec.yaml), which comes with Flutter 3.44.4.
Dart 3.12.2 or newer Included with Flutter.
Android Studio Current stable (tested: 2026.1) With the Flutter and Dart plugins.
JDK 17 or newer (tested: 17.0.18) The JDK bundled with Android Studio works. The apps compile for Java 17.
Android SDK Android 16 (API 36) platform, Platform-Tools, Command-line Tools, Emulator The apps compile against and target API 36. Gradle downloads the build tools and NDK 28.2.13676358 by itself once the SDK licences are accepted.
Gradle, Android Gradle Plugin, Kotlin Included (Gradle 9.1.0, AGP 9.0.1, Kotlin 2.3.20) Nothing to install: the Gradle wrapper downloads them.
Xcode (Mac only) Current release (tested: 27.0) Apple only accepts App Store uploads built with a recent Xcode.
CocoaPods (Mac only) 1.16 or newer (tested: 1.16.2) Installs the iOS parts of the Flutter plugins.
VS Code (optional) Any recent version With the Flutter extension, if you prefer it to Android Studio.

2.2 Supported phones

Minimum Built for
Android Android 7.0 (API 24) Android 16 (API 36)
iOS iOS 15.0 iPhone only, portrait

Once you enforce App Check (10.3), Android phones need Google Play for its Play Integrity check. Phones without it still work, but poll for ride updates and can't use Firebase phone sign-in codes. Every supported iPhone can use App Attest.

2.3 Accounts

Account What for
Firebase Push notifications, instant ride updates and offers, Google sign-in and Firebase phone sign-in codes. Optional: without it, the apps still build and run, and ask the backend for updates every few seconds.
Google Cloud, with billing The Maps SDK keys the apps show maps with (and the backend's server key).
Apple Developer Program (paid) Push notifications, Sign in with Apple, App Attest for App Check, and App Store releases. A free Apple ID can run the app on your own iPhone.
Google Play Console Publishing the Android apps, and Play Integrity for App Check.

2.4 Computer and backend

  • A Mac for iOS. Android works on macOS, Windows and Linux.
  • 16 GB of RAM is recommended: the Android build may use up to 8 GB (org.gradle.jvmargs=-Xmx8G in android/gradle.properties).
  • A RideX backend the phone or emulator can reach (section 9). For testing on your computer, the backend's php artisan serve on port 8000 is enough.

3. Flutter Installation

3.1 Install the Flutter SDK

  1. Open the Flutter install page, https://docs.flutter.dev/install, choose your operating system, and follow its steps to download the SDK from the stable channel.

  2. Unpack it to a folder whose path has no spaces and needs no administrator rights, for example ~/development/flutter (macOS, Linux) or C:\src\flutter (Windows).

  3. Add Flutter's bin folder to your PATH:

    • macOS (zsh):

      echo 'export PATH="$HOME/development/flutter/bin:$PATH"' >> ~/.zshrc
      source ~/.zshrc
      
    • Linux (bash):

      echo 'export PATH="$HOME/development/flutter/bin:$PATH"' >> ~/.bashrc
      source ~/.bashrc
      
    • Windows: open Start, search for environment variables, choose Edit environment variables for your account, select Path, click Edit → New, enter C:\src\flutter\bin and click OK. Then open a new terminal.

  4. Check that the terminal finds Flutter:

    flutter --version
    

    On the tested computer this prints:

    Flutter 3.44.4 • channel stable • https://github.com/flutter/flutter.git
    Framework • revision ad70ec4617 (3 months ago) • 2026-06-24 11:07:06 -0700
    Engine • hash 700aebeca4c0e610f109a3979ee3e71b69d666bc (revision a10d8ac38d) (3 months ago) • 2026-06-23 23:09:55.000Z
    Tools • Dart 3.12.2 • DevTools 2.57.0
    

3.2 Use the tested Flutter version (optional)

A newer stable Flutter usually works, but 3.44.4 is the version the apps were built and tested with. The Flutter SDK is a Git repository, so you can switch to that release:

cd ~/development/flutter
git fetch --tags
git checkout 3.44.4
flutter --version

To go back to the newest stable release later, run git checkout stable and then flutter upgrade.

3.3 Check your setup with flutter doctor

flutter doctor

flutter doctor checks Flutter, the Android tools, Xcode and the connected devices. Real output from a Mac where the Android SDK licences haven't been accepted yet:

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.44.4, on macOS 27.0.1 26A434 darwin-arm64, locale en-US)
[!] Android toolchain - develop for Android devices (Android SDK version 37.0.0)
    ✗ Android license status unknown.
      Run `flutter doctor --android-licenses` to accept the SDK licenses.
      See https://flutter.dev/to/macos-android-setup for more details.
[✓] Xcode - develop for iOS and macOS (Xcode 27.0)
[✓] Chrome - develop for the web
[✓] Connected device (7 available)
[✓] Network resources

! Doctor found issues in 1 category.

What each line needs:

Line When it's not ✓
Flutter Fix your PATH (3.1).
Android toolchain Install the SDK packages and accept the licences (5.3). Android license status unknown means the Command-line Tools are missing or the licences haven't been accepted.
Xcode (Mac only) Install Xcode and CocoaPods (7.1, 7.2).
Connected device Start an emulator or simulator, or connect a phone (12.1, 12.2).
Chrome, Network resources Chrome doesn't matter: the apps are for Android and iOS only. Network resources needs internet access.

Run flutter doctor -v for the details of every line, such as where the Android SDK and the JDK are.

4. Project Setup

4.1 Unpack the package

Unpack the ZIP file you downloaded from CodeCanyon:

RideX/
├── ridex_backend/     REST API, admin panel and web installer (Laravel)
├── ridex_rider/       Rider app (Flutter)
├── ridex_driver/      Driver app (Flutter)
├── Documentation/     This documentation
└── README.txt

Each app folder is a complete Flutter project:

ridex_rider/
├── lib/                 The app's own code: main.dart, config/, features/, localization/
├── packages/ridex_core/ Code shared with the other app (an identical copy in both apps)
├── assets/branding/     App icon and splash screen images
├── android/             Android project (Gradle)
├── ios/                 iOS project (Xcode and CocoaPods)
├── test/                Tests
├── pubspec.yaml         Packages, version number, icon and splash screen settings
└── pubspec.lock         The exact package versions the app was tested with

4.2 Open the project

  • Android Studio: File → Open, select the ridex_rider folder itself (not RideX/ and not android/) and click Open. When asked, click Trust Project. Open ridex_driver the same way, in a second window.
  • VS Code: File → Open Folder and select ridex_rider.

4.3 Tell the IDE where Flutter is

In Android Studio, open Settings (on macOS: Android Studio → Settings), then Languages & Frameworks → Flutter, and set Flutter SDK path to your Flutter folder, for example ~/development/flutter. The Dart SDK is found from it.

Android Studio → Settings → Languages & Frameworks → Flutter: the Flutter SDK path
Figure 16: Android Studio → Settings → Languages & Frameworks → Flutter: the Flutter SDK path

VS Code finds Flutter on your PATH. If it doesn't, open the Command Palette and run Flutter: Change SDK.

4.4 Install the packages

flutter pub get

Real output (shortened):

Resolving dependencies...
Downloading packages...
  cli_util 0.4.2 (0.6.0 available)
  clock 1.1.2 (1.1.3 available)
  …
Got dependencies!
30 packages have newer versions incompatible with dependency constraints.
Try `flutter pub outdated` for more information.

The "newer versions" lines are normal: pubspec.lock keeps the versions the apps were tested with. Don't run flutter pub upgrade --major-versions; it replaces them with untested ones.

flutter pub get also writes two files that belong to your computer and aren't shipped: android/local.properties (where Flutter is) and ios/Flutter/Generated.xcconfig. Android Studio runs it for you when it shows a Pub get banner.

4.5 Add your configuration files

File Create it from What goes in it Section Without it
android/app/google-services.json The Firebase console Android Firebase configuration 10.1 No push notifications, and ride updates come every few seconds instead of instantly
ios/Runner/GoogleService-Info.plist The Firebase console iOS Firebase configuration 10.2 No push notifications, and ride updates come every few seconds instead of instantly
android/secrets.properties android/secrets.properties.example MAPS_API_KEY for Android 6.2 The map shows no streets
ios/Flutter/Secrets.xcconfig ios/Flutter/Secrets.xcconfig.example MAPS_API_KEY for iOS, plus optional sign-in values 8.2 The map shows no streets
android/key.properties android/key.properties.example Your upload keystore 13.5 Release builds use the debug key, which Google Play rejects

Copy the two example files now:

cp android/secrets.properties.example android/secrets.properties
cp ios/Flutter/Secrets.xcconfig.example ios/Flutter/Secrets.xcconfig

Windows (PowerShell):

Copy-Item android\secrets.properties.example android\secrets.properties
Copy-Item ios\Flutter\Secrets.xcconfig.example ios\Flutter\Secrets.xcconfig

These files are listed in .gitignore, so your keys never end up in Git. The backend address isn't kept in a file: you pass it to every run and build (section 9).

The package doesn't include Firebase files, because they belong to your Firebase project. The apps build and run without them, so you can add Firebase later (section 10). Until then, the debug console prints RideX: Firebase not configured (…); push and live updates are off., and the apps ask the backend for ride updates every few seconds.

4.6 Run the app

Once the backend is running and the Firebase files are in place, go to section 12.

5. Android Studio Setup

5.1 Install Android Studio

  1. Download Android Studio from https://developer.android.com/studio and install it.
  2. On the first launch, the setup wizard asks for the install type. Choose Standard, accept the licences and click Finish. The wizard installs the Android SDK, Platform-Tools, the Emulator and a system image.

5.2 Install the Flutter and Dart plugins

  1. Open Settings → Plugins → Marketplace.
  2. Search for Flutter and click Install. Android Studio installs the Dart plugin with it.
  3. Click Restart IDE.
Android Studio → Settings → Plugins: the Flutter plugin, which installs Dart too
Figure 17: Android Studio → Settings → Plugins: the Flutter plugin, which installs Dart too

5.3 Install the Android SDK packages

Open Settings → Languages & Frameworks → Android SDK (or Tools → SDK Manager):

  1. On the SDK Platforms tab, tick Android 16.0 ("Baklava"), which is API level 36, and click Apply.

    SDK Manager → SDK Platforms: Android 16.0 (API 36) installed
    Figure 18: SDK Manager → SDK Platforms: Android 16.0 (API 36) installed
  2. On the SDK Tools tab, tick these and click Apply:

    • Android SDK Build-Tools
    • Android SDK Command-line Tools (latest), which flutter doctor --android-licenses needs
    • Android Emulator
    • Android SDK Platform-Tools
    • Optional: tick Show Package Details and choose NDK (Side by side) → 28.2.13676358. Otherwise Gradle downloads it during the first build.
    SDK Manager → SDK Tools: Build-Tools, Command-line Tools, Emulator and Platform-Tools
    Figure 19: SDK Manager → SDK Tools: Build-Tools, Command-line Tools, Emulator and Platform-Tools
  3. Accept the SDK licences in a terminal, answering y to each:

    flutter doctor --android-licenses
    

5.4 Check the Android SDK location

The SDK Manager shows the SDK's folder at the top (Android SDK Location). The defaults are ~/Library/Android/sdk on macOS, %LOCALAPPDATA%\Android\Sdk on Windows and ~/Android/Sdk on Linux. Flutter finds the default folder by itself. If yours is elsewhere, tell Flutter:

flutter config --android-sdk /path/to/Android/sdk

flutter doctor -v shows the folder, the JDK and the licence status. Real output (home folder shortened to ~):

[!] Android toolchain - develop for Android devices (Android SDK version 37.0.0)
    • Android SDK at ~/Library/Android/sdk
    • Emulator version 37.1.11.0 (build_id 15917651) (CL:N/A)
    • Platform android-37.0, build-tools 37.0.0
    • Java binary at: ~/Library/Java/JavaVirtualMachines/corretto-17.0.18/Contents/Home/bin/java
      This JDK is specified in your Flutter configuration.
      To change the current JDK, run: `flutter config --jdk-dir="path/to/jdk"`.
    • Java version OpenJDK Runtime Environment Corretto-17.0.18.9.1 (build 17.0.18+9-LTS)
    ✗ Android license status unknown.
      Run `flutter doctor --android-licenses` to accept the SDK licenses.
      See https://flutter.dev/to/macos-android-setup for more details.

The Platform line names the newest platform installed; the apps need API 36 to be installed too. Flutter uses Android Studio's JDK unless you choose another with flutter config --jdk-dir.

5.5 Open the project

  1. File → Open, select ridex_rider and click Open.
  2. Wait until indexing finishes. If a banner asks to run Pub get, click it.
  3. The main.dart run configuration appears in the toolbar.

Open the app folder, not its android/ folder: Android Studio then treats it as a Flutter project.

5.6 Create an Android emulator

  1. Open View → Tool Windows → Device Manager and click + → Create Virtual Device.
  2. Choose a phone, for example Pixel 8 or Medium Phone, and click Next.
  3. Choose a system image for API 36 that includes Google Play or Google APIs (Google Maps and push notifications need Google Play services). Use arm64-v8a on an Apple silicon Mac and x86_64 on Intel or AMD computers. Download it, then click Next and Finish.
  4. Click ▶ next to the new device to start it.
Android Studio → Device Manager: an emulator (Virtual) and a phone connected over USB (Physical)
Figure 20: Android Studio → Device Manager: an emulator (Virtual) and a phone connected over USB (Physical)

5.7 Connect an Android phone

  1. On the phone, open Settings → About phone and tap Build number seven times. Developer options appears (under Settings → System on many phones).
  2. In Developer options, turn on USB debugging.
  3. Connect the phone by USB, unlock it and accept Allow USB debugging?.
  4. Check that Flutter sees it: flutter devices.

A phone can't reach your computer's localhost. Section 9.3 explains how it reaches a backend on your computer.

5.8 Run the app from Android Studio

  1. Choose the emulator or phone in the device menu of the toolbar.
  2. Make sure main.dart is the selected run configuration.
  3. Click Run ▶ (or Debug). The first build takes a few minutes.
Android Studio toolbar: the device menu, the main.dart configuration and the Run button
Figure 21: Android Studio toolbar: the device menu, the main.dart configuration and the Run button
RideX Rider on an Android emulator (Pixel 8, Android 16) right after installing: the sign-in screen has loaded its settings from the backend
Figure 22: RideX Rider on an Android emulator (Pixel 8, Android 16) right after installing: the sign-in screen has loaded its settings from the backend

6. Android Configuration

6.1 Where things are

Setting File Default
Package name android/app/build.gradle.kts → namespace and applicationId com.codercampapp.ridex.rider, com.codercampapp.ridex.driver
Minimum, target and compile SDK android/app/build.gradle.kts → flutter.minSdkVersion, flutter.targetSdkVersion, flutter.compileSdkVersion 24, 36, 36 (Flutter's defaults)
NDK android/app/build.gradle.kts → flutter.ndkVersion 28.2.13676358
Java android/app/build.gradle.kts → JavaVersion.VERSION_17 Java 17
Version name and code pubspec.yaml → version 1.0.0+1 (version name 1.0.0, version code 1)
Name under the icon android/app/src/main/AndroidManifest.xml → android:label RideX, RideX Driver
Maps key android/secrets.properties → MAPS_API_KEY Empty
Firebase android/app/google-services.json Not included
Release signing android/key.properties Not included (section 13)
Gradle memory android/gradle.properties → org.gradle.jvmargs -Xmx8G …

6.2 Google Maps key for Android

  1. In the Google Cloud console, open your project (billing must be on), go to APIs & Services → Library, open Maps SDK for Android and click Enable.

  2. Go to APIs & Services → Credentials → Create credentials → API key.

  3. Restrict the key. Under Application restrictions, choose Android apps and add each app's package name with the SHA-1 fingerprints of your debug key, your upload key and Google Play's app signing key (section 13.8 shows how to get them). Under API restrictions, allow only Maps SDK for Android.

  4. Paste the key into android/secrets.properties of each app:

    MAPS_API_KEY=YOUR_ANDROID_MAPS_KEY
    
    Rider app: your Maps key goes after MAPS_API_KEY= in android/secrets.properties, copied from the .example file in section 4.5
    Figure 23: Rider app: your Maps key goes after MAPS_API_KEY= in android/secrets.properties, copied from the .example file in section 4.5
    Driver app: your Maps key goes after MAPS_API_KEY= in android/secrets.properties, copied from the .example file in section 4.5
    Figure 24: Driver app: your Maps key goes after MAPS_API_KEY= in android/secrets.properties, copied from the .example file in section 4.5

The build copies the key into the app's manifest (com.google.android.geo.API_KEY), so no code changes are needed. Without a key the app still works, but the map has no streets. Place search and routes don't use this key: they go through the backend's server key (Google Maps).

6.3 Permissions

The manifests ask for:

  • Both apps: INTERNET, ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION.
  • Driver app, as well: FOREGROUND_SERVICE, FOREGROUND_SERVICE_LOCATION and POST_NOTIFICATIONS. While a driver is online, a location foreground service keeps sending their position, with a "you are online" notification, so the app doesn't need the background location permission.

Flutter plugins add a few more when the app is built, such as notifications and network state. For the Google Play declaration the driver app needs, see section 13.10.

6.4 HTTP and HTTPS

  • android/app/src/main/res/xml/network_security_config.xml applies to release builds: HTTPS only, trusting the phone's built-in certificate authorities only.
  • android/app/src/debug/res/xml/network_security_config.xml lets debug builds use http://, for a backend on your computer.

So http://10.0.2.2:8000 works while you develop, and release builds need https://.

7. Xcode Setup

Everything in this section needs a Mac.

7.1 Install Xcode

  1. Install Xcode from the Mac App Store and open it once. Agree to the licence and, when asked, install the iOS platform. You can also add it later in Xcode → Settings → Components.

  2. In a terminal, point the command-line tools at it and let it finish its setup:

    sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
    sudo xcodebuild -runFirstLaunch
    
  3. flutter doctor should now show [✓] Xcode - develop for iOS and macOS (Xcode 27.0) (with your version).

7.2 Install CocoaPods

brew install cocoapods

If you don't use Homebrew, run sudo gem install cocoapods instead. Then check it:

pod --version

The tested computer prints 1.16.2. If CocoaPods warns that your terminal must use UTF-8, add export LANG=en_US.UTF-8 to ~/.zshrc and open a new terminal.

7.3 Prepare the iOS project

flutter run and flutter build install the iOS plugins by themselves. Before you open the project in Xcode for the first time, install them yourself:

flutter pub get
cd ios
pod install
cd ..

Real output (end):

Integrating client project
Pod installation complete! There are 18 dependencies from the Podfile and 45 total pods installed.

pod install also prints two warnings, about the Profile build configuration and about Firebase moving away from CocoaPods. Neither affects building or running the apps (section 15.4).

7.4 Open the workspace

open ios/Runner.xcworkspace

Always open Runner.xcworkspace, not Runner.xcodeproj: only the workspace includes the plugins.

In the project navigator, select Runner (the blue icon), then TARGETS → Runner. The General tab shows the display name, bundle identifier, version and minimum iOS version.

Xcode: TARGETS → Runner → General, with the display name, bundle identifier, version and minimum deployment (iOS 15.0)
Figure 25: Xcode: TARGETS → Runner → General, with the display name, bundle identifier, version and minimum deployment (iOS 15.0)

7.5 Add your Apple account

Open Xcode → Settings → Accounts, click + and sign in with your Apple ID. A free account can run the app on your own iPhone. Push notifications, Sign in with Apple and App Store releases need a paid Apple Developer Program membership.

7.6 Set the team and bundle identifier

  1. Select TARGETS → Runner and open Signing & Capabilities.
  2. Tick Automatically manage signing.
  3. Choose your Team. The project comes with its author's team, which your account can't use, so this step is required.
  4. Set Bundle Identifier to your own ID, for example com.yourcompany.taxi for the rider app and com.yourcompany.taxi.driver for the driver app.
  5. Select TARGETS → RunnerTests and do the same, with your ID followed by .RunnerTests.

Xcode creates the signing certificate and the provisioning profile for you.

Xcode → Signing & Capabilities: Automatically manage signing, the Team menu and the bundle identifier; until you pick a team, Xcode warns that signing needs one
Figure 26: Xcode → Signing & Capabilities: Automatically manage signing, the Team menu and the bundle identifier; until you pick a team, Xcode warns that signing needs one

7.7 Add the capabilities

With a paid team, click + Capability on the Signing & Capabilities tab and add:

  • Push Notifications, for notifications sent through Firebase. Xcode creates Runner/Runner.entitlements for it.
  • Sign in with Apple, only if you switch it on in the admin panel (Sign in with Google and Apple).
  • App Attest, for App Check (10.3). Then open Runner/Runner.entitlements and set App Attest Environment (com.apple.developer.devicecheck.appattest-environment) to production, because App Check doesn't accept tokens from Apple's development environment.

The background modes are already set in Info.plist: Remote notifications in both apps, plus Location updates in the driver app.

Xcode: Push Notifications and Sign in with Apple added to the Runner target
Figure 27: Xcode: Push Notifications and Sign in with Apple added to the Runner target

7.8 Deployment target

General → Minimum Deployments is iOS 15.0. The Podfile uses the same version (platform :ios, '15.0') and raises the plugins to it after pod install. Don't lower it.

7.9 Run from Xcode

Choose a simulator in the run destination menu of the toolbar, then click Run (⌘R). Xcode builds with the settings of your last flutter run or flutter build, including the backend address. For everyday work, use flutter run (section 12.2) and keep Xcode for signing and archiving.

8. iOS Configuration

8.1 Where things are

Setting Where Default
Bundle ID Xcode → Runner → Signing & Capabilities (and RunnerTests) com.codercampapp.ridex.rider, com.codercampapp.ridex.driver
Team Same tab The author's team: replace it
Name under the icon ios/Runner/Info.plist → CFBundleDisplayName and CFBundleName RideX, RideX Driver
Version and build number pubspec.yaml → version (Info.plist reads $(FLUTTER_BUILD_NAME) and $(FLUTTER_BUILD_NUMBER)) 1.0.0 (1)
Minimum iOS version Xcode → General, and ios/Podfile 15.0
Maps key ios/Flutter/Secrets.xcconfig → MAPS_API_KEY Empty
Firebase ios/Runner/GoogleService-Info.plist Not included
Google Sign-In, Firebase phone sign-in ios/Flutter/Secrets.xcconfig Placeholders
Permission texts ios/Runner/Info.plist → NS…UsageDescription See 8.4
Devices and orientation Xcode → General iPhone only, portrait

8.2 Google Maps key for iOS

  1. In the Google Cloud console, enable Maps SDK for iOS (APIs & Services → Library).

  2. Create an API key (APIs & Services → Credentials). Under Application restrictions, choose iOS apps and add both bundle IDs. Under API restrictions, allow only Maps SDK for iOS.

  3. Paste it into ios/Flutter/Secrets.xcconfig of each app:

    MAPS_API_KEY=YOUR_IOS_MAPS_KEY
    
    Rider app: your Maps key goes after MAPS_API_KEY= in ios/Flutter/Secrets.xcconfig, copied from the .example file in section 4.5
    Figure 28: Rider app: your Maps key goes after MAPS_API_KEY= in ios/Flutter/Secrets.xcconfig, copied from the .example file in section 4.5
    Driver app: your Maps key goes after MAPS_API_KEY= in ios/Flutter/Secrets.xcconfig, copied from the .example file in section 4.5
    Figure 29: Driver app: your Maps key goes after MAPS_API_KEY= in ios/Flutter/Secrets.xcconfig, copied from the .example file in section 4.5
  4. Stop the app and run it again. The key is built into Info.plist (as GMSApiKey), so a hot reload doesn't pick it up.

Without a key, the map has no streets and Xcode's console shows RideX: MAPS_API_KEY missing - set it in ios/Flutter/Secrets.xcconfig; maps will not load.

8.3 Sign-in values (optional)

Google Sign-In and Firebase phone sign-in need a few values in ios/Flutter/Secrets.xcconfig. Remove the // in front of the lines you use:

GOOGLE_IOS_CLIENT_ID=YOUR_CLIENT_ID
GOOGLE_REVERSED_CLIENT_ID=YOUR_REVERSED_CLIENT_ID
FIREBASE_ENCODED_APP_ID=YOUR_ENCODED_APP_ID
Rider app: the sign-in lines in ios/Flutter/Secrets.xcconfig; remove the // in front and add your value after =
Figure 30: Rider app: the sign-in lines in ios/Flutter/Secrets.xcconfig; remove the // in front and add your value after =
Driver app: the sign-in lines in ios/Flutter/Secrets.xcconfig; remove the // in front and add your value after =
Figure 31: Driver app: the sign-in lines in ios/Flutter/Secrets.xcconfig; remove the // in front and add your value after =
Value Where to find it
GOOGLE_IOS_CLIENT_ID CLIENT_ID in this app's GoogleService-Info.plist, after Google sign-in is enabled in Firebase
GOOGLE_REVERSED_CLIENT_ID REVERSED_CLIENT_ID in the same file
FIREBASE_ENCODED_APP_ID Firebase console → Project settings → your iOS app → Encoded App ID. Needed for Firebase phone sign-in without push notifications.

Info.plist turns these values into URL schemes and GIDClientID by itself. Until you set them, Debug.xcconfig and Release.xcconfig supply harmless placeholders. The backend and Firebase steps for both sign-in methods are in Sign-in codes with Firebase Phone Auth and Sign in with Google and Apple.

8.4 Permissions and background modes

iOS shows these texts when the app asks for a permission. Rewrite them in ios/Runner/Info.plist to fit your service: App Review rejects texts that don't say why the app needs the permission.

Key RideX Rider RideX Driver
NSLocationWhenInUseUsageDescription Your location is used to set your pickup point and show nearby drivers. Your location is shared with riders and dispatch while you are online.
NSLocationAlwaysAndWhenInUseUsageDescription Not used Keep sharing your location during trips even when the app is in the background.
NSCameraUsageDescription Take a profile photo. Take your profile photo and photos of your documents and vehicle.
NSPhotoLibraryUsageDescription Choose a profile photo. Choose your profile photo and photos of your documents and vehicle.
UIBackgroundModes remote-notification location, remote-notification
Rider app: the permission texts iOS shows, in ios/Runner/Info.plist
Figure 32: Rider app: the permission texts iOS shows, in ios/Runner/Info.plist
Driver app: the permission texts iOS shows, in ios/Runner/Info.plist
Figure 33: Driver app: the permission texts iOS shows, in ios/Runner/Info.plist

8.5 HTTP and HTTPS

Info.plist sets NSAppTransportSecurity → NSAllowsLocalNetworking to YES, so debug builds on the simulator can reach a backend on your Mac over http://. Release builds only accept an https:// address (section 9.5).

9. API Configuration

9.1 How the apps find the backend

The backend address is a build setting called API_BASE_URL, passed with --dart-define to every flutter run and flutter build. It isn't kept in a file. Both apps read it in packages/ridex_core/lib/src/config/core_config.dart:

static const String _definedBaseUrl = String.fromEnvironment('API_BASE_URL');

/// Local dev defaults: the iOS simulator shares the host network, the Android emulator reaches it via 10.0.2.2.
static String get apiBaseUrl {
  if (kReleaseMode && !Uri.parse(_definedBaseUrl).isScheme('https')) {
    throw StateError('API_BASE_URL must be an https:// address in release builds, e.g. --dart-define=API_BASE_URL=https://your-domain.com');
  }
  if (_definedBaseUrl.isNotEmpty) return _definedBaseUrl;
  return Platform.isAndroid ? 'http://10.0.2.2:8000' : 'http://127.0.0.1:8000';
}

Use your backend's address without /api at the end: the apps add /api/v1 themselves.

flutter run --dart-define=API_BASE_URL=https://your-domain.com

9.2 Development defaults

When you don't pass API_BASE_URL, debug builds use a backend on the same computer:

Where the app runs Address used The backend must run as
iOS simulator http://127.0.0.1:8000 php artisan serve --host=127.0.0.1 --port=8000
Android emulator http://10.0.2.2:8000 (the emulator's name for your computer) The same

9.3 Phones

  • Android phone over USB: forward the phone's port 8000 to your computer, then run with the address set to the phone's own localhost. Repeat adb reverse every time you reconnect the phone:

    adb reverse tcp:8000 tcp:8000
    flutter run --dart-define=API_BASE_URL=http://127.0.0.1:8000
    
  • iPhone: point the app at a backend it can reach over the internet, such as your staging or production server: --dart-define=API_BASE_URL=https://your-domain.com.

9.4 Add the address to your IDE

  • Android Studio: Run → Edit Configurations…, select main.dart, and enter --dart-define=API_BASE_URL=https://your-domain.com in Additional run args.

    Android Studio → Run → Edit Configurations → main.dart: the backend address in Additional run args (here the local address a phone on USB uses, 9.3)
    Figure 34: Android Studio → Run → Edit Configurations → main.dart: the backend address in Additional run args (here the local address a phone on USB uses, 9.3)
  • VS Code: add the argument to .vscode/launch.json:

    {
      "version": "0.2.0",
      "configurations": [
        {
          "name": "ridex_rider",
          "request": "launch",
          "type": "dart",
          "args": ["--dart-define=API_BASE_URL=https://your-domain.com"]
        }
      ]
    }
    

9.5 Release builds

Release builds must get an https:// address. Without one, the app stops at launch with:

API_BASE_URL must be an https:// address in release builds, e.g. --dart-define=API_BASE_URL=https://your-domain.com

Sections 13 and 14 include the address in every build command.

9.6 Check that the backend answers

Open https://your-domain.com/api/v1/config in a browser, on your computer or on the phone. The apps load this page on every launch. A working backend answers with JSON that starts like this (shortened):

{
  "success": true,
  "data": {
    "branding": {
      "app_name": "RideX",
      "primary_color": "#0F9D58",
      "rider_app_name": "RideX",
      "driver_app_name": "RideX Driver"
    },
    "currency": { "code": "USD", "symbol": "$", "position": "left", "decimals": 2 },
    "localization": { … },
    "vehicle_types": [ … ],
    "settings": { … }
  }
}

When the app gets past the splash screen and shows the sign-in screen, like the Android screenshot in 5.8, it is talking to the backend.

9.7 Keys and sign-in

  • No API keys in the app besides the Maps SDK keys (sections 6.2 and 8.2). Place search, routes, payments and texts go through the backend, which keeps its keys on the server.
  • Sign-in: users sign in with their phone number and a code (or Google and Apple, if you switch them on). The backend then gives the app a token, so there's nothing to configure in the app.
  • For local testing, switch on the backend's demo mode (Local testing and demo mode) and use the demo accounts: rider +1 5550000001 and driver +1 5550000002, both with code 123456.
  • Optional build settings: API_CERT_PINS (Certificate pinning), APP_CHECK_DEBUG_TOKEN for debug runs (10.3 App Check) and RASP_WATCHER_EMAIL, RASP_ANDROID_SIGNING_HASHES, RASP_IOS_TEAM_ID (Device security checks) are passed with --dart-define too.

10. Firebase Configuration

The apps use these Firebase services:

Service What for
Cloud Messaging (FCM) Push notifications
Realtime Database Live ride updates, driver positions and instant ride offers. Without it, the apps ask the backend for updates every few seconds instead.
Authentication Signs the apps in to the Realtime Database with tokens from the backend. Optionally sends sign-in codes (Firebase Phone Auth) and runs Google sign-in.
App Check (optional) Lets the Realtime Database and Authentication accept requests only from your genuine apps, once you enforce it (10.3).

One Firebase project serves everything: four app registrations (Rider and Driver, each on Android and iOS) and the backend.

Firebase is optional. Without its files, the apps still build and run: they ask the backend for ride updates every few seconds, notifications appear only in the in-app inbox, and Google sign-in and Firebase phone sign-in codes are unavailable. Adding the files later needs no code changes.

Create the Firebase project

  1. Open the Firebase console, click Create a project and give it a name. Google Analytics isn't used by RideX, so you can leave it off.

    Firebase console: creating the project
    Figure 35: Firebase console: creating the project
  2. Set up the parts the backend needs: the Realtime Database, Authentication, and the service account key you upload in the admin panel, after which you press Deploy rules. Follow Firebase (push notifications and live tracking) in the deployment guide.

10.1 Android Firebase Setup

Do this once for the Rider app and once for the Driver app.

  1. In the project overview, click Add app and choose Android.

  2. Enter the Android package name exactly as applicationId in android/app/build.gradle.kts: com.codercampapp.ridex.rider, com.codercampapp.ridex.driver, or your own (section 11.2).

  3. Enter an App nickname, such as Rider Android. The Debug signing certificate SHA-1 is only needed for Google sign-in and Firebase phone sign-in, and you can add it later (section 13.8).

  4. Click Register app.

    Firebase console → Add app → Android: type your package name exactly as applicationId; the one in this picture is only an example
    Figure 36: Firebase console → Add app → Android: type your package name exactly as applicationId; the one in this picture is only an example
  5. Click Download google-services.json.

    Firebase console → Project settings → Your apps: google-services.json, which you can download here again at any time
    Figure 37: Firebase console → Project settings → Your apps: google-services.json, which you can download here again at any time
  6. Put the file in the app's android/app/ folder: ridex_rider/android/app/google-services.json for the rider app, and the driver's file in ridex_driver/android/app/.

    Rider app: google-services.json goes in android/app/
    Figure 38: Rider app: google-services.json goes in android/app/
    Driver app: google-services.json goes in android/app/
    Figure 39: Driver app: google-services.json goes in android/app/
  7. Skip the console's remaining steps, which add the Firebase SDK and plugin: the project already has them. android/settings.gradle.kts declares the Google services plugin (com.google.gms.google-services 4.4.4) and android/app/build.gradle.kts applies it. The googleServices block at the end of that file turns a missing file into a warning, which is how the app builds without Firebase.

  8. Build and run the app (section 12.1). If the package name in the file doesn't match, the build stops with:

    No matching client found for package name 'com.codercampapp.ridex.rider' in …/android/app/google-services.json
    

10.2 iOS Firebase Setup

Do this once for the Rider app and once for the Driver app.

  1. In the project overview, click Add app and choose iOS+ (Apple).

  2. Enter the Apple bundle ID exactly as in Xcode: com.codercampapp.ridex.rider, com.codercampapp.ridex.driver, or your own. The App nickname and App Store ID are optional.

  3. Click Register app, then Download GoogleService-Info.plist.

    Firebase console → Add app → Apple: type your bundle ID exactly as in Xcode; the one in this picture is only an example
    Figure 40: Firebase console → Add app → Apple: type your bundle ID exactly as in Xcode; the one in this picture is only an example
  4. Copy the file to ios/Runner/GoogleService-Info.plist of the app, in Finder or with cp. You don't add it in Xcode: the Copy Firebase Config build phase (Runner target → Build Phases) copies it into the app when it's there, and the app builds without Firebase when it isn't.

    Rider app: GoogleService-Info.plist goes in ios/Runner/
    Figure 41: Rider app: GoogleService-Info.plist goes in ios/Runner/
    Driver app: GoogleService-Info.plist goes in ios/Runner/
    Figure 42: Driver app: GoogleService-Info.plist goes in ios/Runner/
  5. Skip the console's remaining steps, which add the SDK and initialization code: the project already has them. CocoaPods installs the Firebase SDK, and the app starts Firebase itself.

  6. For push notifications (a paid Apple team):

    1. In your Apple Developer account, open Certificates, Identifiers & Profiles → Keys, click +, tick Apple Push Notifications service (APNs), then Continue and Register. Download the .p8 file (Apple lets you download it only once) and note the Key ID.
    2. In the Firebase console, open Project settings → Cloud Messaging → Apple app configuration, and under APNs Authentication Key click Upload. Choose the .p8 file and enter the Key ID and your Team ID. One key covers both apps.
    3. In Xcode, add the Push Notifications capability (section 7.7).
    Firebase console → Project settings → Cloud Messaging: the APNs authentication key
    Figure 43: Firebase console → Project settings → Cloud Messaging: the APNs authentication key
  7. Build and run the app (section 12.2).

10.3 App Check

App Check lets Firebase accept requests only from your own apps, so the database address in the app files is no use to anyone else. Both apps turn it on at start-up:

Build Android iOS
Release Play Integrity App Attest (DeviceCheck before iOS 14)
Debug and profile A debug token A debug token

Until you enforce it, App Check only counts requests and blocks nothing. Set it up before your release, and enforce it once your store builds show up as verified.

  1. Android: in the Google Play Console, open your app, go to Release → App integrity → Play Integrity API, click Link Cloud project and choose your Firebase project (you must be an Owner of it). In the Firebase console, add the SHA-256 fingerprint of Google Play's app signing key to each Android app (Project settings → Your apps; section 13.8 shows where to find it). Then open Security → App Check → Apps and register each Android app with Play Integrity.

  2. iOS: add the App Attest capability in Xcode (7.7). In your Apple Developer account, open Certificates, Identifiers & Profiles → Keys, click +, tick DeviceCheck and download the .p8 file. In the Firebase console, enter your Team ID in each iOS app's settings (Project settings → Your apps), then register each iOS app under Security → App Check → Apps with App Attest and with DeviceCheck, using the .p8 key and its Key ID.

  3. Debug builds prove themselves with a debug token. Make one with uuidgen (macOS and Linux) or [guid]::NewGuid() (PowerShell). Add it to each app under Security → App Check → Apps → ⋮ → Manage debug tokens, then pass it to your debug runs, next to the backend address (9.4):

    flutter run --dart-define=API_BASE_URL=http://10.0.2.2:8000 --dart-define=APP_CHECK_DEBUG_TOKEN=YOUR_DEBUG_TOKEN
    

    Without it, the Firebase SDK makes a token for each device and prints it: Android in Logcat as Firebase App Check debug token: …, iOS in Xcode's console when the scheme passes the -FIRDebugEnabled launch argument. You can also register a token with the Firebase CLI: firebase appcheck:debugtokens:create YOUR_DEBUG_TOKEN --project YOUR_PROJECT_ID --app YOUR_APP_ID. Keep debug tokens private, delete the ones you no longer use, and never pass APP_CHECK_DEBUG_TOKEN to a release build.

  4. Enforce: open Security → App Check → APIs. When the metrics for Realtime Database and Authentication show your release builds' requests as verified, click Enforce for each. It takes up to 15 minutes to apply. Then book a test ride and check that the trip screen still updates instantly. Enforcing Authentication also covers Firebase phone sign-in codes, so if you use them, sign in on a phone as well.

An app that App Check doesn't verify still signs in to your backend and polls for ride updates instead of streaming them. Only Firebase phone sign-in codes need App Check once Authentication is enforced.

Check that Firebase works

  • Debug console: if a Firebase file is missing or wrong at run time, the app prints RideX: Firebase not configured (…); push and live updates are off.
  • Admin → Firebase Configuration → Connected apps counts the phones that registered for push notifications. Sign in on a real phone and check that the number goes up.
  • Admin → Push Notifications: send a test notification to yourself. Tapping it opens the screen it is about.

11. App Configuration

Everything you may want to change, in one table:

Setting File or place What to change Example
App name, Android android/app/src/main/AndroidManifest.xml android:label android:label="MyTaxi"
App name, iOS ios/Runner/Info.plist CFBundleDisplayName and CFBundleName MyTaxi
Names inside the app Admin → Settings → General Platform and app names Shown after the next app launch, no new build
Package name, Android android/app/build.gradle.kts, MainActivity.kt namespace, applicationId, and the Kotlin package (11.2) com.yourcompany.taxi
Bundle ID, iOS Xcode → Runner and RunnerTests → Signing & Capabilities Bundle Identifier com.yourcompany.taxi
Version pubspec.yaml version: <name>+<build number> version: 1.2.0+5
Backend address Every run and build command --dart-define=API_BASE_URL=… https://your-domain.com
Maps keys android/secrets.properties, ios/Flutter/Secrets.xcconfig MAPS_API_KEY MAPS_API_KEY=YOUR_ANDROID_MAPS_KEY
Firebase android/app/google-services.json, ios/Runner/GoogleService-Info.plist Add your project's files (optional) Section 10
Google Sign-In, Firebase phone sign-in (iOS) ios/Flutter/Secrets.xcconfig GOOGLE_IOS_CLIENT_ID, GOOGLE_REVERSED_CLIENT_ID, FIREBASE_ENCODED_APP_ID Section 8.3
App icon assets/branding/icon.png, icon_foreground.png; pubspec.yaml → flutter_launcher_icons The images and adaptive_icon_background adaptive_icon_background: "#1E40AF"
Splash screen assets/branding/splash.png; pubspec.yaml → flutter_native_splash The image and color color: "#1E40AF"
Brand colours and logo inside the app Admin → Settings → General Primary and secondary colour, logo No new build
Notifications Firebase (section 10) and the Push Notifications capability (iOS) Your Firebase project, your APNs key Section 10
Languages Admin → Languages; the apps' strings in lib/localization/ and packages/ridex_core/lib/src/localization/ Default language, enabled languages, texts Adding a language
Permission texts (iOS) ios/Runner/Info.plist NS…UsageDescription Section 8.4
Release signing (Android) android/key.properties Your upload keystore Section 13.5
Certificate pinning, device checks Build command --dart-define=API_CERT_PINS=…, RASP_… Section 9.7

11.1 App name

Android: change android:label in android/app/src/main/AndroidManifest.xml.

Rider app: the name under the icon is android:label in android/app/src/main/AndroidManifest.xml
Figure 44: Rider app: the name under the icon is android:label in android/app/src/main/AndroidManifest.xml
Driver app: the name under the icon is android:label in android/app/src/main/AndroidManifest.xml
Figure 45: Driver app: the name under the icon is android:label in android/app/src/main/AndroidManifest.xml

iOS: change CFBundleDisplayName (the name under the icon) and CFBundleName in ios/Runner/Info.plist.

Rider app: the app name is CFBundleDisplayName (under the icon) and CFBundleName in ios/Runner/Info.plist
Figure 46: Rider app: the app name is CFBundleDisplayName (under the icon) and CFBundleName in ios/Runner/Info.plist
Driver app: the app name is CFBundleDisplayName (under the icon) and CFBundleName in ios/Runner/Info.plist
Figure 47: Driver app: the app name is CFBundleDisplayName (under the icon) and CFBundleName in ios/Runner/Info.plist

The names shown inside the app, its colours and its logo come from the backend (Admin → Settings → General), so they change without a new build.

11.2 Package name and bundle ID

Every store app needs its own ID, and Google Play and the App Store don't let you change it after the first release. Use a reverse domain you own, such as com.yourcompany.taxi for the rider app and com.yourcompany.taxi.driver for the driver app.

Android (rider app shown; for the driver, replace rider with driver):

  1. In android/app/build.gradle.kts, change both namespace and applicationId:

    namespace = "com.yourcompany.taxi"
    // …
    applicationId = "com.yourcompany.taxi"
    
    Rider app: the package name is namespace and applicationId in android/app/build.gradle.kts
    Figure 48: Rider app: the package name is namespace and applicationId in android/app/build.gradle.kts
    Driver app: the package name is namespace and applicationId in android/app/build.gradle.kts
    Figure 49: Driver app: the package name is namespace and applicationId in android/app/build.gradle.kts
  2. Move android/app/src/main/kotlin/com/codercampapp/ridex/rider/MainActivity.kt to a folder that matches the new name, for example android/app/src/main/kotlin/com/yourcompany/taxi/MainActivity.kt, and delete the old empty folders.

  3. Change the first line of MainActivity.kt to the new package:

    package com.yourcompany.taxi
    
    Rider app: the folders of MainActivity.kt and its first line follow the package name
    Figure 50: Rider app: the folders of MainActivity.kt and its first line follow the package name
    Driver app: the folders of MainActivity.kt and its first line follow the package name
    Figure 51: Driver app: the folders of MainActivity.kt and its first line follow the package name

iOS: in Xcode, set the Bundle Identifier of Runner and RunnerTests (section 7.6).

After renaming:

  1. Register the new IDs in Firebase and replace both Firebase files (section 10).

  2. Update the restrictions of your Maps keys: package names and SHA-1 fingerprints on Android, bundle IDs on iOS (sections 6.2 and 8.2).

  3. If Google or Apple sign-in is on, add the new IDs to GOOGLE_CLIENT_IDS and APPLE_CLIENT_IDS in the backend's .env (Sign in with Google and Apple).

  4. Rebuild from scratch:

    flutter clean
    flutter pub get
    flutter run
    

11.3 Version

version: 1.0.0+1 in pubspec.yaml sets both platforms: 1.0.0 is the version users see (Android versionName, iOS CFBundleShortVersionString), and 1 is the build number (Android versionCode, iOS CFBundleVersion). Raise the build number for every upload to the stores. You can also set them per build with --build-name=1.2.0 --build-number=5.

Rider app: version 1.0.0 and build number 1 in pubspec.yaml
Figure 52: Rider app: version 1.0.0 and build number 1 in pubspec.yaml
Driver app: version 1.0.0 and build number 1 in pubspec.yaml
Figure 53: Driver app: version 1.0.0 and build number 1 in pubspec.yaml

11.4 App icon and splash screen

Replace the images in assets/branding/ and keep their sizes:

File Size Used for
icon.png 1024 × 1024, no transparency The iOS icon and the older Android icon
icon_foreground.png 1024 × 1024, transparent, artwork in the middle two-thirds The Android adaptive and themed icon
splash.png 1152 × 1152, transparent, artwork inside the central 768 px circle The logo on the launch screen

The .svg files next to them are the editable originals. Set the background colours in pubspec.yaml: adaptive_icon_background under flutter_launcher_icons, and color (twice, including under android_12) under flutter_native_splash.

Rider app: the images to replace in assets/branding/, and their background colour (#0F9D58) in pubspec.yaml
Figure 54: Rider app: the images to replace in assets/branding/, and their background colour (#0F9D58) in pubspec.yaml
Driver app: the images to replace in assets/branding/, and their background colour (#111827) in pubspec.yaml
Figure 55: Driver app: the images to replace in assets/branding/, and their background colour (#111827) in pubspec.yaml

Then generate every Android and iOS size:

dart run flutter_launcher_icons
dart run flutter_native_splash:create

The tools end with ✓ Successfully generated launcher icons and ✅ Native splash complete. Stop the app and run it again to see the new icon.

11.5 Theme and branding

The brand colours, logo, app names, currency and languages inside the apps come from Admin → Settings and are cached on the phone, so they change without a new app release (White-labeling). Only what the phone shows before the app starts is built in: the app name under the icon, the icon and the splash screen.

12. Running the Application

Before the first run, check that:

  • the backend is running and reachable (section 9.6);
  • google-services.json and GoogleService-Info.plist are in place, if you use Firebase (section 10);
  • the Maps keys are set (sections 6.2 and 8.2).

For local testing, turn on the backend's demo mode and sign in with a demo account. The demo driver is online in Manhattan, so give the emulator or simulator a location there (for example 40.7590, -73.9845). Simulators report their location as mocked, which the backend refuses unless you set TRACKING_BLOCK_MOCK_LOCATIONS=false in its .env (on your test server only).

12.1 Android

  1. Start an emulator (5.6) or connect a phone (5.7).

  2. List the devices Flutter can use:

    flutter devices
    

    Real output (shortened):

    Found 6 connected devices:
      sdk gphone64 arm64 (mobile)  • emulator-5560                        • android-arm64  • Android 16 (API 36) (emulator)
      iPhone 17 Pro (mobile)       • A384609F-FF3E-4390-85E5-2AD38A563691 • ios            • com.apple.CoreSimulator.SimRuntime.iOS-26-5 (simulator)
      …
    
  3. Install the packages and run the app on the device:

    flutter pub get
    flutter run -d emulator-5560
    

    Use your own device ID from flutter devices. With a single device connected, flutter run is enough. On a phone over USB, add the backend address as described in 9.3.

  4. Give the emulator a location: click ⋯ (Extended controls) on the emulator's side bar, open Location, pick a point and click Set location. Without a location, the home map shows the whole world, and after 20 seconds the pickup line says the location isn't available.

While flutter run is running, press r to hot reload, R to hot restart and q to quit. In Android Studio, choose the device in the toolbar and click Run (5.8).

12.2 iOS

  1. Open the simulator and choose an iPhone model (File → Open Simulator):

    open -a Simulator
    
  2. Run the app on it. flutter run installs the iOS plugins by itself, so you don't need pod install here:

    flutter pub get
    flutter run -d "iPhone 17 Pro"
    
  3. Give the simulator a location: Features → Location → Custom Location…, enter a latitude and longitude, and click OK.

RideX Rider on the iPhone 17 Pro simulator after signing in, with a custom location: the map and the pickup address from the backend
Figure 56: RideX Rider on the iPhone 17 Pro simulator after signing in, with a custom location: the map and the pickup address from the backend

On an iPhone:

  1. Connect it with a cable, unlock it and tap Trust on the phone.
  2. On iOS 16 and newer, turn on Settings → Privacy & Security → Developer Mode (the option appears once the iPhone has been connected to Xcode) and restart the phone when asked.
  3. Set your team in Xcode (7.6), then run flutter run -d <your iPhone's ID> with an address the phone can reach (9.3).
  4. If iOS says the developer isn't trusted, open Settings → General → VPN & Device Management and trust your developer account.

With Xcode 27, flutter build ios --simulator fails, because Xcode 27's lipo accepts only one architecture. Use flutter run -d <simulator> to run on a simulator; device builds aren't affected.

13. Android Release Build

13.1 Check the application ID

Make sure applicationId is your own (11.2). Google Play never lets you change it after the first upload.

13.2 Set the version

Raise version in pubspec.yaml (11.3). Every upload needs a higher build number (versionCode) than the last one.

13.3 Check the launcher icon

Generate your icon and splash screen (11.4) before building.

13.4 Create an upload keystore

Create it once and keep it safe: you sign every update with it. keytool comes with the JDK.

keytool -genkey -v -keystore ~/upload-keystore.jks -keyalg RSA -keysize 2048 -validity 10000 -alias upload

keytool asks for a password twice (nothing appears while you type it), then for your name, organisation and country, and saves the keystore. Both apps can use the same upload keystore. Store the keystore and its password somewhere safe, outside the project. With Google Play App Signing, Google keeps the key that signs the app on users' phones, and this upload key only proves that an upload comes from you.

Creating the upload keystore with keytool: what you type is shown in blue
Figure 57: Creating the upload keystore with keytool: what you type is shown in blue

13.5 Create key.properties

Do this in each app's folder:

cp android/key.properties.example android/key.properties

Fill it in:

storeFile=/absolute/path/to/upload-keystore.jks
storePassword=YOUR_KEYSTORE_PASSWORD
keyAlias=upload
keyPassword=YOUR_KEYSTORE_PASSWORD

keytool gives the keystore and its key one password, so keyPassword is the same as storePassword. On Windows, write the path with forward slashes, for example storeFile=C:/Users/you/upload-keystore.jks. key.properties, *.jks and *.keystore are listed in android/.gitignore. Never commit or share them.

Rider app: a filled-in android/key.properties, next to key.properties.example
Figure 58: Rider app: a filled-in android/key.properties, next to key.properties.example
Driver app: a filled-in android/key.properties, next to key.properties.example
Figure 59: Driver app: a filled-in android/key.properties, next to key.properties.example

Check that Gradle reads it. In the app's android folder, run:

./gradlew signingReport

On Windows, run gradlew signingReport. If android/gradlew is missing, run flutter build apk --config-only once in the app's folder: it creates it. Under > Task :app:signingReport, the release variant must show Config: release, your keystore and the upload alias:

  • Config: debug means Gradle didn't find key.properties.
  • An Error: line, such as keystore password was incorrect, means a wrong password or path. The report still ends with BUILD SUCCESSFUL, so read the release block.
Rider app: the signing report shows the release variant signed with your upload key
Figure 60: Rider app: the signing report shows the release variant signed with your upload key
Driver app: the signing report shows the release variant signed with your upload key
Figure 61: Driver app: the signing report shows the release variant signed with your upload key

13.6 Gradle signing

Nothing to edit: android/app/build.gradle.kts already reads key.properties and signs release builds with it.

val keystore = rootProperties("key.properties")
// …
buildTypes {
    release {
        // Without android/key.properties this falls back to the debug key: fine for testing, but Play rejects it.
        signingConfig = signingConfigs.findByName("release") ?: signingConfigs.getByName("debug")
    }
}

13.7 Build the App Bundle and the APK

flutter clean
flutter pub get
flutter build appbundle --release --dart-define=API_BASE_URL=https://your-domain.com
flutter build apk --release --dart-define=API_BASE_URL=https://your-domain.com

Real output (the rider app, shortened):

Running Gradle task 'bundleRelease'...
✓ Built build/app/outputs/bundle/release/app-release.aab (100.8MB)

Running Gradle task 'assembleRelease'...                          101.9s
✓ Built build/app/outputs/flutter-apk/app-release.apk (126.1MB)

The driver app's files come out about the same size: 100.2MB and 125.4MB.

File Where What for
app-release.aab build/app/outputs/bundle/release/ Upload to Google Play. Play sends each phone only the parts it needs.
app-release.apk build/app/outputs/flutter-apk/ Installing directly, for testing or other stores.
Rider app: the release builds are written to build/app/outputs/bundle/release/ and build/app/outputs/flutter-apk/
Figure 62: Rider app: the release builds are written to build/app/outputs/bundle/release/ and build/app/outputs/flutter-apk/
Driver app: the release builds are written to build/app/outputs/bundle/release/ and build/app/outputs/flutter-apk/
Figure 63: Driver app: the release builds are written to build/app/outputs/bundle/release/ and build/app/outputs/flutter-apk/

The APK contains every processor type. For smaller files, build one APK per type with flutter build apk --release --split-per-abi --dart-define=API_BASE_URL=https://your-domain.com.

Optional extras for release builds:

  • --obfuscate --split-debug-info=build/symbols makes the code harder to read. Keep build/symbols for every version you publish, because you need it to read crash reports (flutter symbolize), and never ship it. Obfuscation doesn't hide text such as your API address, so never put secrets in the app.
  • --dart-define=API_CERT_PINS=… and the RASP_… values (section 9.7).

Messages about "Built-in Kotlin" and the "Kotlin Gradle Plugin" during the build are notices for the plugins' authors. The build still succeeds.

13.8 Get the SHA-1 fingerprints

Firebase (Google sign-in, phone sign-in) and your Maps key restrictions need the SHA-1 fingerprints of the keys that sign the app:

cd android
./gradlew signingReport

On Windows, run gradlew signingReport. The report lists every build variant: debug shows your debug key, and release shows your upload key once key.properties exists (section 13.5). Copy the SHA1 lines. For the key Google Play signs the app with, open your app's App integrity page in the Play Console: its App signing tab shows that key's SHA-1. Add all of them to the Firebase Android apps and to the Maps key.

13.9 Test the release build

  1. Run the project's checks. They should end with No issues found! and All tests passed!:

    flutter analyze
    flutter test
    
  2. Release builds only talk to an https:// backend, so use a server with a certificate.

  3. Install the APK on a phone:

    adb install -r build/app/outputs/flutter-apk/app-release.apk
    
  4. Sign in, book or accept a ride, and check the map, notifications and payments.

  5. For Google Play, upload the .aab to an internal testing track first.

13.10 Google Play notes

  • Driver app: under App content → Foreground service permissions, declare the Location foreground service and attach a short video of a driver going online.
  • Fill in the data safety form for location, photos and phone numbers, and link your privacy policy.

14. iOS Release Build

14.1 Check the bundle ID and team

TARGETS → Runner → Signing & Capabilities shows your team and bundle ID (7.6). In App Store Connect, open Apps → + → New App and create the app with the same bundle ID.

14.2 Certificates and provisioning profiles

With Automatically manage signing, Xcode creates and renews the distribution certificate and the App Store provisioning profile when you archive.

If your company uses manual signing, untick Automatically manage signing, then in your Apple Developer account:

  1. Create an Apple Distribution certificate (Certificates → +) and install it in your Keychain.
  2. Register the bundle ID (Identifiers → +) with the capabilities you use: Push Notifications and Sign in with Apple.
  3. Create an App Store Connect provisioning profile for it (Profiles → +), download it and choose it in Xcode under Provisioning Profile (Release).

14.3 Capabilities

Push Notifications, plus Sign in with Apple if you use it and App Attest for App Check (section 7.7). With automatic signing, Xcode switches them on for the App ID too.

14.4 Version and build number

Raise version in pubspec.yaml (11.3). App Store Connect refuses a build number it has seen before for the same version.

14.5 Build the archive

flutter clean
flutter pub get
flutter build ipa --release --dart-define=API_BASE_URL=https://your-domain.com

flutter build ipa builds with the Release configuration and creates:

  • build/ios/archive/Runner.xcarchive, the archive Xcode's Organizer opens;
  • build/ios/ipa/, the .ipa file for App Store Connect.

The optional --obfuscate --split-debug-info=build/symbols, API_CERT_PINS and RASP_… settings work the same way as on Android (13.7).

Archiving in Xcode instead: first save the build settings, including the backend address, into the Xcode project:

flutter build ios --release --config-only --dart-define=API_BASE_URL=https://your-domain.com

Then, in Xcode, choose Any iOS Device (arm64) as the run destination and click Product → Archive. Archives use the Release configuration (Product → Scheme → Edit Scheme → Archive).

14.6 Validate and upload

  1. Open the archive in Xcode's Organizer:

    open build/ios/archive/Runner.xcarchive
    
  2. Click Validate App and follow the steps. Validation catches signing and capability problems before Apple's review does.

  3. Click Distribute App, choose App Store Connect and upload.

  4. When the build has been processed in App Store Connect, test it with TestFlight, then add it to an App Store version and submit it for review.

App Store Connect asks about encryption for each build. RideX only uses standard HTTPS.

14.7 App Review notes

  • Give the reviewer a test account that works with your sign-in method. With Firebase phone sign-in, add the number under Phone numbers for testing in Firebase (Demo credentials).
  • If the app offers Google sign-in, Apple asks for Sign in with Apple too (Sign in with Google and Apple).
  • Make sure the permission texts say why the app needs each permission (8.4).

15. Troubleshooting

15.1 flutter doctor

Message Fix
Android license status unknown Install Android SDK Command-line Tools (latest) (5.3), then run flutter doctor --android-licenses and answer y.
Unable to locate Android SDK Install the SDK with Android Studio (5.1), or run flutter config --android-sdk /path/to/sdk (5.4).
Xcode is missing or incomplete Install Xcode, then run sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer and sudo xcodebuild -runFirstLaunch (7.1).
CocoaPods is missing brew install cocoapods (7.2).

15.2 flutter pub get

  • The Dart SDK is too old ("Because ridex_rider requires SDK version ^3.12.2, version solving failed"): install Flutter 3.44 or newer (3.1, 3.2).
  • "N packages have newer versions incompatible with dependency constraints" isn't an error: pubspec.lock keeps the tested versions.
  • packages/ridex_core is missing (could not find package ridex_core at "packages/ridex_core"): keep that folder inside the app folder, where pubspec.yaml points.
  • The editor can't find a package (Target of URI doesn't exist: 'package:…'): run flutter pub get in the app's folder, the one with pubspec.yaml.

15.3 Android build

Message Cause and fix
File google-services.json is missing. The Google Services Plugin cannot function without it. Only a warning: the build goes on without Firebase. Add the file to android/app/ (10.1) for push notifications and instant ride updates.
No matching client found for package name '…' The file was made for another package name. Download the file of the Android app whose package name equals applicationId (10.1).
flutter.sdk not set in local.properties Run flutter pub get in the app folder; it writes android/local.properties.
Failed to install the following Android SDK packages as some licences have not been accepted Run flutter doctor --android-licenses, answer y to each licence, and build again (5.3).
Could not resolve all files for configuration … or Could not GET 'https://…' Gradle couldn't download a dependency. Check the internet connection, VPN or proxy, then build again.
A Java version error from Gradle Use JDK 17 or newer: flutter config --jdk-dir="/path/to/jdk", then flutter doctor -v to check.
The build runs out of memory Close other programs, or lower -Xmx8G in android/gradle.properties, for example to -Xmx4G.
Any other Gradle failure Start clean: flutter clean, flutter pub get, and build again. Run ./gradlew --stop in android/ to stop old Gradle processes.

Warnings about "Built-in Kotlin" or "plugins that apply Kotlin Gradle Plugin (KGP)" are notices for the plugins' authors; the build still succeeds.

15.4 iOS build and CocoaPods

Message Cause and fix
warning: ios/Runner/GoogleService-Info.plist not found, so Firebase (push and live updates) is off. Only a warning: the app builds without Firebase. Add the file to ios/Runner/ (10.2) for push notifications and instant ride updates.
Build input file cannot be found: '…/GoogleService-Info.plist' The file was added to the Runner target in Xcode and then moved or deleted. Put it back in ios/Runner/, or remove it from Runner → Build Phases → Copy Bundle Resources: the Copy Firebase Config phase copies it without that entry (10.2).
Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first Run flutter pub get, then pod install again.
The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation. Run flutter pub get, then pod install in ios/, and build again.
No such module 'Flutter' or Module '…' not found in Xcode Xcode opened Runner.xcodeproj. Open ios/Runner.xcworkspace instead (7.4), after flutter pub get and pod install.
CocoaPods can't find a pod version Update the pod index: cd ios, pod install --repo-update.
CocoaPods requires your terminal to be using UTF-8 encoding Add export LANG=en_US.UTF-8 to ~/.zshrc and open a new terminal.
CocoaPods did not set the base configuration of your project because your project already has a custom config set Expected in this project, for the Profile configuration. Debug and release builds aren't affected.
FirebaseCore has been deprecated in favor of the Firebase Apple SDK via Swift Package Manager A notice from Firebase: the installed versions keep working.
The following plugins do not support Swift Package Manager A notice from Flutter; the apps use CocoaPods.
flutter build ios --simulator fails with Xcode 27 Xcode 27's lipo accepts only one architecture. Run on simulators with flutter run -d <simulator>.

15.5 iOS signing

  • "Signing for "Runner" requires a development team" or Xcode can't find the project's team: choose your own team in Signing & Capabilities, for Runner and RunnerTests (7.6).
  • Xcode can't register the bundle identifier: another developer account uses that ID. Choose a unique one (11.2).
  • Push Notifications or Sign in with Apple can't be added: they need a paid Apple Developer Program team.
  • Manual signing: the profile must match the bundle ID, include your certificate, and allow the capabilities you added (14.2).

15.6 Firebase

  • Check each file's IDs: package_name in google-services.json must equal applicationId, and BUNDLE_ID in GoogleService-Info.plist must equal the Xcode bundle identifier.
  • The debug console prints RideX: Firebase not configured (…); push and live updates are off.: the build has no Firebase files, or a file belongs to another app. After adding or replacing one, stop the app and run it again; a hot restart doesn't pick it up.
  • Sign-in shows "We could not verify your number" and the debug console prints RideX: Firebase phone sign-in is selected in the admin panel, but this app has no Firebase configuration files.: add the Firebase files to both apps (10.1, 10.2), or set Send sign-in codes with back to Twilio (Sign-in codes with Firebase Phone Auth).
  • No push on iOS: the console shows no valid "aps-environment" entitlement string found for application until you add the Push Notifications capability. Also check that the APNs key is uploaded to Firebase (10.2). Without a push token, notifications still appear in the app's inbox.
  • Live updates stop, or Firebase sign-in codes fail, after you enforce App Check: that build isn't verified. Debug builds need a registered debug token, and release builds need Play Integrity or App Attest set up (10.3). Security → App Check → APIs shows how many requests were refused.
  • The debug console prints RideX: App Check not started (…); Firebase requests go without its token.: the App Check plugin isn't in the running build. Stop the app and run it again, because a hot restart doesn't load new plugins (on iOS, run pod install first). Until App Check is enforced, the app works the same either way.
  • App Check shows your release builds as unverified: on Android, check that the Play Integrity API is linked to your Firebase project and that the SHA-256 of Google Play's app signing key is added, and test with a build installed from Google Play, such as an internal testing track. On iOS, check the App Attest capability, its production environment, and that the DeviceCheck key and your Team ID are in Firebase (10.3). Don't enforce App Check until your release builds show up as verified.
  • No push at all: check that the server's Firebase key is uploaded and the rules are deployed (Firebase), and that the backend's queue worker is running (Scheduler and queue worker).

15.7 Backend connection

The app shows a "no connection" or "Something went wrong" message:

  1. Open <API_BASE_URL>/api/v1/config in the phone's browser (9.6). If it doesn't load, the problem is the network or the server, not the app.
  2. Check the address: the site root without /api, and https:// for release builds.
  3. Android emulator: the backend must run on the same computer, which the emulator calls 10.0.2.2.
  4. Android phone over USB: run adb reverse tcp:8000 tcp:8000 again after reconnecting, and pass --dart-define=API_BASE_URL=http://127.0.0.1:8000 (9.3).
  5. With API_CERT_PINS, a server certificate with another key is refused (Certificate pinning).
  6. If the release app stops at launch with API_BASE_URL must be an https:// address in release builds, add the address to the build command (9.5).

15.8 Maps and location

  • The map has no streets: the Maps key is missing, its API isn't enabled, or its restrictions don't match the package name with SHA-1 (Android) or the bundle ID (iOS). On iOS, Xcode's console shows RideX: MAPS_API_KEY missing - set it in ios/Flutter/Secrets.xcconfig; maps will not load. Fully stop and rerun the app after changing a key.
  • "Location isn't available" on an emulator or simulator: give it a location (12.1, 12.2).
  • The demo driver can't go online on a simulator: set TRACKING_BLOCK_MOCK_LOCATIONS=false in the test backend's .env (section 12).
  • The address or route is missing: those come from the backend's server key (Google Maps).

16. Important Security Notes

Never share these, commit them to a public repository, or show them in screenshots:

File or value Where it belongs
android/key.properties, your .jks keystore and their passwords Only on the computer that builds releases, with a backup kept safely
Firebase service account key (.json with a private key) Only in Admin → Firebase Configuration. Never in the apps
APNs and DeviceCheck keys (.p8) Only in the Firebase console
App Check debug tokens The Firebase console and your own debug runs. Never in a release build or a repository
android/secrets.properties, ios/Flutter/Secrets.xcconfig Your build computer. They hold your Maps keys
google-services.json, GoogleService-Info.plist Your build computer. Not secret, but they connect the apps to your Firebase project
Payment, SMS and server Maps keys Only on the backend
build/symbols (obfuscation symbols) Private storage, to read crash reports

Also:

  • Anyone can read keys inside an app, so restrict the Maps keys to your package names, SHA-1 fingerprints and bundle IDs (6.2, 8.2).
  • Use https:// for the backend. Release builds refuse anything else.
  • Enforce Firebase App Check once your store builds are verified, so only your genuine apps can use the database and Firebase sign-in (10.3).
  • Switch off the backend's demo mode before going live (Going live checklist).
  • Cover account names, team IDs and keys before you share screenshots of the Firebase console, Google Cloud, Xcode or Play Console.

17. Support Information

  • Contact: WhatsApp, email and what to tell us are in Support & Contact. For the apps, also send the output of flutter doctor -v.
  • Third-party licences: see Credits & licenses.

Rider app

The passenger-facing Flutter application for the RideX ride-hailing platform.

RideX Rider is built with Flutter 3.44+, GetX, Dio, and Google Maps. It shares its core functionality with the RideX Driver application through the reusable ridex_core package located at packages/ridex_core.


✨ Overview

The Rider app provides the complete passenger experience, including:

  • 🔐 Authentication and account management
  • 📍 Location search and saved places
  • 🚕 On-demand, scheduled, rental, and outstation rides
  • 🗺️ Live ride tracking
  • 💳 Payments, wallet, tips, and receipts
  • 💬 Driver chat and calling
  • 🆘 Emergency/SOS functionality
  • 🎁 Promotions and referral features
  • ⭐ Ratings and reviews
  • 🌐 Multi-language support
  • 🔔 Push notifications and live ride updates
  • 🛡️ Device and transport security features

The app is designed to be backend-configurable, allowing branding, features, payment methods, languages, and other behavior to be controlled from the RideX administration system.


🚀 Setup

For the complete step-by-step setup, from installing Flutter to store releases, follow the Mobile app setup guide.

1. Install Dependencies

Install the Flutter packages before running the application:

flutter pub get

2. Configure Google Maps

Google Maps API key files are intentionally gitignored so credentials are never committed to version control.

iOS

Add your Maps SDK key to:

ios/Flutter/Secrets.xcconfig
MAPS_API_KEY=...

Android

Add your Maps SDK key to:

android/secrets.properties
MAPS_API_KEY=...

🔐 API Key Security

Use restricted Google Maps SDK keys:

  • iOS: Restrict by Bundle ID.
  • Android: Restrict by package name + SHA-1.

Important: Place search and routing requests go through the backend. The backend uses its own server-side Google Maps key (GOOGLE_MAPS_SERVER_KEY in ridex_backend/.env), so the Rider app does not ship a key capable of directly calling Google Maps web APIs.


3. Configure the Backend URL

The default local backend addresses are:

Environment Backend URL
iOS Simulator http://127.0.0.1:8000
Android Emulator http://10.0.2.2:8000

Physical Android Device

When developing on a physical Android phone over USB:

adb reverse tcp:8000 tcp:8000

Run this again after reconnecting the device.

Then launch the app with:

flutter run --dart-define=API_BASE_URL=http://127.0.0.1:8000

In Android Studio, this can be added under:

Run → Edit Configurations → Additional run args

Without this configuration, the app attempts to connect to the emulator-only address and may display "Something went wrong".

Other Environments

Release and non-local environments must explicitly provide the backend URL.

flutter run --dart-define=API_BASE_URL=https://api.yourdomain.com

Release builds require an https:// API_BASE_URL. Without one, the application stops at launch with API_BASE_URL must be an https:// address in release builds.


4. Configure Firebase

Firebase is used for:

  • Push notifications
  • Live ride updates

Firebase is optional: without the two files below, the app still builds and runs, polls for ride updates, and shows notifications only in the in-app inbox. Add them for push notifications and instant updates.

Firebase Configuration Files

Platform File
iOS ios/Runner/GoogleService-Info.plist
Android android/app/google-services.json

Download both files from your own Firebase project; the package doesn't include them.

Backend Configuration

The backend also requires:

  • Firebase service-account credentials
  • Realtime Database rules

The database rules are located at:

ridex_backend/firebase/database.rules.json

iOS Push Notifications

iOS push notifications additionally require a paid Apple Developer team.

In Xcode:

  1. Open the Runner target.
  2. Go to Signing & Capabilities.
  3. Add Push Notifications.
  4. Upload an APNs key in the Firebase Console under: Project settings → Cloud Messaging

The Remote Notifications background mode is already configured in Info.plist.

Without an APNs key, iOS devices do not receive a push token. Notifications will still appear in the in-app inbox, and the open trip screen continues to post ride updates and chat messages as notifications.

Verify Firebase

To verify the configuration:

  1. Sign in on a real phone.
  2. Open Admin → Firebase Configuration.
  3. Confirm that Connected apps includes the phone.
  4. Open Admin → Push Notifications.
  5. Send a test notification to yourself.

Tapping a notification, or an entry in the in-app inbox, opens what it is about: the ride or its chat, the receipt, the wallet, a support ticket or Lost & Found.

If notifications are blocked on the phone, the home screen says so, with a Turn on button that asks again or opens the app's notification settings.

If Firebase is missing or incorrectly configured, the debug console prints:

RideX: Firebase not configured (…)

App Check

App Check lets Firebase accept database and sign-in requests only from your genuine app. The app turns it on at start-up: release builds attest with Play Integrity on Android and App Attest on iOS (DeviceCheck before iOS 14), and debug and profile builds use a debug token. Nothing is blocked until you enforce it in the Firebase Console.

  1. Android: in the Google Play Console, open the app, go to Release → App integrity → Play Integrity API and click Link Cloud project to link your Firebase project. In Firebase, add the SHA-256 fingerprint of Google Play's app signing key to the Android app (Project settings → Your apps), then register it under Security → App Check → Apps with Play Integrity.

  2. iOS (paid Apple Developer team): in Xcode, add App Attest under Runner → Signing & Capabilities, then set App Attest Environment to production in Runner/Runner.entitlements. Create a DeviceCheck key in your Apple Developer account (Certificates, Identifiers & Profiles → Keys → +). In Firebase, enter your Team ID in the iOS app's settings, then register the app under Security → App Check → Apps with App Attest and with DeviceCheck (the .p8 key and its Key ID).

  3. Debug builds: make a token with uuidgen, add it under Security → App Check → Apps → ⋮ → Manage debug tokens, and pass it when you run:

    flutter run --dart-define=API_BASE_URL=http://10.0.2.2:8000 --dart-define=APP_CHECK_DEBUG_TOKEN=YOUR_DEBUG_TOKEN
    

    Without it, the SDK makes a token for each device and prints it (Android Logcat: Firebase App Check debug token: …). Never pass APP_CHECK_DEBUG_TOKEN to a release build.

  4. Enforce App Check for Realtime Database and Authentication under Security → App Check → APIs once the metrics show your store builds as verified. It takes up to 15 minutes; then check that a booked ride still updates live.

A build that App Check rejects still signs in to the backend and polls for ride updates; only Firebase phone sign-in codes stop working once Authentication is enforced. Full steps: Mobile app setup guide → 10.3 App Check.


5. Configure Google, Apple & Firebase Phone Authentication

These authentication methods are optional and are enabled from:

Admin → Maps, SMS & Sign-in

The backend setup is documented in DEPLOYMENT.md under:

  • Sign in with Google and Apple
  • Sign-in codes with Firebase Phone Auth

iOS Configuration

Add the following values to:

ios/Flutter/Secrets.xcconfig

See:

ios/Flutter/Secrets.xcconfig.example

Required values:

Variable Source
GOOGLE_IOS_CLIENT_ID CLIENT_ID from GoogleService-Info.plist
GOOGLE_REVERSED_CLIENT_ID REVERSED_CLIENT_ID from GoogleService-Info.plist
FIREBASE_ENCODED_APP_ID Encoded App ID of the iOS Firebase app

FIREBASE_ENCODED_APP_ID is required for Firebase phone authentication when push notifications are not configured.

Sign in with Apple

Also enable the Apple capability in:

Xcode → Runner → Signing & Capabilities


6. Run the Application

With RIDEX_DEMO_MODE=true enabled on the backend, you can use the demo account:

Phone: +1 5550000001
Code:  123456

Then start the application:

flutter run

On a simulator or emulator, set a location first (iOS Simulator: Features → Location → Custom Location…; Android emulator: Extended controls → Location). Without one the home map stays zoomed out to the whole world, and after 20 seconds the pickup line says the location isn't available; tap it to try again.


📦 Release Builds

Before creating a production build, complete the following steps.

1. Configure Android Signing

Copy:

android/key.properties.example

to:

android/key.properties

Then fill in your upload keystore configuration.

The file is gitignored.

Important: Without a production upload keystore, release builds are signed with the debug key, which Google Play rejects.


2. Build Android & iOS Releases

Always provide the production backend URL.

Android — App Bundle

flutter build appbundle \
  --release \
  --obfuscate \
  --split-debug-info=build/symbols \
  --dart-define=API_BASE_URL=https://api.yourdomain.com

iOS — IPA

flutter build ipa \
  --release \
  --obfuscate \
  --split-debug-info=build/symbols \
  --dart-define=API_BASE_URL=https://api.yourdomain.com

Obfuscation & Debug Symbols

--obfuscate renames Dart classes and functions in the compiled application.

The corresponding symbols are generated in:

build/symbols

These symbols are required to translate crash stack traces back into readable names using:

flutter symbolize

Keep the symbol files for every published version, but never ship them with the application.

Obfuscation does not hide strings such as your API address. Never embed secrets directly into the application.


3. Optional Certificate Pinning

You can pin your server's public key using:

--dart-define=API_CERT_PINS=<primary>,<backup>

For implementation details, see Certificate pinning in the backend's DEPLOYMENT.md.


4. Optional Device Security Checks

For full freeRASP device-security checks, configure:

RASP_WATCHER_EMAIL
RASP_ANDROID_SIGNING_HASHES
RASP_IOS_TEAM_ID

Without these values, only the basic root/jailbreak check is performed.

See Device security checks in the backend's DEPLOYMENT.md.


🎨 White-Labeling

RideX Rider is designed for white-label deployments.

The following values are retrieved from the backend through:

GET /api/v1/config

They include:

  • Application name
  • Brand color
  • Currency
  • Languages
  • Feature flags

The configuration is cached locally, allowing the application to start with its branding even when offline.

A rebrand performed from the admin panel does not require a new application build for backend-controlled branding and features.


App Name

To change the name displayed below the application icon:

iOS

Update:

CFBundleDisplayName
CFBundleName

in:

ios/Runner/Info.plist

Android

Update:

android:label

in:

android/app/src/main/AndroidManifest.xml

App Icon & Splash Screen

Branding assets are located in:

assets/branding/

Replace the existing images while preserving their required dimensions.

File Size Purpose
icon.png 1024 × 1024 iOS icon and legacy Android icon
icon_foreground.png 1024 × 1024 Android adaptive/themed icon
splash.png 1152 × 1152 Launch screen logo

Icon Guidelines

icon.png

  • 1024 × 1024
  • No transparency

icon_foreground.png

  • 1024 × 1024
  • Transparent background
  • Keep artwork within the middle two-thirds

splash.png

  • 1152 × 1152
  • Transparent background
  • Keep artwork inside the central 768 px circle

The background colors are configured in pubspec.yaml through:

  • flutter_launcher_icons
  • flutter_native_splash

The .svg files located alongside the PNG assets are the editable source files.

After replacing the assets, regenerate all Android and iOS sizes:

dart run flutter_launcher_icons && dart run flutter_native_splash:create

📱 Change the Package / Bundle ID

Every store application requires its own unique identifier.

The default Rider application ID is:

com.codercampapp.ridex.rider

Replace it with your own identifier, for example:

com.yourcompany.taxi

Important: Choose the production application ID carefully. Store application IDs cannot be changed after the application has been published.


1. Android

Open:

android/app/build.gradle.kts

Update both:

namespace
applicationId

Then move:

android/app/src/main/kotlin/com/codercampapp/ridex/rider/MainActivity.kt

to a directory matching your new package name.

For example:

android/app/src/main/kotlin/com/yourcompany/taxi/

Update the first line of MainActivity.kt:

package com.yourcompany.taxi

2. iOS

Open:

ios/Runner.xcworkspace

In Xcode:

  1. Select the Runner target.
  2. Open Signing & Capabilities.
  3. Select your Team.
  4. Set your Bundle Identifier.

Repeat the process for the RunnerTests target.

The test target should use your application ID followed by:

.RunnerTests

3. Firebase

Add the new Android and iOS applications to your Firebase project.

Then download the new configuration files and replace:

android/app/google-services.json
ios/Runner/GoogleService-Info.plist

If Google Sign-In is enabled, also copy the new:

CLIENT_ID
REVERSED_CLIENT_ID

values into:

ios/Flutter/Secrets.xcconfig

4. Google Cloud

Update your Google Maps API key restrictions with:

Android

  • New package name
  • SHA-1 of the signing key

iOS

  • New Bundle ID

5. Backend

If Google or Apple Sign-In is enabled, update the backend .env file:

GOOGLE_CLIENT_IDS
APPLE_CLIENT_IDS

Add the new application IDs as required.


Verify the New Package Name

After completing the changes:

flutter clean && flutter run

Confirm that the renamed application launches correctly.


🏗️ Architecture

RideX Rider follows a feature-based architecture with a clear separation between domain, data, and presentation layers.

lib/
├── main.dart
│   └── Startup: RideXCore.bootstrap + rider repositories
│
├── config/
│   └── Build configuration, routes, pages and bindings
│
├── localization/
│   └── Rider-specific strings merged over ridex_core shared strings
│
└── features/
    └── <feature>/
        ├── domain/
        │   └── Entities + repository contracts
        │       └── No Flutter or network imports
        │
        ├── data/
        │   └── Repository implementations over ApiClient
        │
        └── presentation/
            └── GetX controllers + pages

Architectural Principles

  • Domain contains business entities and repository contracts.
  • Data contains repository implementations and API integration.
  • Presentation contains GetX controllers and UI pages.
  • Shared functionality lives inside ridex_core.
  • Rider-specific features remain isolated under lib/features/.

🧩 Features

The Rider application contains the following feature modules:

Feature Description
auth Authentication and profile
home Rider home screen
places Place search and saved places
booking Ride types, vehicle selection, stops, payments and promotions
ride_tracking Live ride tracking
payments Post-ride payment, tips, receipts and saved cards
promotions Promotional features
safety Emergency contacts
lost_items Lost-item reporting

The following shared functionality is provided by ridex_core:

  • Login
  • Ride history
  • Ride details
  • Driver chat
  • SOS
  • Ratings
  • Help & Support
  • Settings
  • Wallet
  • Refer & Earn
  • Notifications
  • Networking
  • Theme
  • Shared widgets

🛡️ Rider Experience

Safety & SOS

The SOS button on the trip map:

  1. Alerts the safety team.
  2. Sends emergency contacts a live trip link.
  3. Offers to call the local emergency number.

Emergency contacts can be managed from:

Safety

Contacts with Share my trips automatically enabled receive the live trip link by SMS whenever a trip starts.


During a Trip

Passengers can:

  • Call the driver
  • Chat with the driver
  • View unread message badges
  • Use quick replies
  • See read receipts
  • Share their trip
  • Cancel the ride

The cancel sheet shows the cancellation fee, or how long cancelling stays free after the driver accepts (a live countdown).


After a Trip

Passengers can rate the driver using:

  • Compliment tags
  • Written reviews

From My rides, passengers can also:

  • Track a ride in progress
  • Pay for a card or online ride that is still unpaid
  • View the receipt
  • Report a lost item
  • Get help with the trip

Fare disputes automatically attach the relevant trip and amount.


Profile

Tap your name in the side menu to change your profile photo (camera or gallery), name, email and address. Drivers see your photo on the ride request, during the trip and in chat, and you see theirs.


Saved Places

Passengers can create and manage:

  • Home
  • Work
  • Other saved places

Saved places can be:

  • Added
  • Renamed
  • Moved
  • Removed

They are displayed as shortcuts on the home screen and during search.


Home Screen Banners

Administrators can configure banners displayed beneath the booking buttons.

A banner can:

  • Open an external link
  • Navigate to an application screen
  • Open Wallet
  • Open Promotions
  • Open Safety
  • Preserve a promo code for the next booking

🚕 Ride Types

Ride types are displayed as chips on the home screen and are only shown when enabled by the administrator.

Ride

Books a ride immediately.

Schedule

Allows passengers to select a pickup time within the configured administrative window.

Scheduled bookings appear under My rides and can be cancelled until driver searching begins shortly before pickup.

Rental

Books a vehicle by the hour.

Passengers select a rental package, with an optional drop-off location.

Outstation

Books an intercity trip:

  • One-way
  • Round-trip
  • Multi-day

The trip leaves now, or at a departure time the passenger picks. A trip for later waits under My rides like a scheduled ride.


📋 Booking Extras

Passengers can provide additional booking information, including:

  • A note for the driver, such as a gate or landmark
  • Booking for someone else
  • Passenger name and phone number for another rider
  • Airport pickup/drop-off information

When a booking involves an airport, the app displays the applicable airport fee notice.


🔐 Sign-In Options

Depending on administrator configuration, users can sign in using:

  • Phone number + verification code
  • Firebase Phone Auth
  • Google
  • Apple

Apple Sign-In is available on iPhone only.

A Google or Apple registration also requires a verified phone number before the account can proceed.


👤 Account Management

From:

Settings → Delete account

Users can permanently remove their account after confirmation.

Account deletion is unavailable while the user is currently on a ride.

Users can also control account email preferences through:

Settings → Emails

This controls receipt and account-related emails.


🔄 Force Updates

Administrators can increase the minimum supported application version.

When an older build is no longer supported:

  1. The application stops at launch.
  2. The user sees an Update now button.
  3. The button opens the appropriate application store.

⚠️ Error Handling

API failures are converted into:

AppException

Each exception carries:

  • The server-provided message
  • error_code

The UI displays errors through:

AppFeedback

or:

ErrorView

ErrorView provides a retry action when applicable.


📡 Live Ride Updates

Live ride updates are abstracted behind:

RideUpdates

The application supports two implementations:

FirebaseRideUpdates

When Firebase is available, the application listens to the ride's Firebase node.

  • Ride status changes trigger a full ride refresh.
  • Driver location changes are applied directly from the Firebase feed.

PollingRideUpdates

If Firebase is unavailable or listening fails—for example, when database rules have not been deployed—the application automatically falls back to polling.

The ride is refreshed according to:

tracking.location_interval_seconds

This provides a fallback mechanism without requiring Firebase for basic ride tracking.


💳 Payments

Payment methods are dynamically controlled by:

GET /payments/methods

The payment picker, wallet top-up flow, and tip sheet only display payment methods enabled through:

Admin → Payment Configuration

Payment methods are not hard-coded into the Rider application.


Card & Online Payments

Card and online payment methods open the provider's hosted checkout page inside:

PaymentCheckout

After the provider redirects back:

  1. The web view closes.
  2. The app requests the payment result from the backend.

Blocked Bookings

When a booking is blocked because of an outstanding balance or insufficient wallet funds, the application explains the reason and provides the appropriate resolution, such as:

  • Adding money to the wallet
  • Clearing outstanding dues
  • Paying for the previous unpaid ride

Demo Payments

When the backend is running in demo mode, the Demo payment gateway allows payment flows to be tested without real payment credentials.


🌍 Localization

Rider-specific translations are maintained in:

lib/localization/rider_translations.dart

These translations are merged over the shared strings provided by ridex_core.

Supported Languages

The following languages are complete:

  • 🇬🇧 English
  • 🇸🇦 Arabic
  • 🇪🇸 Spanish
  • 🇫🇷 French
  • 🇧🇩 Bengali

Arabic includes right-to-left support.


Translation Validation

Translation completeness is checked by:

test/translations_test.dart

The test fails when:

  • A language is missing a string.
  • A @placeholder is lost.

The application selects its initial language using this order:

  1. The phone's language, when enabled by the administrator.
  2. Otherwise, the administrator's configured default language.

The selected language is persisted through Settings.

To add a new language, see Adding a language in the backend README.


🧪 Testing

Run static analysis:

flutter analyze

Run the complete test suite:

flutter test

You can run both before submitting changes:

flutter analyze && flutter test

📁 Important Project Locations

Path Purpose
lib/ Rider application source
lib/features/ Rider-specific feature modules
lib/config/ Routes, pages, bindings and build configuration
lib/localization/ Rider translations
packages/ridex_core/ Shared RideX application core
assets/branding/ Application icons and splash assets
ios/Flutter/Secrets.xcconfig iOS local secrets/configuration
android/secrets.properties Android local secrets/configuration
android/key.properties Android release signing configuration
ios/Runner/Info.plist iOS application configuration
android/app/src/main/AndroidManifest.xml Android application configuration

🔒 Security Checklist

Before creating a production build, verify that:

  • Google Maps SDK keys are restricted.
  • Server-side Google Maps keys remain on the backend.
  • API secrets are not committed to Git.
  • Secrets.xcconfig is not committed.
  • secrets.properties is not committed.
  • key.properties is not committed.
  • Production builds use HTTPS.
  • A production upload keystore is configured.
  • Release builds use --obfuscate.
  • Debug symbols are securely archived.
  • Debug symbols are not included in the released application.
  • Firebase configuration belongs to the correct application IDs.
  • App Check is registered for Android and iOS, and enforced once the store builds are verified.
  • Google Maps restrictions match the production package/bundle IDs.
  • Google/Apple client IDs are correctly configured in the backend.
  • Certificate pinning is configured if required.
  • Production RASP values are configured if full device-security checks are required.

🛠️ Development Quick Start

For a typical local development environment:

# Install dependencies
flutter pub get

# Run static analysis
flutter analyze

# Run tests
flutter test

# Start the application
flutter run

For Android emulator development, the backend normally runs at:

http://10.0.2.2:8000

For iOS Simulator development:

http://127.0.0.1:8000

For a physical Android device:

adb reverse tcp:8000 tcp:8000

flutter run \
  --dart-define=API_BASE_URL=http://127.0.0.1:8000

For a production-style build:

flutter build appbundle \
  --release \
  --obfuscate \
  --split-debug-info=build/symbols \
  --dart-define=API_BASE_URL=https://api.yourdomain.com

📌 Notes

  • Backend configuration controls many Rider features dynamically.
  • Firebase is optional: without its files the app still builds, and ride updates fall back to polling.
  • Payment methods are controlled by the backend.
  • Branding can be updated through backend configuration without rebuilding for backend-controlled values.
  • Application IDs must be configured before the first store release.
  • Never embed server-side secrets into the mobile application.

🧪 Final Pre-Release Checklist

Before publishing a Rider build:

Configuration

  • Production API_BASE_URL configured
  • Firebase configured
  • Google Maps configured
  • Google/Apple Sign-In configured if enabled
  • Production package/bundle IDs configured

Branding

  • App name updated
  • App icon replaced
  • Splash screen replaced
  • Branding assets regenerated

Android

  • Production signing key configured
  • SHA-1 registered with Google Cloud
  • Firebase Android app updated
  • Release App Bundle generated

iOS

  • Apple Developer team configured
  • Bundle ID configured
  • Push Notifications capability configured
  • APNs key configured in Firebase
  • Firebase iOS configuration updated
  • IPA generated

Security

  • No secrets committed
  • API keys restricted
  • HTTPS backend configured
  • Debug symbols archived securely
  • RASP configured if required
  • Certificate pinning configured if required
  • App Check registered, and enforced once the store builds are verified

Quality

  • flutter analyze passes
  • flutter test passes
  • Authentication tested
  • Booking tested
  • Live tracking tested
  • Payment flows tested
  • Push notifications tested
  • SOS tested
  • Production build tested on a physical device

RideX Rider — a configurable, white-label Flutter passenger application for the RideX platform.

Driver app

The driver-facing Flutter application for the RideX ride-hailing platform.

RideX Driver is built with Flutter 3.44+, GetX, Dio, and Google Maps. It shares its core functionality with the Rider application through the reusable ridex_core package located at packages/ridex_core.

Shared functionality includes:

  • Authentication
  • Networking
  • Configuration
  • Theme
  • Ride models
  • Ride history
  • Settings

The ridex_core package exists as an identical copy in both the Rider and Driver applications.


✨ Overview

RideX Driver provides the complete driver experience, including:

  • 🔐 Driver registration and onboarding
  • 📋 KYC and document verification
  • 🚗 Vehicle management
  • 🟢 Online/offline availability
  • 📍 Background location tracking
  • 🔥 Demand heatmaps and surge zones
  • 🚕 Real-time ride offers
  • 🗺️ Trip navigation and management
  • 💬 Rider calling and chat
  • 🆘 SOS and safety features
  • 💰 Earnings and incentives
  • 💳 Driver wallet and withdrawals
  • 📦 Lost-item management
  • 🎫 Support tickets
  • 🔔 Push notifications
  • 🌐 Multi-language support

🚀 Setup

For the complete step-by-step setup, from installing Flutter to store releases, follow the Mobile app setup guide.

1. Install Dependencies

Install the Flutter packages:

flutter pub get

2. Configure Google Maps

Google Maps API key files are gitignored so credentials never reach version control.

iOS

Add the Maps SDK key to:

ios/Flutter/Secrets.xcconfig
MAPS_API_KEY=...

Android

Add the Maps SDK key to:

android/secrets.properties
MAPS_API_KEY=...

🔐 API Key Restrictions

Use Maps SDK for iOS / Android keys restricted to the Driver application's identifiers.

Default application ID:

com.codercampapp.ridex.driver

Configure the restrictions using:

  • iOS: Bundle ID
  • Android: Package name + signing SHA-1

Without a Maps key, the application can still run, but the map will not display map tiles.


3. Configure the Backend URL

The default local backend addresses are:

Environment Backend URL
iOS Simulator http://127.0.0.1:8000
Android Emulator http://10.0.2.2:8000

Physical Android Device

When developing with a physical Android phone over USB, forward the backend port:

adb reverse tcp:8000 tcp:8000

Run this again whenever the device is reconnected.

Then start the application with:

flutter run --dart-define=API_BASE_URL=http://127.0.0.1:8000

In Android Studio, the argument can be configured under:

Run → Edit Configurations → Additional run args

Without this configuration, the application waits for the emulator-only address and may display "Something went wrong".

Other Environments

For staging or production environments:

flutter run --dart-define=API_BASE_URL=https://api.yourdomain.com

Release builds require an https:// API_BASE_URL. Without one, the application stops at launch with API_BASE_URL must be an https:// address in release builds.


🔥 Firebase

Firebase provides:

  • Push notifications
  • Live ride updates
  • Instant driver ride offers

Firebase is optional: the app still builds and runs without the two files below.

Without them:

  • Ride updates fall back to polling.
  • Notifications are available through the in-app inbox.

Firebase Configuration Files

Platform File
iOS ios/Runner/GoogleService-Info.plist
Android android/app/google-services.json

Download both files from your own Firebase project; the package doesn't include them.


Backend Firebase Configuration

The backend requires:

  • Firebase service-account credentials
  • Realtime Database rules

The database rules are located at:

ridex_backend/firebase/database.rules.json

iOS Push Notifications

iOS push notifications require a paid Apple Developer team.

In Xcode:

  1. Open the Runner target.
  2. Go to Signing & Capabilities.
  3. Add Push Notifications.
  4. Upload an APNs key in Firebase: Project settings → Cloud Messaging

The Remote Notifications background mode is already configured in Info.plist.

Without an APNs key, iOS does not receive a push token. Notifications still appear in the in-app inbox.

Tapping a notification, or an entry in the in-app inbox, opens what it is about: the wallet (fares paid later, tips), payouts, documents, vehicles, incentives, a support ticket or Lost & Found. New ride requests and a trip in progress show by themselves, and nothing opens over a trip.


Verify Firebase

To verify Firebase and real-time ride offers:

  1. Sign in on a physical phone.
  2. Go online.
  3. Open: Admin → Firebase Configuration
  4. Check Connected apps.
  5. Confirm that the phone is listed.
  6. Create a ride offer and verify that it arrives immediately.

If Firebase is missing or incorrectly configured, the debug console prints:

RideX: Firebase not configured (…)

App Check

App Check lets Firebase accept database and sign-in requests only from your genuine app. The app turns it on at start-up: release builds attest with Play Integrity on Android and App Attest on iOS (DeviceCheck before iOS 14), and debug and profile builds use a debug token. Nothing is blocked until you enforce it in the Firebase Console.

  1. Android: in the Google Play Console, open the app, go to Release → App integrity → Play Integrity API and click Link Cloud project to link your Firebase project. In Firebase, add the SHA-256 fingerprint of Google Play's app signing key to the Android app (Project settings → Your apps), then register it under Security → App Check → Apps with Play Integrity.

  2. iOS (paid Apple Developer team): in Xcode, add App Attest under Runner → Signing & Capabilities, then set App Attest Environment to production in Runner/Runner.entitlements. Create a DeviceCheck key in your Apple Developer account (Certificates, Identifiers & Profiles → Keys → +). In Firebase, enter your Team ID in the iOS app's settings, then register the app under Security → App Check → Apps with App Attest and with DeviceCheck (the .p8 key and its Key ID).

  3. Debug builds: make a token with uuidgen, add it under Security → App Check → Apps → ⋮ → Manage debug tokens, and pass it when you run:

    flutter run --dart-define=API_BASE_URL=http://10.0.2.2:8000 --dart-define=APP_CHECK_DEBUG_TOKEN=YOUR_DEBUG_TOKEN
    

    Without it, the SDK makes a token for each device and prints it (Android Logcat: Firebase App Check debug token: …). Never pass APP_CHECK_DEBUG_TOKEN to a release build.

  4. Enforce App Check for Realtime Database and Authentication under Security → App Check → APIs once the metrics show your store builds as verified. It takes up to 15 minutes; then go online and check that a new ride offer still arrives at once.

A build that App Check rejects still signs in to the backend and polls for ride offers and trip updates; only Firebase phone sign-in codes stop working once Authentication is enforced. Full steps: Mobile app setup guide → 10.3 App Check.


🔐 Authentication

Google Sign-In, Apple Sign-In, and Firebase phone authentication are optional.

The setup is the same as the Rider application, but the Driver app must use its own GoogleService-Info.plist values in:

ios/Flutter/Secrets.xcconfig

Authentication methods can be enabled from the backend administration configuration.


🧪 Demo Mode

With:

RIDEX_DEMO_MODE=true

enabled on the backend, the following driver accounts are available.

Phone Code Status
+1 5550000002 123456 Approved driver
+1 5550000003 123456 Application under review

Any other phone number creates a new driver account.

When:

OTP_PROVIDER=log

is configured, the OTP is written to:

storage/logs/laravel.log

Run the application:

flutter run

On a simulator or emulator, set a location before going online (iOS Simulator: Features → Location → Custom Location…; Android emulator: Extended controls → Location). Without one, Go online reports after 20 seconds that the location isn't available.


🍎 Xcode 27 Simulator Note

When using Xcode 27, run a specific simulator with:

flutter run -d <simulator>

flutter build ios --simulator fails with Xcode 27 because its lipo command accepts only one architecture.


📦 Release Builds

1. Configure Android Signing

Copy:

android/key.properties.example

to:

android/key.properties

Then configure your upload keystore.

The file is gitignored.

Important: Without a production upload keystore, release builds are signed with the debug key, which Google Play rejects.


2. Build Production Applications

Always provide the production backend URL.

Android

flutter build appbundle \
  --release \
  --obfuscate \
  --split-debug-info=build/symbols \
  --dart-define=API_BASE_URL=https://api.yourdomain.com

iOS

flutter build ipa \
  --release \
  --obfuscate \
  --split-debug-info=build/symbols \
  --dart-define=API_BASE_URL=https://api.yourdomain.com

🔒 Obfuscation & Debug Symbols

The --obfuscate option renames Dart classes and functions in the compiled application.

Debug symbols are written to:

build/symbols

These symbols allow crash stack traces to be converted back into readable names with:

flutter symbolize

Keep debug symbols for every published version, but never ship them with the application.

Obfuscation does not hide values such as your API address. Never embed secrets into the mobile application.


3. Optional Certificate Pinning

Pin the server's public key using:

--dart-define=API_CERT_PINS=<primary>,<backup>

See Certificate pinning in the backend's DEPLOYMENT.md for details.


4. Optional Device Security Checks

For complete freeRASP checks, configure:

RASP_WATCHER_EMAIL
RASP_ANDROID_SIGNING_HASHES
RASP_IOS_TEAM_ID

Without these values, only the basic root/jailbreak detection runs.

See Device security checks in the backend's DEPLOYMENT.md.


🎨 App Icon & Splash Screen

The Driver application uses the dark version of the brand mark so riders and drivers can visually distinguish the two applications.

Branding assets are located in:

assets/branding/

Replace:

icon.png
icon_foreground.png
splash.png

Use the same dimensions and asset rules as the Rider application.

You can update the background colors in:

pubspec.yaml

Then regenerate the application assets:

dart run flutter_launcher_icons && dart run flutter_native_splash:create

📱 App Name & Package Name

App Name

The name shown underneath the application icon is configured using:

iOS

ios/Runner/Info.plist

Update:

CFBundleDisplayName
CFBundleName

The default name is:

RideX Driver

Android

Update:

android/app/src/main/AndroidManifest.xml

using:

android:label

📦 Change the Package / Bundle ID

The default Driver application ID is:

com.codercampapp.ridex.driver

Replace it before the first production release.

Use the same procedure documented in the Rider README under:

Change the package name

For the Driver application, the relevant paths are:

android/app/build.gradle.kts

and:

android/app/src/main/kotlin/com/codercampapp/ridex/driver/MainActivity.kt

Important: Rider and Driver applications must use different application IDs.

You also need separate Firebase applications for Rider and Driver.


🧪 Testing on Simulator & Emulator

Simulators report GPS coordinates as mocked locations.

By default, the backend rejects mocked locations as part of its anti-spoofing protection.

For local development only, set the following in the backend .env:

TRACKING_BLOCK_MOCK_LOCATIONS=false

Do not disable mock-location protection in production.


🚘 Simulate a Drive on iOS

To simulate movement on the iOS simulator:

xcrun simctl location booted start --speed=15 40.7590,-73.9845 40.7527,-73.9850 40.7484,-73.9857

This allows the Driver application to receive simulated GPS movement.


✨ Features

📋 Driver Onboarding & KYC

Driver onboarding is driven by:

GET /driver/onboarding

This means new required document types can be introduced without requiring an application update.

Personal Details

Drivers provide:

  • Profile photo (camera or gallery; riders see it once the driver is approved)
  • Name
  • City
  • Address (optional)
  • Date of birth

After verification, the photo, name and date of birth are changed by support. The address can still be edited from the profile (tap the name in the side menu).

Drivers must be 18 years or older.

Documents

Documents support:

  • Camera upload
  • Gallery upload
  • Front side
  • Back side
  • Expiry dates

Rejected or expired documents display the reason and can be uploaded again.

Vehicle

The onboarding flow requires the driver's first vehicle.

Application Review

Drivers submit their onboarding application for review.

While the application is under review, the checklist is locked.

Once approved, the driver proceeds directly to the home screen.


🏠 Driver Home

The Driver home screen provides:

  • 🟢 Online/offline status
  • 📊 Today's trips
  • 💰 Today's earnings
  • ⏱️ Online time
  • ⭐ Driver rating
  • ✅ Acceptance rate
  • 🔥 Demand heatmap
  • 📈 Current surge zones
  • 📢 Administrative banners

Online / Offline

Drivers can switch between online and offline states with the switch at the top of the home panel (also in the side menu). While offline, a Go online button sits at the bottom of the panel.

If going online is refused, the application displays the reasons.

Going offline requires confirmation.

Demand Heatmap

The map displays:

  • Current demand
  • Surge zones
  • Current surge pricing
  • Highest active multiplier

Admin Banners

Administrators can display banners above the online button.

A banner can open:

  • An external link
  • Earnings
  • Incentives
  • Wallet
  • Other configured screens

📍 Driver Location Tracking

When the driver is online, GPS coordinates are streamed and uploaded in batches according to:

tracking.location_interval_seconds

Location tracking continues in the background.

Stationary Drivers

When the driver is stationary, the last known location is re-sent so dispatch continues to see the driver.

Rejected Locations

Rejected GPS fixes—for example, mocked locations—are reported to the driver once.


Android Background Location

Android tracking uses a location foreground service.

The service displays the "you are online" notification.

The application therefore does not require a separate background-location permission.

Google Play Configuration

In the Play Console, declare the:

Location foreground service type

under:

App content → Foreground service permissions

You must also attach a short video demonstrating a driver going online.


🚕 Ride Offers

Ride offers provide the driver with the information required to accept or decline a trip.

An offer can include:

  • Countdown
  • Estimated earnings
  • Pickup distance
  • ETA
  • Route
  • Stops
  • Payment method
  • Pickup note
  • Ride type

The following scheduled ride types display additional information:

  • Scheduled
  • Rental
  • Outstation

For example:

  • Pickup time
  • Rental duration
  • One-way / round-trip status

Offer Alerts

When a new offer arrives, the application brings the driver back to the home screen from another screen.

This helps ensure ride offers are not missed.


🗺️ Trip Management

The standard trip flow is:

Arrived
   ↓
Rider PIN
   ↓
Start
   ↓
Per-stop arrival
   ↓
Complete
   ↓
Collect cash
   ↓
Rate rider

The final fare is calculated from the GPS trail.

Getting Paid

The trip summary shows whether the fare is in:

  • Cash trips ask the driver to confirm the cash was collected.
  • Wallet trips are paid when the trip ends.
  • Card and online trips are paid in the rider's app after the trip. The summary shows Waiting for the rider to pay and switches to Paid by itself. The driver does not need to wait: a Fare received notification arrives when the earning is added to the wallet.

During a Trip

Drivers can:

  • Hand off navigation to Google Maps
  • Call the rider
  • Chat with the rider
  • View unread chat messages
  • Use quick replies
  • Handle multiple stops
  • Cancel when applicable
  • Trigger SOS

Booking for Someone Else

When a ride was booked for another person:

  • The passenger's name is displayed.
  • Call passenger rings the passenger.
  • The original pickup note remains visible until pickup.

🆘 SOS & Safety

The SOS button is available on the trip map.

It:

  1. Alerts the safety team.
  2. Offers the local emergency number.

❌ Trip Cancellation

Drivers can cancel using configured driver cancellation reasons.

A rider no-show can also be reported after the free waiting period.


🚗 My Vehicles

The My vehicles section allows drivers to manage multiple vehicles.

Drivers can:

  • Add additional vehicles
  • Upload vehicle documents
  • Track vehicle review status
  • Select the vehicle currently being driven
  • Remove vehicles

Only approved vehicles can be selected for driving.

Vehicle Restrictions

The active vehicle:

  • Cannot be changed during a trip.
  • Cannot be removed while the driver is online.

Dispatch sends ride offers according to the vehicle currently being driven.


📦 Lost Items

Drivers can receive lost-item reports related to their trips.

The driver can:

  • Call the rider
  • Select I found it
  • Select Returned it
  • Select Not in my car
  • Add an optional note

The rider is notified of the response.


🎫 Help & Support

Drivers can create support tickets with threaded replies.

Support functionality is provided through:

ridex_core

Trip-specific assistance is available through:

Get help with this trip

from trip history.


💰 Earnings

The earnings section supports:

  • Today
  • This week
  • This month

It displays:

  • Gross fares
  • Commission
  • Net earnings
  • Tips
  • Bonuses
  • Cash collected
  • Online time
  • Daily earnings bars
  • Recent trips

🎯 Incentives

Drivers can participate in configured:

  • Daily targets
  • Weekly targets
  • Monthly targets

Progress is displayed using progress bars.

When a target is reached, the incentive is automatically paid into the driver's wallet.


💳 Wallet & Withdrawals

The wallet is provided by:

ridex_core

It provides:

  • Wallet ledger
  • Top-up
  • Withdrawals

Withdrawal Details

Before requesting a withdrawal, drivers must provide payout details.

Supported payout methods include:

  • Bank transfer
  • Mobile money
  • PayPal

Account numbers are:

  • Stored encrypted
  • Displayed in masked form

Withdrawal Processing

Requested withdrawal amounts are held until an administrator:

  • Pays the request, or
  • Rejects the request

💸 Driver Dues

For cash trips, the platform's commission is charged against the driver's wallet.

The home screen warns the driver when commission is owed and provides:

Settle

When the outstanding amount exceeds the administrator's configured limit, dispatch stops sending new trips.


🔥 Firebase Ride Offers

Ride offers are delivered through Firebase:

drivers/{uid}/offers

Offers arrive as soon as dispatch creates them.

Polling remains enabled as a safety mechanism.


📚 Shared Features

The following functionality is provided through ridex_core:

  • Trip history
  • Per-trip earnings
  • Refer & Earn
  • Notification inbox
  • Settings
  • Language selection
  • Theme (system default, light or dark)
  • Push preferences
  • Email preferences
  • Delete account
  • Logout
  • Google Sign-In
  • Apple Sign-In
  • Force-update screen
  • Shared translations

🌍 Localization

The Driver application uses the shared translations from ridex_core.

Supported languages include:

  • 🇬🇧 English
  • 🇸🇦 Arabic
  • 🇪🇸 Spanish
  • 🇫🇷 French
  • 🇧🇩 Bengali

Arabic supports right-to-left layout.


🏗️ Architecture

RideX Driver follows a feature-based architecture with separate domain, data, and presentation layers.

lib/
├── main.dart
│   └── Startup: RideXCore.bootstrap + driver repositories
│
├── config/
│   └── Build configuration, routes, pages and bindings
│
├── localization/
│   └── Driver strings merged over ridex_core shared strings
│
└── features/
    └── <feature>/
        ├── domain/
        │   └── Entities + repository contracts
        │
        ├── data/
        │   └── Repository implementations over ApiClient
        │       + LocationReporter
        │
        └── presentation/
            └── GetX controllers + pages

🧩 Feature Modules

Driver-specific features are organized into:

Module Responsibility
onboarding Driver registration, KYC and onboarding
driving Home, ride offers, surge zones, location reporting and offer alerts
trip Active trip lifecycle
vehicles Vehicle management
earnings Earnings and incentives
payouts Withdrawals and payout details
lost_items Lost-item management

📡 Live Trip Updates

Live trip updates are abstracted behind:

RideUpdates

FirebaseRideUpdates

When Firebase is available, the application streams the trip directly from Firebase.

PollingRideUpdates

If Firebase is unavailable, the application falls back to polling.

The trip is refreshed according to the configured tracking interval.

If a trip disappears—for example, because it has been re-dispatched—the live stream ends and the application returns the driver to the home screen.


⚠️ Error Handling

Every API failure is converted into:

AppException

The exception contains:

  • Server-provided message
  • error_code

Errors are displayed through:

AppFeedback

This keeps API error handling consistent throughout the application.


🔐 Security Checklist

Before publishing a production Driver application, verify:

  • Google Maps keys are restricted.
  • Production backend uses HTTPS.
  • API secrets are not committed.
  • Secrets.xcconfig is not committed.
  • secrets.properties is not committed.
  • key.properties is not committed.
  • Production Android signing is configured.
  • Firebase uses the correct Driver application IDs.
  • App Check is registered for Android and iOS, and enforced once the store builds are verified.
  • Google Maps restrictions use the correct package/bundle IDs.
  • Release builds use --obfuscate.
  • Debug symbols are securely archived.
  • Debug symbols are not shipped.
  • Certificate pinning is configured if required.
  • RASP configuration is supplied if full device-security checks are required.
  • Mock-location protection remains enabled in production.
  • Android foreground-service requirements are configured in Google Play Console.

🧪 Testing

Run static analysis:

flutter analyze

Run the Flutter test suite:

flutter test

Or run both:

flutter analyze && flutter test

🛠️ Development Quick Start

A typical local development workflow:

# Install dependencies
flutter pub get

# Analyze the project
flutter analyze

# Run tests
flutter test

# Start the application
flutter run

Android Emulator

http://10.0.2.2:8000

iOS Simulator

http://127.0.0.1:8000

Physical Android Device

adb reverse tcp:8000 tcp:8000

flutter run \
  --dart-define=API_BASE_URL=http://127.0.0.1:8000

🚀 Production Build Quick Reference

Android

flutter build appbundle \
  --release \
  --obfuscate \
  --split-debug-info=build/symbols \
  --dart-define=API_BASE_URL=https://api.yourdomain.com

iOS

flutter build ipa \
  --release \
  --obfuscate \
  --split-debug-info=build/symbols \
  --dart-define=API_BASE_URL=https://api.yourdomain.com

📋 Final Pre-Release Checklist

Configuration

  • Production API_BASE_URL configured
  • Firebase configured
  • Google Maps configured
  • Google/Apple Sign-In configured if enabled
  • Driver package/bundle IDs configured

Branding

  • Driver app name configured
  • Dark brand icon installed
  • Splash screen configured
  • Branding assets regenerated

Android

  • Production keystore configured
  • SHA-1 registered
  • Firebase Android app configured
  • Foreground service type declared
  • Required Play Console video uploaded
  • Release App Bundle generated

iOS

  • Apple Developer team configured
  • Bundle ID configured
  • Push Notifications capability enabled
  • APNs key configured
  • Firebase iOS configuration updated
  • Release IPA generated

Driver Features

  • Driver onboarding tested
  • Document upload tested
  • Vehicle approval tested
  • Online/offline flow tested
  • Location tracking tested
  • Mock-location protection verified
  • Ride offers tested
  • Scheduled rides tested
  • Rental rides tested
  • Outstation rides tested
  • Trip lifecycle tested
  • Navigation tested
  • Calling/chat tested
  • SOS tested
  • Lost-item flow tested
  • Earnings tested
  • Incentives tested
  • Wallet tested
  • Withdrawal flow tested
  • Dues/settlement tested
  • Push notifications tested

Quality

  • flutter analyze passes
  • flutter test passes
  • Production build tested on a physical device
  • Debug symbols archived
  • No secrets included in the application
  • Production backend uses HTTPS
  • App Check registered, and enforced once the store builds are verified

📌 Important Notes

  • Rider and Driver applications must use different application IDs.
  • Rider and Driver require separate Firebase applications.
  • Firebase is optional: without its files the app still builds, and ride updates fall back to polling.
  • Ride offers use Firebase for immediate delivery with polling as a safety net.
  • Location tracking continues in the background while the driver is online.
  • Mock-location protection should only be disabled for local simulator/emulator testing.
  • Production Android builds require a valid upload keystore.
  • Production builds must provide an HTTPS API_BASE_URL.
  • Never embed server-side secrets into the mobile application.
  • Keep release debug symbols securely archived for crash analysis.

RideX Driver — a configurable, white-label Flutter driver application for the RideX platform.

Shared app code

Shared foundation of the RideX rider and driver apps. Each app includes an identical copy at packages/ridex_core and depends on it by path, adding only its own features on top.

Keep the two copies identical. After changing the shared code in one app, copy its whole packages/ridex_core folder over the other app's, so both apps build from the same code. From the folder that holds both apps (or copy the folder in your file manager):

rsync -a --delete ridex_rider/packages/ridex_core/ ridex_driver/packages/ridex_core/

What it provides

Area Contents
Startup RideXCore.bootstrap(): storage, Firebase (optional) and App Check, API client, white-label config, location (LocationService, which asks for permission and gives up after 20 seconds without a position), the signed-in session and the shared services below. RideXApp wraps GetMaterialApp with theme, dark mode, language and RTL: the saved language, else the phone's when the platform offers it, else the admin's default. The splash screen stops builds older than the admin's minimum version (AppUpdate) with an Update now button.
Networking ApiClient (Dio): auth and locale headers, the {success, data} envelope, pagination (getPage), and every failure as an AppException with the server's message, error_code, field errors and data.
Auth Phone sign-in with codes from the server or Firebase Phone Auth (PhoneVerifier; Firebase codes need the app's Firebase files and fail with firebase_not_configured without them), Google and Apple sign-in (SocialSignIn), adding a verified phone to a Google/Apple sign-up, account deletion, and SessionController (current user, logout, 401 handling). Phone fields pick the country from a searchable list of every country (CountryCodeField, PhoneCountries), starting on the admin's default phone country.
Shared screens Splash, phone, the code screen (one box per digit of the server's code length, and "Step 2 of N" when an app passes corePages(signUpSteps:)), ride history and ride details (rating, help, app actions via RideAction), settings (language, dark mode, push and email preferences, delete account), wallet (ledger, top-up, optional withdraw link), Refer & Earn, notification inbox, in-ride chat, help & support (tickets and replies). corePages() registers them; each app adds its own routes.
Payments PaymentsRepository (GET /payments/methods, top-up, confirm) and PaymentCheckout, which opens a provider's hosted page in a web view and confirms the result with the backend.
Live updates RideUpdates with two implementations: FirebaseRideUpdates (Realtime Database, via RealtimeSession's custom-token sign-in) and PollingRideUpdates. The Firebase one falls back to polling on its own.
App Check Firebase App Check starts right after Firebase, in _activateAppCheck() (lib/src/app/ridex_core.dart): release builds attest with Play Integrity on Android and App Attest on iOS (DeviceCheck before iOS 14), debug and profile builds with the debug provider. --dart-define=APP_CHECK_DEBUG_TOKEN=<token> (CoreConfig.appCheckDebugToken) sets a debug token you registered; without it, the Firebase SDK makes one per device. Nothing is refused until App Check is enforced in the Firebase console, and a refused build falls back to polling (Mobile app setup → 10.3 App Check).
Notifications NotificationService: FCM token registration, foreground pushes shown as toasts (except those the open screen already shows, e.g. that ride's chat), and the unread badge (unread).
Safety & ratings SosButton (alert with position, then offers the local emergency number), RateTripCard + RatingSheet (stars, admin-configured compliment tags, review). Each app registers its RatingsRepositoryImpl with its rate endpoint.
UI kit AppFeedback (toasts, and confirm() for yes/no questions), LoadingView / ErrorView / EmptyView, LoadingButton, PagedList + PagedListView (pull to refresh, infinite scroll), TripMap with vehicle-type markers (MapMarkers), AppDrawer + DrawerItem, MessageBubble, StatusChip, PersonAvatar (photo or initial; a tap on a photo opens a profile card with the optional PersonDetails: vehicle, rating, trips, verified) and AvatarPicker, CountryCodeField, CodeBoxes (CodeBoxesStyle.pin for the ride PIN).
Dialogs AppDialog.show() slides a DialogCard up from the bottom over the dimmed, blurred page. Building blocks: ConfirmDialog, DialogIconBadge (tinted by DialogTone: neutral, brand, danger, accent), DialogButtons (Cancel and the action), DialogButton, DialogPill.
Utils money() in the configured currency, ride type labels (rideTypeLabel(), tripTypeLabel()), formatDateTime() / formatDate() / formatTime() in the app's language, trSpan() (a translated string with bold values), openExternal() / callNumber(), lenient JSON readers, polyline decoding.

Conventions

  • Repository pattern: domain/ holds entities and repository contracts, data/ the implementations over ApiClient, and presentation/ the GetX controllers and pages.
  • Configuration from the backend: branding, currency, languages, feature flags and payment methods come from the API, so white-label changes need no new build.
  • Dialogs: every dialog in both apps opens with AppDialog.show() on a DialogCard, so they share one look and follow the brand colour; restyle them all in lib/src/widgets/app_dialog.dart.
  • Translations: shared strings are in lib/src/localization/core_translations.dart, in English, Arabic, Spanish, French and Bengali. Each app's strings are merged over them, and an app can override any key. The translation tests in each package fail when a language misses a key or a @placeholder.

Tests

flutter analyze
flutter test

API reference

Import the Postman collection to try every endpoint. Endpoints marked public need no token.

The mobile API used by the Rider and Driver apps. Regenerate this file after changing routes: php artisan ridex:api-collection.

  1. Set the baseUrl variable to {APP_URL}/api/v1.

  2. Sign in with auth.otp.send, then auth.otp.verify (in demo mode: +1 5550000001 with code 123456), and copy data.token into the token variable.

  3. Every response uses the same envelope: {success, message, data, meta, errors, error_code}. Send X-Locale (en, ar, …) for translated messages.

Config

GET /api/v1/config public

No sign-in needed: branding, currency, languages, phone country, active vehicle types, code lengths and every public setting.

Content

GET /api/v1/content/pages public

The information pages an app lists (About, Terms, Privacy...), each with its public web address.

GET /api/v1/content/pages/:slug public

One active information page by its slug, with the title and content translated; 404 when it is missing or switched off.

GET /api/v1/content/faqs public

Active help-centre questions for the app, in sort order, each with its category and the translated question and answer.

GET /api/v1/content/banners public

Live banners for the zone at ?lat&lng (or ?zone_id); banners without a zone show everywhere.

  • zone_id · integer
  • lat · required_with:lng, numeric, between:-90,90
  • lng · required_with:lat, numeric, between:-180,180
GET /api/v1/content/cancellation-reasons public

The reasons the app offers when cancelling a ride, translated and in sort order.

GET /api/v1/content/cities public

Operating cities (driver onboarding picks one).

GET /api/v1/content/quick-messages public

One-tap replies the app offers in the trip chat, translated and in sort order.

GET /api/v1/content/rating-tags public

Compliment tags the app offers when rating the other person.

Trips

GET /api/v1/trips/share/:token public

No sign-in, secret token: status, pickup, drop, first names, the driver's live position and the car; 410 once the ride has ended.

Auth

POST /api/v1/auth/otp/send public

Texts a sign-in or phone-change code and returns its lifetime; refused when sign-in runs on Firebase Phone Auth instead.

  • country_code required · string, regex:/^+\d{1,4}$/
  • phone required · string, regex:/^\d{5,14}$/
  • purpose · in:"login","change_phone"
POST /api/v1/auth/otp/verify public

Checks the texted code, then signs in or creates the account; wrong, expired or over-tried codes and blocked accounts are refused.

  • country_code required · string, regex:/^+\d{1,4}$/
  • phone required · string, regex:/^\d{5,14}$/
  • user_type required · in:"rider","driver"
  • referral_code · string, max:20, exists:users,referral_code,user_type,"NULL",deleted_at,"NULL"
  • fcm_token · string, max:255
  • device_type · in:"android","ios"
  • device_name · string, max:100
  • app_version · string, max:20
  • code required · string, regex:/^\d{4,8}$/
POST /api/v1/auth/firebase public

Signs in or signs up with a Firebase Phone Auth ID token; refuses a phone number that differs from the one Firebase verified.

  • country_code required · string, regex:/^+\d{1,4}$/
  • phone required · string, regex:/^\d{5,14}$/
  • user_type required · in:"rider","driver"
  • referral_code · string, max:20, exists:users,referral_code,user_type,"NULL",deleted_at,"NULL"
  • fcm_token · string, max:255
  • device_type · in:"android","ios"
  • device_name · string, max:100
  • app_version · string, max:20
  • id_token required · string, max:4096
POST /api/v1/auth/social public

Google or Apple sign-in: verifies the ID token, then signs in, links by email or creates the account; 403 when that provider is off.

  • user_type required · in:"rider","driver"
  • referral_code · string, max:20, exists:users,referral_code,user_type,"NULL",deleted_at,"NULL"
  • fcm_token · string, max:255
  • device_type · in:"android","ios"
  • device_name · string, max:100
  • app_version · string, max:20
  • provider required · in:"google","apple"
  • id_token required · string, max:4096
  • name · string, max:100
GET /api/v1/auth/me

The signed-in user; for drivers, also the driver profile with its current vehicle and vehicle type.

POST /api/v1/auth/logout

Revokes this device's token and clears its push token; drivers also go offline so dispatch skips them.

POST /api/v1/auth/fcm-token

Saves the phone's FCM push token, with the device type and app version when sent.

  • fcm_token required · string, max:255
  • device_type · in:"android","ios"
  • app_version · string, max:20
DELETE /api/v1/auth/account

Deletes the account as app stores require: personal data is wiped, rides and payments are kept; refused during an active ride.

Profile

GET /api/v1/profile

The signed-in user's profile, with the driver profile for drivers.

POST /api/v1/profile

Also used for "profile completion" right after the first login.

  • name required · custom, string, min:2, max:100
  • email · email:rfc, max:150, unique
  • gender · in
  • date_of_birth · custom, date, custom
  • address · string, max:255
  • avatar · custom, image, custom
POST /api/v1/profile/phone

Add a phone (social sign-ups) or change it. Ownership proven by OTP or Firebase token.

  • country_code required · string, regex:/^+\d{1,4}$/
  • phone required · string, regex:/^\d{5,14}$/
  • code · required_without:firebase_id_token, string, regex:/^\d{4,8}$/
  • firebase_id_token · required_without:code, string, max:4096
PUT /api/v1/profile/language

Saves the user's language (an active language code); notifications sent to them then use it.

  • language required · string, exists:languages,code,is_active,"1"
PUT /api/v1/profile/notification-preferences

Switches notification types and channels on or off; keys not sent keep their value, and safety alerts always go out.

  • preferences required · array
  • preferences.ride_updates · boolean
  • preferences.chat · boolean
  • preferences.payments · boolean
  • preferences.promotions · boolean
  • preferences.news · boolean
  • preferences.sms · boolean
  • preferences.email · boolean

Wallet

GET /api/v1/wallet

The balance, any dues owed (the negative part), the currency and the top-up limits.

GET /api/v1/wallet/transactions

The user's wallet ledger, newest first and paginated, each entry with the record behind it.

Referrals

GET /api/v1/referrals

The user's invite code, the reward amounts, the total earned and everyone they invited with each reward's status.

Payments

GET /api/v1/payments/methods

Methods and gateways switched on in Admin → Payment Configuration.

POST /api/v1/payments/wallet/top-up

Opens a wallet top-up checkout on the chosen gateway; refused when the wallet is off, below the minimum or past the balance cap.

  • amount required · numeric, min:0.01, max:99999999
  • gateway required · string, max:30
POST /api/v1/payments/:payment/confirm

Asks the gateway how the user's own payment went once the hosted page closes; when paid, the money moves exactly once.

GET /api/v1/payments/history

The user's payments, newest first and paginated, each with its ride.

Notifications

GET /api/v1/notifications

The user's notifications, newest first and paginated, with the unread count in meta.

GET /api/v1/notifications/unread-count

For the app's badge.

POST /api/v1/notifications/read-all

Marks every unread notification as read; the unread count comes back as 0.

POST /api/v1/notifications/:id/read

Marks one of the user's own notifications as read and returns the new unread count; 404 for any other id.

Rides

GET /api/v1/rides/:ride/chat

Messages on this ride, oldest first; with after_id only newer ones. Only the ride's rider and driver may read them.

  • after_id · integer, min:1
POST /api/v1/rides/:ride/chat

Sends a text or quick reply and pushes it to the other person; refused when chat is off, before a driver accepts or after the trip.

  • message required · string, custom
  • type · in:"text","quick_reply"
POST /api/v1/rides/:ride/chat/read

Marks the messages sent to the signed-in user on this ride as read; only the ride's rider and driver may call it.

Realtime

POST /api/v1/realtime/token

A Firebase custom token, the database URL and the paths this user may listen to; 503 when Firebase is not set up, so the app polls.

SOS

POST /api/v1/sos

Raises an SOS on the given or current ride and texts the emergency contacts; pressing again soon after updates the open alert.

  • ride_uuid · uuid
  • lat · required_with:lng, numeric, between:-90,90
  • lng · required_with:lat, numeric, between:-180,180

Support tickets

GET /api/v1/support-tickets

The user's tickets, most recently updated first and paginated, each with its ride.

POST /api/v1/support-tickets

Opens a ticket at its category's default priority; a fare dispute must name one of the user's own rides.

  • category required · in:"general","ride_issue","fare_dispute","payment","safety","driver_behavior","lost_item","account","other"
  • subject required · string, max:150
  • description required · string, max:2000
  • ride_uuid · , uuid
  • disputed_amount · numeric, gt:0, max:1000000
GET /api/v1/support-tickets/:support_ticket

One of the user's own tickets with its ride and replies; 404 for anyone else's.

POST /api/v1/support-tickets/:support_ticket/reply

Adds the user's reply to their own ticket (404 otherwise) and puts it back in the support queue; closed tickets are refused.

  • message required · string, custom

Rider › Home

GET /api/v1/rider/home

The rider home screen in one call: the active ride, saved places, live banners and the zone at ?lat&lng.

  • lat · numeric, between:-90,90
  • lng · numeric, between:-180,180

Rider › Nearby drivers

GET /api/v1/rider/nearby-drivers

Available cars around a point for the home map.

  • lat required · numeric, between:-90,90
  • lng required · numeric, between:-180,180
  • vehicle_type_id · integer

Rider › Places

GET /api/v1/rider/places/autocomplete

Google Places suggestions for the typed text (2+ characters), biased towards ?lat&lng; an empty list when Google is not set up or fails.

  • q required · string, min:2, max:200
  • lat · numeric, between:-90,90, required_with:lng
  • lng · numeric, between:-180,180, required_with:lat
  • session_token · string, regex:/^[A-Za-z0-9_-]{1,36}$/
GET /api/v1/rider/places/reverse

The readable address at a GPS point (e.g. for "Current location"); the coordinates themselves when Google gives none.

  • lat required · numeric, between:-90,90
  • lng required · numeric, between:-180,180
GET /api/v1/rider/places/:placeId

A suggestion's name, address and coordinates in the request language, cached; 404 when Google returns no such place.

  • session_token · string, regex:/^[A-Za-z0-9_-]{1,36}$/

Rider › Fare estimate

POST /api/v1/rider/fare-estimate

One priced option per vehicle type offered at the pickup, each with a quote_id for booking; 422 when no type serves the trip there.

  • ride_type required · in:"instant","scheduled","rental","outstation"
  • pickup required · array
  • pickup.lat required · numeric, between:-90,90
  • pickup.lng required · numeric, between:-180,180
  • drop required · array
  • drop.lat required · numeric, between:-90,90
  • drop.lng required · numeric, between:-180,180
  • stops · array, max:3
  • stops.*.lat required · numeric, between:-90,90
  • stops.*.lng required · numeric, between:-180,180
  • scheduled_at · date, after_or_equal:29 minutes from now, before_or_equal:1 week from now
  • rental_hours · integer, min:1, max:24
  • is_round_trip · boolean
  • outstation_days · integer, min:1, max:30
  • vehicle_type_id · integer, exists:vehicle_types,id,is_active,"1",deleted_at,"NULL"
  • promo_code · string, max:30

Rider › Rental packages

GET /api/v1/rider/rental-packages

The rental package hours any active vehicle type offers at the pickup, from each type's fare plan, unique and sorted.

  • lat required · numeric, between:-90,90
  • lng required · numeric, between:-180,180

Rider › Promo codes

POST /api/v1/rider/promo-codes/validate

Checks a code for this rider and the pickup at ?lat&lng; 422 with the reason when it is invalid, expired, used up or not usable.

  • code required · string, max:30
GET /api/v1/rider/promo-codes

Valid codes this rider can still use, newest first; codes limited to areas only show when ?lat&lng is inside one of them.

Rider › Rides

GET /api/v1/rider/rides

The rider's rides, newest first and paginated; ?status takes a comma-separated list of statuses.

POST /api/v1/rider/rides

Books at the quoted price and starts dispatch (scheduled rides wait); refused for an expired quote, a ride in progress or unpaid dues.

  • quote_id required · uuid
  • payment_method required · in:"cash","card","wallet","online"
  • pickup_address required · string, max:500
  • drop_address · string, max:500
  • stop_addresses · array, max:3
  • stop_addresses.* · string, max:500
  • pickup_note · string, max:500
  • passenger_name · string, max:100, required_with:passenger_phone
  • passenger_phone · string, max:30, required_with:passenger_name
GET /api/v1/rider/rides/active

The rider's ride in progress with its driver, vehicle, stops and unread chat count, or null when there is none.

GET /api/v1/rider/rides/:ride

One of the rider's own rides with its driver, vehicle, stops and unread chat count; 404 for anyone else's.

POST /api/v1/rider/rides/:ride/cancel

Cancels the rider's own ride; free before a driver is assigned and for a grace period after, then a fee applies. Refused once started.

  • reason · string, max:500
POST /api/v1/rider/rides/:ride/share

Link for trusted contacts to follow the trip live; stops working when the ride ends.

POST /api/v1/rider/rides/:ride/tip

Wallet tips are sent at once; card / online tips return a checkout page to open.

  • amount required · numeric, min:0.01
  • method required · in:"wallet","card","online"
  • gateway · required_unless:method,wallet, string, max:30
PUT /api/v1/rider/rides/:ride/payment-method

Switches the payment method before the ride ends; once an unpaid card or online ride has ended, only the wallet can settle it.

  • payment_method required · in:"cash","card","wallet","online"
GET /api/v1/rider/rides/:ride/receipt

The receipt of the rider's own completed ride, or of a cancelled one charged a fee, with its payments; 404 otherwise.

POST /api/v1/rider/rides/:ride/rate

Rates the other person on a completed ride the user was on (404 otherwise) and updates their average; one rating each per ride.

  • rating required · integer, custom
  • review · string, custom
  • tags · array
  • tags.* · string, distinct, in
POST /api/v1/rider/rides/:ride/lost-items

Reports an item left on the rider's own completed ride and tells the driver; refused before completion or after the reporting window.

  • item_name required · string, max:100
  • description · string, max:1000
  • contact_phone · string, max:20, regex:/^+?[\d\s-]{5,20}$/

Rider › Lost items

GET /api/v1/rider/lost-items

The rider's lost-item reports, newest first and paginated, each with its ride.

Rider › Saved places

GET /api/v1/rider/saved-places

The rider's saved places, sorted by label and newest first within each label.

POST /api/v1/rider/saved-places

Saves a place for the rider; there can be only one home and one work place.

  • label required · in, when
  • name required · string, max:100
  • address required · string, max:500
  • lat required · numeric, between:-90,90
  • lng required · numeric, between:-180,180
PUT /api/v1/rider/saved-places/:saved_place

Edits one of the rider's own places (still one home and one work at most); 404 for anyone else's.

  • label required · in, when
  • name required · string, max:100
  • address required · string, max:500
  • lat required · numeric, between:-90,90
  • lng required · numeric, between:-180,180
DELETE /api/v1/rider/saved-places/:saved_place

Removes one of the rider's own places; 404 for anyone else's.

Rider › Emergency contacts

GET /api/v1/rider/emergency-contacts

The rider's emergency contacts, by name.

POST /api/v1/rider/emergency-contacts

Adds an emergency contact; refused once the rider has the maximum allowed in the safety settings.

PUT /api/v1/rider/emergency-contacts/:emergency_contact

Edits one of the rider's own contacts; 404 for anyone else's.

DELETE /api/v1/rider/emergency-contacts/:emergency_contact

Removes one of the rider's own contacts; 404 for anyone else's.

Rider › Payments

POST /api/v1/rider/payments/rides/:ride/pay

Opens a checkout for the rider's own completed card or online ride (404 for others); refused when nothing is left to pay.

  • gateway required · string, max:30

Rider › Saved cards

GET /api/v1/rider/saved-cards

The rider's cards, default first, after syncing them with Stripe; 422 when saved cards are not available.

POST /api/v1/rider/saved-cards

Opens Stripe's page to add a card; the app lists the cards again when it returns.

DELETE /api/v1/rider/saved-cards/:saved_card

Removes one of the rider's own cards at Stripe and here; another card becomes the default when needed.

POST /api/v1/rider/saved-cards/:saved_card/default

Makes one of the rider's own cards the default, the one charged when a card ride ends.

Driver › Onboarding

GET /api/v1/driver/onboarding

The driver's checklist: approval status, rejection reason, can_submit and the profile, documents and vehicle steps.

POST /api/v1/driver/onboarding/profile

Step 1: saves the personal details, photo and city and returns the updated checklist; identity fields are locked once verified.

  • name required · custom, string, min:2, max:100
  • email · email:rfc, max:150, unique
  • city_id required · integer, exists
  • gender · in
  • date_of_birth required · custom, date, custom
  • address · string, max:255
  • avatar · custom, image, custom
POST /api/v1/driver/onboarding/submit

Sends the application to admin review; refused while a step is incomplete, or once it is under review or approved.

Driver › Documents

GET /api/v1/driver/documents/requirements

Which documents exist and their rules – lets the app build upload forms dynamically.

GET /api/v1/driver/documents

The driver's current documents, newest first; ?vehicle_id narrows them to one vehicle.

POST /api/v1/driver/documents

Uploads a document for review; it replaces older copies of that type, except an approved one still in force.

  • document_type required · in:"profile_photo","national_id","driving_license","vehicle_registration","insurance","fitness_certificate","other"
  • vehicle_id · prohibited
  • file_front required · file, mimes:jpg,jpeg,png,webp,pdf, max:5120
  • file_back · file, mimes:jpg,jpeg,png,webp,pdf, max:5120
  • document_number · string, max:100
  • expires_at · date, after:today
GET /api/v1/driver/documents/:document

One of the driver's own documents with its review status; 404 for anyone else's.

POST /api/v1/driver/documents/:document/reupload

A new copy of the driver's own document (same type and vehicle) for review, e.g. after a rejection or before it expires.

  • file_front required · file, mimes:jpg,jpeg,png,webp,pdf, max:5120
  • file_back · file, mimes:jpg,jpeg,png,webp,pdf, max:5120
  • document_number · string, max:100
  • expires_at · date, after:today

Driver › Vehicles

GET /api/v1/driver/vehicles

The driver's vehicles, the active one first and then newest, each with its type and documents.

POST /api/v1/driver/vehicles

Adds a vehicle (with an optional photo) awaiting review; the driver's first vehicle becomes the active one.

  • vehicle_type_id required · integer, exists:vehicle_types,id,is_active,"1",deleted_at,"NULL"
  • make required · string, max:50
  • model required · string, max:50
  • year required · integer, min:1990, max:2027
  • color required · string, max:30
  • plate_number required · string, min:2, max:20, unique:vehicles,plate_number,NULL,id
  • seats · integer, min:1, max:20
  • image · image, max:5120
GET /api/v1/driver/vehicles/:vehicle

One of the driver's own vehicles with its type and documents; 404 for anyone else's.

PUT /api/v1/driver/vehicles/:vehicle

Edits the driver's own vehicle; a new type, plate, make, model or year sends an approved one back to review. Refused mid-trip.

  • vehicle_type_id required · integer, exists:vehicle_types,id,is_active,"1",deleted_at,"NULL"
  • make required · string, max:50
  • model required · string, max:50
  • year required · integer, min:1990, max:2027
  • color required · string, max:30
  • plate_number required · string, min:2, max:20, unique:vehicles,plate_number,NULL,id
  • seats · integer, min:1, max:20
  • image · image, max:5120
DELETE /api/v1/driver/vehicles/:vehicle

Removes the driver's own vehicle and its documents, promoting another if it was active; refused on a trip or while online in it.

POST /api/v1/driver/vehicles/:vehicle/default

Makes this the driver's only active vehicle; once the driver is approved, only an approved vehicle can be chosen. Refused mid-trip.

Driver › Home

GET /api/v1/driver/home

Everything the driver home screen needs: online state, today's totals, rating, wallet, vehicle, active ride, open offer and destination.

Driver › Status

POST /api/v1/driver/status

Going online checks the phone, the documents and unpaid dues; going offline is refused during a trip and hands open offers on.

  • online required · boolean

Driver › Location

POST /api/v1/driver/location

Takes one GPS fix or a buffered batch and returns how many points were kept; jumps are dropped, mock GPS refused when blocking is on.

  • is_mock · boolean
  • lat required · numeric, between:-90,90
  • lng required · numeric, between:-180,180
  • heading · integer, between:0,359
  • recorded_at · date

Driver › Heatmap

GET /api/v1/driver/heatmap

Recent ride requests binned into grid cells around the driver, plus zones currently surging.

Driver › Destination

PUT /api/v1/driver/destination

Head somewhere (e.g. home): only trips that end closer to it are offered. Counts against today's limit.

  • address required · string, max:255
  • lat required · numeric, between:-90,90
  • lng required · numeric, between:-180,180
DELETE /api/v1/driver/destination

Stop heading to the destination: every trip is offered again.

Driver › Places

GET /api/v1/driver/places/autocomplete

Google Places suggestions for the typed text (2+ characters), biased towards ?lat&lng; an empty list when Google is not set up or fails.

  • q required · string, min:2, max:200
  • lat · numeric, between:-90,90, required_with:lng
  • lng · numeric, between:-180,180, required_with:lat
  • session_token · string, regex:/^[A-Za-z0-9_-]{1,36}$/
GET /api/v1/driver/places/reverse

The readable address at a GPS point (e.g. for "Current location"); the coordinates themselves when Google gives none.

  • lat required · numeric, between:-90,90
  • lng required · numeric, between:-180,180
GET /api/v1/driver/places/:placeId

A suggestion's name, address and coordinates in the request language, cached; 404 when Google returns no such place.

  • session_token · string, regex:/^[A-Za-z0-9_-]{1,36}$/

Driver › Ride requests

GET /api/v1/driver/ride-requests/current

The driver's newest offer still inside its countdown, with the ride and rider, or null when there is none.

POST /api/v1/driver/ride-requests/:rideRequest/accept

Accepts the driver's own offer and returns the ride; refused on an untrusted phone, after expiry, once taken or while on a trip.

POST /api/v1/driver/ride-requests/:rideRequest/reject

Declines the driver's own offer (optional reason) and offers the ride to the next driver; an already answered offer is left alone.

  • reason · string, max:255

Driver › Trips

GET /api/v1/driver/trips

The driver's trips, newest first and paginated; ?status takes a comma-separated list of statuses.

GET /api/v1/driver/trips/active

The driver's ride in progress with its stops, rider and unread chat count, or null when there is none.

GET /api/v1/driver/trips/:ride

One of the driver's own trips with its stops, rider and the rating the driver gave; 404 for anyone else's.

POST /api/v1/driver/trips/:ride/arrived

Marks the driver at the pickup and tells the rider; refused unless accepted, or when a fresh GPS fix is too far from the pickup.

POST /api/v1/driver/trips/:ride/start

Starts the trip after arrival once the rider's PIN matches (when PINs are on) and records the waiting time.

  • otp · custom, string, max:10
POST /api/v1/driver/trips/:ride/stops/:stop/arrived

Marks a stop on the driver's own started trip as reached (the first time only); 404 for a stop on another ride.

POST /api/v1/driver/trips/:ride/complete

Ends the trip: final fare from the GPS trail or the app's distance plus tolls, then settlement and rewards; card rides are charged.

  • distance_km required · numeric, min:0, max:10000
  • tolls · numeric, min:0, max:200
POST /api/v1/driver/trips/:ride/cancel

Cancels before the trip starts: a rider no-show (after the free wait) charges the rider's fee, any other reason re-dispatches the ride.

  • reason required · string, max:500
  • rider_no_show · boolean
POST /api/v1/driver/trips/:ride/collect-cash

Records the rider's cash as paid on the driver's own completed cash trip; refused when there is no cash left to collect.

POST /api/v1/driver/trips/:ride/rate

Rates the other person on a completed ride the user was on (404 otherwise) and updates their average; one rating each per ride.

  • rating required · integer, custom
  • review · string, custom
  • tags · array
  • tags.* · string, distinct, in

Driver › Earnings

GET /api/v1/driver/earnings

?period=today|week|month (default today).

  • period · in
GET /api/v1/driver/earnings/trips

The driver's completed trips, latest first and paginated, with the fare, commission, earning and tip of each.

Driver › Withdrawals

GET /api/v1/driver/withdrawals

The balance, the minimum withdrawal, the masked payout details, the open request and recent requests.

POST /api/v1/driver/withdrawals

Requests a payout for the finance team and debits the wallet at once. Refused on an untrusted phone, without payout details, below the minimum, above the balance or while another is open.

  • amount required · numeric, min:0.01, max:99999999

Driver › Payout details

PUT /api/v1/driver/payout-details

Saves where payouts go (bank, mobile money or PayPal), encrypted, and returns them masked; refused on an untrusted phone.

  • method required · in:"bank_transfer","mobile_money","paypal"
  • account_name required · string, max:100
  • bank_name · required_if:method,bank_transfer, string, max:100
  • account_number · required_if:method,bank_transfer,mobile_money, string, max:40
  • routing_code · string, max:40
  • provider · required_if:method,mobile_money, string, max:50
  • paypal_email · required_if:method,paypal, email, max:150

Driver › Incentives

GET /api/v1/driver/incentives

Running targets for the driver's city and vehicle type with live progress; a target reached is paid into the wallet once.

Driver › Lost items

GET /api/v1/driver/lost-items

Lost items reported on the driver's trips, newest first and paginated, each with its ride.

PUT /api/v1/driver/lost-items/:lostItem

Answers a report on the driver's own trip as found, returned or not found, and tells the rider; closed reports are refused.

  • status required · in:"reported","driver_contacted","found","returned","not_found","closed"
  • response · string, max:1000

Webhooks

POST /api/v1/webhooks/payments/:gateway public

Checks the gateway's signature (400 when invalid) and re-checks the payment it names; unknown payments get a plain 200.

Credits & licenses

RideX uses the open-source packages, fonts, icons and services listed below. RideX's own code, artwork and documentation are licensed to you under your Envato Market license; these notices don't change that.

Each package's full license text ships with the package:

  • Backend: ridex_backend/vendor/<package>/, after composer install
  • Admin panel scripts: ridex_backend/node_modules/<package>/, after npm ci
  • Apps: your Flutter pub cache, after flutter pub get

The tables were generated from composer.lock, package-lock.json and both apps' pubspec.lock. If you update the packages, the versions will differ.

Licenses that need attention

  • LGPL (PDF reports). barryvdh/laravel-dompdf creates the report PDFs with three LGPL libraries:

    • dompdf/dompdf (LGPL-2.1)
    • dompdf/php-font-lib (LGPL-2.1 or later)
    • dompdf/php-svg-lib (LGPL-3.0 or later)

    Composer installs them unmodified, as PHP source, in vendor/dompdf/, so you can replace or update them with Composer. That is what the LGPL asks for. Keep their license files (LICENSE.LGPL, LICENSE) when you redistribute them.

  • Nette. nette/schema and nette/utils are offered under BSD-3-Clause, GPL-2.0 or GPL-3.0. RideX uses them under BSD-3-Clause.

  • DOMPurify. It comes with the Trix editor in the admin panel's scripts, which npm run build bundles into public/build/. It is offered under MPL-2.0 or Apache-2.0. RideX uses it under Apache-2.0.

  • MPL-2.0 (Linux desktop only). dbus, geoclue and gsettings come in through the geolocator plugin's Linux support. The Android and iOS builds don't contain them.

  • Notices in the apps. Your Android and iOS builds contain compiled code from the Flutter packages below. Their BSD, MIT and Apache licenses ask you to pass their notices on with the app. Flutter collects all of them at build time; to show them, open showLicensePage(context: context) from a menu entry, for example in Settings.

Fonts, icons, images and map data

Item Used in License
Inter font Admin panel. Loaded from Bunny Fonts (fonts.bunny.net), not shipped SIL Open Font License 1.1
DejaVu fonts PDF reports. Come with dompdf, in vendor/dompdf/dompdf/lib/fonts/ after composer install Bitstream Vera and DejaVu font license (free to redistribute)
Heroicons Admin panel icons, through blade-ui-kit/blade-heroicons MIT, © Tailwind Labs
Material Icons App icons; bundled with Flutter Apache-2.0, © Google
Leaflet images (markers, layer switcher) Admin maps and the trip-share page, bundled into public/build/ by npm run build BSD-2-Clause, © Volodymyr Agafonkin
RideX logo, favicon, app icons, splash screens, vehicle icons Backend, admin panel and both apps Original artwork made for RideX; covered by your Envato license
OpenStreetMap tiles Admin maps, the trip-share page (the default MAPS_TILE_URL) and the zone editor screenshot in this documentation Map data © OpenStreetMap contributors, ODbL 1.0
Country names in English, Arabic, Spanish, French and Bengali The apps' country code list (ridex_core/lib/src/localization/phone_countries.dart) and the admin's Default phone country list (app/Support/PhoneCountries.php), taken from Unicode CLDR 48 Unicode License v3, © Unicode, Inc.
Currency names and symbols The admin's currency and currency symbol lists (app/Support/Currencies.php), taken from Unicode CLDR 48 Unicode License v3, © Unicode, Inc.

OpenStreetMap attribution. Keep the attribution visible on every map. Its text is set in Admin → Maps, SMS & Sign-in. The public tile server is for light use only, under the OpenStreetMap tile usage policy. For a busy platform, set MAPS_TILE_URL to a commercial tile provider, and change the attribution to match.

External services

These are not shipped with RideX. You sign up for each one, use your own keys, and accept the provider's terms:

  • Google Maps Platform: the maps in the apps, routes and place search.
  • Firebase: push notifications, live updates, phone codes and App Check.
  • Google Play Integrity and Apple App Attest / DeviceCheck: the device checks behind Firebase App Check (optional).
  • Twilio: SMS.
  • Google Sign-In and Sign in with Apple.
  • Payment gateways: Stripe, PayPal, Razorpay and Flutterwave.

Backend (Composer, runtime packages)

Package Version License
barryvdh/laravel-dompdf 3.1.2 MIT
blade-ui-kit/blade-heroicons 2.7.0 MIT
blade-ui-kit/blade-icons 1.10.1 MIT
brick/math 0.14.8 MIT
carbonphp/carbon-doctrine-types 3.2.1 MIT
dflydev/dot-access-data 3.0.3 MIT
doctrine/inflector 2.1.0 MIT
doctrine/lexer 3.0.2 MIT
dompdf/dompdf 3.1.6 LGPL-2.1
dompdf/php-font-lib 1.0.2 LGPL-2.1-or-later
dompdf/php-svg-lib 1.0.2 LGPL-3.0-or-later
dragonmantank/cron-expression 3.6.0 MIT
egulias/email-validator 4.0.4 MIT
firebase/php-jwt 7.2.0 BSD-3-Clause
fruitcake/php-cors 1.4.0 MIT
graham-campbell/result-type 1.2.0 MIT
guzzlehttp/guzzle 7.15.5 MIT
guzzlehttp/promises 2.5.3 MIT
guzzlehttp/psr7 2.13.1 MIT
guzzlehttp/uri-template 1.0.11 MIT
laravel/framework 12.69.2 MIT
laravel/prompts 0.3.24 MIT
laravel/sanctum 4.3.3 MIT
laravel/serializable-closure 2.1.0 MIT
laravel/tinker 2.11.1 MIT
league/commonmark 2.10.3 BSD-3-Clause
league/config 1.2.0 BSD-3-Clause
league/flysystem 3.36.0 MIT
league/flysystem-local 3.35.3 MIT
league/mime-type-detection 1.17.0 MIT
league/uri 7.8.1 MIT
league/uri-interfaces 7.8.1 MIT
masterminds/html5 2.11.0 MIT
monolog/monolog 3.12.0 MIT
nesbot/carbon 3.14.0 MIT
nette/schema 1.3.6 BSD-3-Clause OR GPL-2.0-only OR GPL-3.0-only (used under BSD-3-Clause)
nette/utils 4.1.5 BSD-3-Clause OR GPL-2.0-only OR GPL-3.0-only (used under BSD-3-Clause)
nikic/php-parser 5.9.0 BSD-3-Clause
nunomaduro/termwind 2.4.0 MIT
openspout/openspout 5.11.3 MIT
phpoption/phpoption 1.10.0 Apache-2.0
psr/clock 1.0.0 MIT
psr/container 2.0.2 MIT
psr/event-dispatcher 1.0.0 MIT
psr/http-client 1.0.3 MIT
psr/http-factory 1.1.0 MIT
psr/http-message 2.0 MIT
psr/log 3.0.2 MIT
psr/simple-cache 3.0.0 MIT
psy/psysh 0.12.24 MIT
ralouphie/getallheaders 3.0.3 MIT
ramsey/collection 2.1.1 MIT
ramsey/uuid 4.9.4 MIT
sabberworm/php-css-parser 9.5.0 MIT
symfony/clock 8.1.0 MIT
symfony/console 7.4.19 MIT
symfony/css-selector 8.1.6 MIT
symfony/deprecation-contracts 3.7.1 MIT
symfony/error-handler 7.4.17 MIT
symfony/event-dispatcher 8.1.5 MIT
symfony/event-dispatcher-contracts 3.7.1 MIT
symfony/finder 7.4.19 MIT
symfony/http-foundation 7.4.19 MIT
symfony/http-kernel 7.4.19 MIT
symfony/mailer 7.4.19 MIT
symfony/mime 7.4.19 MIT
symfony/polyfill-ctype 1.37.0 MIT
symfony/polyfill-intl-grapheme 1.41.0 MIT
symfony/polyfill-intl-idn 1.42.0 MIT
symfony/polyfill-intl-normalizer 1.42.0 MIT
symfony/polyfill-mbstring 1.38.2 MIT
symfony/polyfill-php80 1.37.0 MIT
symfony/polyfill-php83 1.41.0 MIT
symfony/polyfill-php84 1.38.1 MIT
symfony/polyfill-php85 1.41.0 MIT
symfony/polyfill-uuid 1.37.0 MIT
symfony/process 7.4.19 MIT
symfony/routing 7.4.18 MIT
symfony/service-contracts 3.7.3 MIT
symfony/string 8.1.7 MIT
symfony/translation 8.1.5 MIT
symfony/translation-contracts 3.7.1 MIT
symfony/uid 7.4.17 MIT
symfony/var-dumper 7.4.18 MIT
tijsverkoyen/css-to-inline-styles 2.4.0 BSD-3-Clause
vlucas/phpdotenv 5.7.0 BSD-3-Clause
voku/portable-ascii 2.1.1 MIT

Admin panel scripts (npm, bundled into public/build/)

Package Version License
@kurkle/color 0.3.4 MIT
@types/trusted-types 2.0.7 MIT
@vue/reactivity 3.5.43 MIT
@vue/shared 3.5.43 MIT
alpinejs 3.17.4 MIT
chart.js 4.5.1 MIT
dompurify 3.4.16 MPL-2.0 OR Apache-2.0 (used under Apache-2.0)
leaflet 1.9.4 BSD-2-Clause
trix 2.1.19 MIT

Rider and Driver apps (Flutter, runtime packages of both apps)

Package Version License
Flutter SDK (flutter, flutter_localizations, flutter_web_plugins, sky_engine) 3.44.4 BSD-3-Clause
_flutterfire_internals 1.3.77 BSD-3-Clause
args 2.7.0 BSD-3-Clause
async 2.13.1 BSD-3-Clause
characters 1.4.1 BSD-3-Clause
clock 1.1.2 Apache-2.0
code_assets 1.2.1 BSD-3-Clause
collection 1.19.1 BSD-3-Clause
convert 3.1.2 BSD-3-Clause
cross_file 0.3.5+5 BSD-3-Clause
crypto 3.0.7 BSD-3-Clause
csslib 1.0.2 BSD-3-Clause
dbus 0.7.15 MPL-2.0
dio 5.11.1 MIT
dio_web_adapter 2.2.2 MIT
ffi 2.2.0 BSD-3-Clause
ffi_leak_tracker 0.1.2 BSD-3-Clause
file_selector_linux 0.9.4+1 BSD-3-Clause
file_selector_macos 0.9.5+1 BSD-3-Clause
file_selector_platform_interface 2.7.0 BSD-3-Clause
file_selector_windows 0.9.3+6 BSD-3-Clause
firebase_app_check 0.4.8 BSD-3-Clause
firebase_app_check_platform_interface 0.4.2+1 BSD-3-Clause
firebase_app_check_web 0.2.6+1 BSD-3-Clause
firebase_auth 6.7.0 BSD-3-Clause
firebase_auth_platform_interface 9.1.0 BSD-3-Clause
firebase_auth_web 6.3.0 BSD-3-Clause
firebase_core 4.15.0 BSD-3-Clause
firebase_core_platform_interface 8.1.1 BSD-3-Clause
firebase_core_web 3.12.0 BSD-3-Clause
firebase_database 12.6.0 BSD-3-Clause
firebase_database_platform_interface 0.4.0+7 BSD-3-Clause
firebase_database_web 0.2.7+14 BSD-3-Clause
firebase_messaging 16.7.0 BSD-3-Clause
firebase_messaging_platform_interface 4.10.0 BSD-3-Clause
firebase_messaging_web 4.2.5 BSD-3-Clause
fixnum 1.1.1 BSD-3-Clause
flutter_jailbreak_detection 1.10.0 BSD-3-Clause
flutter_local_notifications 22.3.1 BSD-3-Clause
flutter_local_notifications_linux 8.0.1 BSD-3-Clause
flutter_local_notifications_platform_interface 12.2.0 BSD-3-Clause
flutter_local_notifications_web 1.0.0 BSD-3-Clause
flutter_local_notifications_windows 3.1.1 BSD-3-Clause
flutter_plugin_android_lifecycle 2.0.35 BSD-3-Clause
flutter_secure_storage 11.2.0 BSD-3-Clause
flutter_secure_storage_darwin 0.4.3 BSD-3-Clause
flutter_secure_storage_linux 3.0.3 BSD-3-Clause
flutter_secure_storage_platform_interface 2.1.1 BSD-3-Clause
flutter_secure_storage_web 2.1.1 BSD-3-Clause
flutter_secure_storage_windows 4.2.2 BSD-3-Clause
freerasp 8.2.2 MIT
geoclue 0.1.1 MPL-2.0
geolocator 14.0.3 MIT
geolocator_android 5.0.3 MIT
geolocator_apple 2.3.14 MIT
geolocator_linux 0.2.6 MIT
geolocator_platform_interface 4.3.0 MIT
geolocator_web 4.1.4 MIT
geolocator_windows 0.2.5 MIT
get 4.7.3 MIT
get_storage 2.1.1 MIT
google_identity_services_web 0.3.3+1 BSD-3-Clause
google_maps 8.3.0 Apache-2.0
google_maps_flutter 2.18.1 BSD-3-Clause
google_maps_flutter_android 2.19.13 BSD-3-Clause
google_maps_flutter_ios 2.18.6 BSD-3-Clause
google_maps_flutter_platform_interface 2.17.0 BSD-3-Clause
google_maps_flutter_web 0.6.3+1 BSD-3-Clause and MIT
google_sign_in 7.2.0 BSD-3-Clause
google_sign_in_android 7.2.17 BSD-3-Clause
google_sign_in_ios 6.3.5 BSD-3-Clause
google_sign_in_platform_interface 3.1.0 BSD-3-Clause
google_sign_in_web 1.1.3 BSD-3-Clause
gsettings 0.2.8 MPL-2.0
hooks 2.0.2 BSD-3-Clause
html 0.15.7 MIT
http 1.6.0 BSD-3-Clause
http_parser 4.1.2 BSD-3-Clause
image_picker 1.2.3 BSD-3-Clause and Apache-2.0
image_picker_android 0.8.13+23 BSD-3-Clause and Apache-2.0
image_picker_for_web 3.1.1 BSD-3-Clause
image_picker_ios 0.8.13+7 BSD-3-Clause and Apache-2.0
image_picker_linux 0.2.2 BSD-3-Clause
image_picker_macos 0.2.2+1 BSD-3-Clause
image_picker_platform_interface 2.11.1 BSD-3-Clause
image_picker_windows 0.2.2 BSD-3-Clause
intl 0.20.2 BSD-3-Clause
jni 1.0.3 BSD-3-Clause
jni_flutter 1.0.3 BSD-3-Clause
jni_util 1.0.0 BSD-3-Clause
json_annotation 4.12.0 BSD-3-Clause
logging 1.3.0 BSD-3-Clause
material_color_utilities 0.13.0 Apache-2.0
meta 1.18.0 BSD-3-Clause
mime 2.1.0 BSD-3-Clause
objective_c 9.5.0 BSD-3-Clause
package_config 3.0.0 BSD-3-Clause
package_info_plus 10.2.1 BSD-3-Clause
package_info_plus_platform_interface 4.1.0 BSD-3-Clause
path 1.9.1 BSD-3-Clause
path_provider 2.1.6 BSD-3-Clause
path_provider_android 2.3.1 BSD-3-Clause
path_provider_foundation 2.6.0 BSD-3-Clause
path_provider_linux 2.2.2 BSD-3-Clause
path_provider_platform_interface 2.1.3 BSD-3-Clause
path_provider_windows 2.3.0 BSD-3-Clause
petitparser 7.0.2 MIT
platform 3.2.0 BSD-3-Clause
plugin_platform_interface 2.1.8 BSD-3-Clause
pub_semver 2.2.1 BSD-3-Clause
record_use 0.6.0 BSD-3-Clause
sanitize_html 2.2.0 Apache-2.0
sign_in_with_apple 8.2.0 MIT
sign_in_with_apple_platform_interface 2.0.0 MIT
sign_in_with_apple_web 3.0.0 MIT
source_span 1.10.2 BSD-3-Clause
stream_transform 2.1.2 BSD-3-Clause
string_scanner 1.4.1 BSD-3-Clause
term_glyph 1.2.2 BSD-3-Clause
timezone 0.11.1 BSD-2-Clause
typed_data 1.4.0 BSD-3-Clause
url_launcher 6.3.2 BSD-3-Clause
url_launcher_android 6.3.33 BSD-3-Clause
url_launcher_ios 6.4.2 BSD-3-Clause
url_launcher_linux 3.2.3 BSD-3-Clause
url_launcher_macos 3.2.6 BSD-3-Clause
url_launcher_platform_interface 2.3.2 BSD-3-Clause
url_launcher_web 2.4.3 BSD-3-Clause
url_launcher_windows 3.1.6 BSD-3-Clause
uuid 4.6.0 MIT
vector_math 2.2.0 BSD-3-Clause
web 1.1.1 BSD-3-Clause
webview_flutter 4.14.1 BSD-3-Clause
webview_flutter_android 4.14.1 BSD-3-Clause
webview_flutter_platform_interface 2.15.1 BSD-3-Clause
webview_flutter_wkwebview 3.26.1 BSD-3-Clause
win32 6.4.0 BSD-3-Clause
xdg_directories 1.1.0 BSD-3-Clause
xml 7.0.1 MIT
yaml 3.1.4 MIT

Troubleshooting & FAQ

Most problems come from a missing setting or a mismatched tool version, and the error message usually names it.

Before anything else

  • Backend: read the newest entry in storage/logs/laravel.log, and run php artisan optimize after every .env change. Keep the scheduler and the queue worker running (Scheduler and queue worker).

  • Apps: run flutter doctor and fix what it reports, then start clean in the app's folder:

    flutter clean
    flutter pub get
    

Where to find the fix

Problem Where to look
The installer, the admin panel or the API shows an error; rides, sign-in codes, emails or payments don't work Deployment → Troubleshooting
flutter doctor reports a problem Mobile app setup → 15.1 flutter doctor
flutter pub get fails, or the editor can't find a package 15.2 flutter pub get
An Android build fails: SDK licences, downloads, a Firebase file, Java, Gradle or memory 15.3 Android build
An iOS build fails: CocoaPods, a missing module, a Firebase file or Xcode 15.4 iOS build and CocoaPods
Xcode asks for a team or a signing certificate 15.5 iOS signing
Push notifications or live ride updates don't work 15.6 Firebase
Live updates stop, or Firebase sign-in codes fail, after you enforce App Check 10.3 App Check
App Check shows your release builds as unverified, or the app prints App Check not started 15.6 Firebase
An app can't reach the backend 15.7 Backend connection
The map has no streets, or the position doesn't show 15.8 Maps and location
A release build isn't signed with your upload key 13.5 Create key.properties

FAQ

Which phones do the apps run on?
Android 7.0 (API 24) or newer, and iPhones with iOS 15.0 or newer, in portrait. Building the iOS apps needs a Mac with Xcode.

What does the server need?
PHP 8.4.1 or newer and MySQL 8+ or MariaDB 10.4+, plus a cron entry and a queue worker; installing also needs Composer 2 and Node.js 20.19+, on the server or on your computer (Requirements). A VPS and cPanel shared hosting both work; on shared hosting, you run Composer and npm on your computer, upload the folder, and the web installer at /install sets up the database and settings (Shared hosting).

Can I try everything before I have SMS or payment keys?
Yes. Demo mode adds demo riders and drivers, a fixed sign-in code and a Demo payment gateway (Local testing and demo mode, Demo credentials). Switch it off before going live.

Do the apps need Firebase?
No, they build and run without it, and ask the backend for ride updates every few seconds. Add your two Firebase files for push notifications and instant ride updates; Google sign-in and Firebase phone sign-in codes need them too (10. Firebase Configuration).

What is App Check, and do I need it?
It's optional extra protection. The database rules already let each rider and driver read only their own rides and offers (Firebase); App Check also makes Firebase refuse requests that don't come from your genuine apps, using Play Integrity on Android and App Attest on iOS. Both apps turn it on, but nothing is blocked until you enforce it in the Firebase console, once your store builds show up as verified (10.3 App Check).

Will App Check block my debug builds?
Only after you enforce it. Then each debug build needs a debug token registered in the Firebase console and passed with --dart-define=APP_CHECK_DEBUG_TOKEN=…. A build App Check refuses still signs in to your backend and polls for ride updates; only Firebase phone sign-in codes stop working (10.3 App Check).

How do the apps find my backend?
Pass its address to every run and build: --dart-define=API_BASE_URL=https://your-domain.com. Release builds need https:// (9. API Configuration).

Which payment methods are there?
Cash, wallet, card (through Stripe) and pay online (through Stripe, Razorpay, PayPal or Flutterwave). You switch them on and enter the gateway keys in the admin panel (Payment gateways).

Which maps does RideX use?
Google Maps. Each app needs a Maps SDK key, and the backend a server key for place search and routes (Google Maps).

Can I rebrand without building the apps again?
The names, colours, logo, currency and languages inside the apps come from Admin → Settings, so they change without a new build (White-labeling). The name under the icon, the icon, the splash screen and the package name are built into the apps (11. App Configuration).

Which languages are included?
English, Arabic (right-to-left), Spanish, French and Bengali, in both apps, API messages, notifications and emails (Adding a language).

Can both apps use the same upload keystore?
Yes. Each app's android/key.properties can point to the same keystore (13.4 Create an upload keystore).

How do I install an update?
Follow Updating: it keeps your .env and storage/, then runs the new migrations.

Support & Contact

If you run into a problem, have a question, or need help with customization, please contact us. Many answers are already in Troubleshooting & FAQ.

WhatsApp: +880 1744165013

Email: codercampapp@gmail.com

You can also use the Support tab on the RideX item page on CodeCanyon. Item support follows Envato's item support policy: questions about the item and fixes for reported bugs. Customization, installation on your servers, and third-party services such as your Firebase, Google Cloud, Apple and Google Play accounts are outside item support.

To help us answer quickly, tell us:

  • what it is about: the backend and admin panel, the Rider app or the Driver app, and Android or iOS;
  • the exact error message, and the step or command that caused it;
  • your versions: php -v for the backend, flutter --version for the apps;
  • what you have already tried from this documentation.
↑