Rider App

Sellino Food — go online, pick up, navigate, deliver with OTP
Version: 1.0.0 (Stable)
Tech Stack: Flutter · Dart · REST API
License: Envato Market – Regular / Extended

Developed By: BugBuild Labs

Introduction

The Rider App is the delivery side of Sellino Food. A rider goes online, receives the tasks assigned to them, collects from the kitchen, navigates to the drop pin and closes the delivery with an OTP.

One Flutter codebase for Android and iOS on the same /api/v10 backend. Riders belong either to the platform or to a single restaurant, and that choice is what decides who keeps the delivery fee.

While a rider is online the app shares its location — that is what draws the moving marker on the customer's tracking map. Nothing is sent while off duty.

Task listassets/images/sellino-food-app/dapp_tasks.jpg
Earningsassets/images/sellino-food-app/dapp_earnings.jpg

Requirements

  • Flutter SDK (stable channel) with Dart — flutter doctor should pass.
  • Android Studio (Android SDK 21+) and/or Xcode for iOS builds (macOS only).
  • A running Sellino Food backend reachable over HTTPS — set up first via Quick Start.
  • An approved staff login on that backend (cashier, branch manager or admin).
  • For push notifications: a Firebase project — see Common Setup.

Package & Source Code

The download's Main_Files/RiderAppSourceCode/ folder holds the complete Flutter project (next to WebSourceCode and the other two app folders). Open it in Android Studio or VS Code and run flutter pub get.

  • lib/core/ — environment config, API client, theme, shared widgets and controllers.
  • lib/features/ — one folder per screen area: delivery (online switch, location ping, tasks, pickup and delivery), earnings, orders, profile, notifications, settings.
  • assets/branding/ — app icon sources used by the launcher-icon generator.

App Configuration

  1. Open the RiderAppSourceCode folder in your IDE and run flutter pub get.
  2. Set the API base URL in lib/core/env/prod_env.dart (release builds) or lib/core/env/dev_env.dart (debug builds) — image URLs, websocket scheme and broadcasting auth URL are derived from it automatically.
  3. Paste the App API key (kAppApiKey) from Admin → Settings → API Security in the same file. Wrong or missing key → 401 on every screen.
  4. If you use real-time features, set kReverbAppKey to the backend's effective Reverb key (Settings → Realtime, else REVERB_APP_KEY from .env).
  5. Configure push notification keys if used — see Common Setup.
// lib/core/env/prod_env.dart   → used by RELEASE builds
class ProdEnv implements Env {
  static const String kBaseUrl      = 'https://yourdomain.com/api/v10';  // your API root
  static const String kAppApiKey    = 'PASTE-KEY-FROM-ADMIN-PANEL';      // Settings → API Security
  static const String kReverbAppKey = 'sellino-app-key';                 // backend .env REVERB_APP_KEY
}

// lib/core/env/dev_env.dart    → used by DEBUG / PROFILE builds (your local backend)
// imageBaseUrl, reverbScheme and broadcastingAuthUrl are all derived from kBaseUrl.

Tip: always use HTTPS — plain HTTP is blocked by default on Android/iOS. Built-in guard: a release build refuses to start if the key is still empty or the URL still points at a dev host (localhost, .test…), so a forgotten swap fails on your desk, not at the counter.

Never regenerate the App API key after release unless it leaked — installed apps stop working until users update. See Web → Security → App API Key.

App Name & Icon

  • Name: change the display name in android/app/src/main/AndroidManifest.xml (android:label) and iOS Info.plist (CFBundleDisplayName).
  • Package / bundle id: set your own unique id (Android applicationId in android/app/build.gradle; iOS bundle identifier in Xcode) before publishing.
  • Icon: replace the sources in assets/branding/ and regenerate launcher icons (flutter_launcher_icons).

Build, Signing & Release

Before either platform: confirm the release build points at your production backend — lib/core/env/prod_env.dart. The app's built-in guard throws on first launch if the key is empty or the URL is still a dev host, so a forgotten swap fails on your desk.

Android — release signing

1. Generate a keystore. Run this once and keep the file safe — Google Play ties your app to it forever; lose it and you cannot publish updates under the same listing.

keytool -genkey -v -keystore ~/sellino-food-release.jks \
  -keyalg RSA -keysize 2048 -validity 10000 -alias sellino-food

# keytool ships with the JDK. On Windows it is:
#   "%JAVA_HOME%\bin\keytool" -genkey -v -keystore %USERPROFILE%\sellino-food-release.jks ^
#     -keyalg RSA -keysize 2048 -validity 10000 -alias sellino-food

It asks for a keystore password, a key password and your name/organisation details. Note both passwords down — the next step needs them.

