Skip to content
Abdullah Almutairi
All projects

Case study · Mobile app · Serverless backend

Anaaya Plus

A car-service booking app for the Saudi market: phone/OTP sign-in through serverless functions and Firebase, vehicle and location management, race-safe time-slot booking, and a tracked booking lifecycle in Arabic and English.

Role
Solo — product scope, Flutter client, Firestore data and rules, Edge Functions
Context
Personal project
Status
PrototypeTest application — payment, SMS and technician dispatch are simulated
Stack
  • Flutter
  • Dart
  • Riverpod
  • go_router
  • Firebase Auth
  • Cloud Firestore
  • FCM
  • Supabase Edge Functions
  • Deno
  • GitHub Actions
Anaaya Plus home screen in Arabic showing the upcoming service, a row of services with starting prices, and offers.
Date and time step of the booking flow with a strip of days and a grid of available time slots.
Service tracking screen with a vertical timeline: booking confirmed, technician assigned, technician on the way, in progress, completed.

Overview

Anaaya Plus is a Flutter app for booking at-home car services. A customer signs in with their phone number, chooses a service, vehicle, location and time slot, completes a test payment, and follows the booking through a technician-dispatch lifecycle. It is a portfolio/test application, not a live service.

Problem & requirements

A service booking depends on several steps that each need real backing: who the customer is, which car, where, and when — and two customers must never get the same slot. The aim was to implement that whole journey with real authentication, persistence and authorization instead of a set of static screens.

  • Phone-number sign-in with one-time codes, verified server-side.
  • Concurrent customers must not be able to claim the same time slot.
  • Arabic-first (RTL) with a full English (LTR) experience.
  • A zero-cost test environment: no real payments, SMS or dispatch.

My role

Solo project. I defined the scope and built the Flutter client, the Firestore data model and security rules, and the Supabase Edge Functions behind the OTP sign-in.

Key features

  • Phone + OTP sign-in

    Edge Functions generate, hash and verify one-time codes with expiry, attempt limits, resend cooldown, rate limiting and replay rejection, then mint a Firebase custom token the app signs in with.

  • Race-safe scheduling

    Slot availability lives in Firestore, and claiming a slot runs in a transaction so two clients cannot book the same time.

  • Vehicles, services and locations

    Vehicle management, a service catalogue with per-service options and pricing, and saved, current or map-picked locations (OpenStreetMap).

  • Booking lifecycle and notifications

    Booking, test payment, cancellation where the state allows it, a simulated Assigned → On the way → In progress → Completed lifecycle, and push notifications via FCM.

  • Arabic and English

    Arabic RTL and English LTR experiences generated from ARB sources with Flutter gen-l10n.

Architecture

Firebase is the session and application-data boundary; Supabase Edge Functions provide isolated backend execution for the custom OTP flow, so the authentication logic does not live in the client.

  1. Flutter client

    Feature modules (auth, booking, cars, location, payment, scheduling, notifications…) with repository abstractions over data access.

    • Flutter / Dart
    • Riverpod
    • go_router
    • gen-l10n
  2. Firebase

    Authenticated session, application data guarded by Firestore security rules, and push notifications.

    • Firebase Auth
    • Cloud Firestore + rules
    • FCM
  3. Supabase Edge Functions

    Deno functions for the OTP flow: send-otp stores a hashed code and sends it; verify-otp enforces the OTP controls and returns a Firebase custom token.

    • Deno
    • Firebase Admin (custom token)
  4. External services (test mode)

    SMS delivery through Twilio's test API; maps from OpenStreetMap.

    • Twilio TEST API
    • OpenStreetMap

Data model

Cloud Firestore, organised per user so security rules can scope every read and write to its owner. Shared slot documents record which user claimed them.

  • users/{userId}

    • profile

    Parent of all per-user data

  • cars

    • make, model, year, plate

    users/{userId}/cars

  • bookings

    • service, vehicle and location snapshots
    • price
    • status · statusUpdatedAt · estimatedArrival

    users/{userId}/bookings — snapshots and price are create-only; one payment sub-document

  • locations

    • saved addresses

    users/{userId}/locations

  • notifications · devices

    • notification history
    • device tokens for FCM

    users/{userId}/…

  • serviceSlots

    • claimedByUid

    Top-level; rules allow a claim only on the caller's own behalf

API design

Edge Functions invoked over HTTPS. The OTP functions are public because they are the authentication step; everything else requires a verified Firebase identity.

Anaaya Plus endpoints
POSTsend-otpGenerate, hash and store a one-time code and send it via the Twilio test API.
POSTverify-otpCheck { phone, otp } against the OTP controls; on success return a one-shot Firebase custom token and whether the user is new.
POSTverify-firebase-authVerify a Firebase ID token and return the caller's uid, without holding any Firebase secret.
GEThealth-checkConfirm the Edge Functions deployment responds.

Engineering decisions

  1. Custom OTP flow instead of Firebase phone auth

    Native Firebase phone authentication depends on real SMS delivery and device-integrity checks, which did not fit a zero-cost test setup. Edge Functions own the OTP logic and hand back a Firebase custom token.

    Trade-off: Two backend platforms and more moving parts than the native option (documented in ADR 0001).

  2. Serverless functions over a custom server

    A single monolithic server would add operational work without adding value at this scope; Edge Functions give an isolated backend boundary without running one.

  3. Authorization in Firestore rules

    Per-user data paths let security rules enforce ownership on the server, rather than trusting the client to filter data.

  4. Booking lifecycle enforced on the server

    Security rules allow an update to touch only the status fields and only along approved transitions (upcoming → on the way or cancelled, on the way → in progress → completed). The Dart-side check is a fast pre-check, not the only guard. Bookings can never be deleted by a client.

    Trade-off: There is no technician role yet, so the customer's own app drives the demo lifecycle, and payment records are client-written — acceptable only because payments are simulated.

  5. Simulate what would need real money or real people

    Payment, SMS and technician dispatch are explicitly test-mode, and the repository lists them as known limitations instead of hiding them.

Testing & reliability

Documented in the repository

The repository documents automated tests across the client and the backend functions. These results are taken from the repository's own testing document and were not re-run for this site.

  • Flutter unit, repository and widget tests: 629 passed, 0 failed (documented baseline).
  • Deno tests for the shared backend utilities: 70 passed, 0 failed (documented baseline).
  • Coverage includes OTP rules, custom-token exchange, booking/payment/notification state transitions, slot claiming, and Arabic/English widget flows.
  • A GitHub Actions workflow is configured for the Flutter project.

Screenshots

  • Anaaya Plus home screen in Arabic showing the upcoming service, a row of services with starting prices, and offers.
    Home — upcoming booking and service catalogue (Arabic, RTL)
  • Date and time step of the booking flow with a strip of days and a grid of available time slots.
    Scheduling — available slots for the selected day
  • Service tracking screen with a vertical timeline: booking confirmed, technician assigned, technician on the way, in progress, completed.
    Tracking — simulated technician-dispatch lifecycle
  • My cars screen listing a default vehicle and another vehicle, each with an edit button, and an add-car button.
    Vehicles — default car and saved cars

Outcome & next steps

A complete customer-side booking journey with real authentication, authorization and persistence, intentionally stopping short of production infrastructure.

Next steps

  • A real payment gateway and SMS delivery behind trusted backend services.
  • A technician-facing app and role, with live location instead of a simulated lifecycle.

Contact

Got something in mind?

Have a project, an idea, or just want to talk software? Send me an email. I’d be happy to hear from you.