2. Create android/key.properties (same folder as android/app/), with exactly these four keys:

storePassword=your_keystore_password
keyPassword=your_key_password
keyAlias=sellino-food
storeFile=/Users/you/sellino-food-release.jks

# storeFile — absolute path, or a path relative to the android/ folder.
# Windows: use forward slashes, e.g. C:/Users/you/sellino-food-release.jks

Never commit key.properties or the .jks file to source control, and never ship them to anyone. They are the identity of your published app.

3. The Gradle wiring is already done. android/app/build.gradle.kts reads that file and applies it — nothing to edit, shown here so you know what happens:

// Loads key.properties if the buyer created it
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}

signingConfigs {
    create("release") {
        if (keystorePropertiesFile.exists()) {
            keyAlias      = keystoreProperties["keyAlias"] as String?
            keyPassword   = keystoreProperties["keyPassword"] as String?
            storeFile     = (keystoreProperties["storeFile"] as String?)?.let { file(it) }
            storePassword = keystoreProperties["storePassword"] as String?
        }
    }
}

buildTypes {
    release {
        // Your keystore when key.properties exists; debug signing otherwise,
        // so `flutter run --release` still works before you set one up.
        signingConfig = if (keystorePropertiesFile.exists())
            signingConfigs.getByName("release")
        else
            signingConfigs.getByName("debug")
    }
}

Play Store builds must not be debug-signed. If key.properties is missing, a release build now stops with a clear error instead of silently signing with the debug key (which Google Play rejects); create the file and build again. Create the file before your first release.

4. Build.

flutter build appbundle --release --obfuscate --split-debug-info=build/symbols   # .aab → Google Play
flutter build apk --release --obfuscate --split-debug-info=build/symbols        # .apk → direct install / other stores
# --obfuscate hides Dart symbol names in the binary; keep build/symbols safe — it is what
# turns an obfuscated crash log back into readable stack traces (flutter symbolize).

# Verify what signed it (should NOT say "Android Debug"):
keytool -printcert -jarfile build/app/outputs/bundle/release/app-release.aab

Also set your own applicationId in android/app/build.gradle.kts before publishing — the package ships as com.sellino.rider and every listing needs a unique id you own.

iOS — signing & distribution

iOS builds need macOS with Xcode and a paid Apple Developer Program membership (US$99/year — a third-party cost, not included).

  1. Bundle identifier. Open ios/Runner.xcworkspace in Xcode (the workspace, not the project). Select the Runner target → General → set your own reverse-domain Bundle Identifier (e.g. com.yourbrand.pos). It must match the App ID you register with Apple, and should mirror the Android applicationId.
  2. Signing team. Xcode → Settings → Accounts → add your Apple ID. Then on the Runner target → Signing & Capabilities → tick Automatically manage signing and pick your Team. Xcode then creates the development certificate and provisioning profile for you — this is the recommended route.
  3. Register the App ID at Identifiers using the same bundle id. If you use push, enable the Push Notifications capability here and generate the APNs key — see Common Setup.
  4. Create the app record in App Store Connect → My Apps → + → pick the bundle id, set name, primary language and SKU.
  5. Version & build number. Set them in pubspec.yaml (version: 1.0.0+1 — the part after + is the build number). Every upload to App Store Connect needs a build number higher than the last.
  6. Archive and upload.
    flutter build ipa --release --obfuscate --split-debug-info=build/symbols
    # then either open the generated .xcarchive in Xcode → Distribute App,
    # or upload build/ios/ipa/*.ipa with Transporter (free, Mac App Store)
    In Xcode the equivalent is Product → Archive → Distribute App → App Store Connect.
  7. TestFlight → review. The build appears in App Store Connect after processing (a few minutes to an hour). Test it via TestFlight, fill in the store listing, privacy details and screenshots, then submit for review.

Apple's review requires a reachable Privacy Policy URL and an in-app account deletion path. Both ship with the app — the sign-in screen links the policy (edited in Website → Pages) and Settings carries account deletion.

Store accounts (Google Play one-time US$25 / Apple Developer US$99 per year) are third-party services and are not included with this item.

For Developers — API Structure & Data Model

The app is a pure API client: it holds no business logic of its own and writes nothing the web panel cannot read. Everything goes through /api/v10/* on your Laravel backend.

Every request carries these headers

Accept:        application/json
X-App-Key:     <the App API key from Settings → API Security>   // rejected with 401 if wrong
Authorization: Bearer <jwt>                                     // authenticated calls only
X-Branch-Id:   <active branch id>                               // the multi-branch scope
Accept-Language: en | bn | ar | he                              // drives translated responses

X-Branch-Id is the heart of the app. Stock, counters, registers, orders and reports are all scoped to it server-side, so switching branch in the UI changes what every subsequent endpoint returns. It is restored from secure storage before the first request on launch.

Response envelope

Every endpoint answers in the same shape, so the app has one parser and one error path:

{ "status": true,  "message": "Request successfully executed", "data": { … } }
{ "status": false, "message": "Human readable error",          "data": {} }

// Paginated lists put the rows in data.items alongside:
//   current_page · last_page · per_page · total

Endpoint families

Routes live in routes/api/v10/ on the backend, one file per area. The app's back office uses the staff group (routes/api/v10/staff/, ~34 files) — each route behind auth:api, auth.staff and its own hasPermission:* gate:

auth pos cash-register counter orders products inventory warehouses branches categories brands attributes customers suppliers members accounts reports returns replacements coupon campaigns flash-sell users panel-features activity-logs archive

The same hasPermission:* names drive the app's drawer — see Web → Users & Roles. A role missing order_read gets no Orders menu item and a 403 if it calls the endpoint directly.

Core data model

EntityRelationship
BranchThe top-level scope. Owns warehouses, counters and staff assignments; every stock row, register and order belongs to one.
Warehouse → RackStock lives per warehouse; racks locate it inside one. The cart's rack picker reads these.
Counter → Cash RegisterA counter is the physical till; a register is one shift on it (open → sell → close). One open register per counter, one per user.
Product → VariantProducts carry attributes; the sellable unit is the variant (own SKU, barcode, price, stock).
Stock → Cost layersOn-hand quantity per variant per warehouse, backed by FIFO cost layers that purchases create and sales consume.
Order → Items → PaymentsA sale, its lines, and one or more payments (cash / bank / MFS split), tied to the register it was rung on.
Journal → TransactionsEvery money movement posts a balanced journal of ledger lines against Chart of Accounts heads — sales, expenses, pickups, purchases, returns.
User → Role → PermissionsRoles bundle permissions; users may also carry per-user overrides. This is what gates both menu and API.

Auth & token lifecycle

  • Login returns a JWT plus its expiry; the app stores it with flutter_secure_storage (Keychain on iOS, EncryptedSharedPreferences on Android) — never in plain preferences.
  • An interceptor refreshes the token through POST /login/refresh before expiry, and retries once on a 401; concurrent calls share one refresh instead of racing.
  • Auth endpoints are rate limited server-side (throttle middleware) against brute force.

Extending it

  • Add a backend route under routes/api/v10/staff/ with its hasPermission:* gate, then a matching service + controller in the app's feature folder — the folder layout under lib/features/ mirrors these route files closely.
  • lib/core/network/api_endpoints.dart holds every path in one place; dio_client.dart owns headers, refresh and error mapping.
  • Full endpoint-by-endpoint reference: docs/api-v10-endpoints.md in the backend source.

Login & Going Online

  • Sign in with the delivery account created in Delivery → Delivery men.
  • Online switch — the rider marks themselves available. Tasks only arrive while online.
  • Location ping — while online the app shares its position every few seconds, which is what moves the customer's tracking map. Off duty, nothing is sent.
  • Permissions — the app asks for location on first use and explains why; the platform requires it for background updates.
Loginassets/images/sellino-food-app/dapp_login.jpg
Online switchassets/images/sellino-food-app/dapp_online.jpg

Tasks

  • Task list — the deliveries assigned to this rider, newest first.
  • Task card — pickup outlet with its address, drop address, distance, the kitchen's state (still preparing? ready to collect?) and what the delivery earns.
  • Navigate — opens the phone's maps app to the outlet, then to the drop pin.
  • Call the customer or the restaurant from the card.
Task listassets/images/sellino-food-app/dapp_tasks.jpg
Task cardassets/images/sellino-food-app/dapp_task.jpg

Pickup & Delivery

  • Picked up — collected from the kitchen; the order moves to out-for-delivery and the customer sees it.
  • Delivered — the customer reads out their OTP, with an optional proof photo.
  • Only then is the order Delivered — which is where the platform recognises the revenue, so the proof matters.
  • Cash on delivery — the amount to collect is on the card, and what the rider collected is reconciled against their account.
Pickupassets/images/sellino-food-app/dapp_pickup.jpg
Deliver with OTPassets/images/sellino-food-app/dapp_deliver.jpg

Earnings & History

  • Earnings — commission per delivery plus tips, by day and by period.
  • History — every completed delivery with its outlet, drop and payout.
  • Payouts — what has been settled and what is still owed; tips are paid out of the platform's tip liability.
  • Notifications for new tasks, deep-linking to the task card.
Earningsassets/images/sellino-food-app/dapp_earnings.jpg
Historyassets/images/sellino-food-app/dapp_history.jpg