Version 1.0.0Web + App

Mobile App: Flutter for Android & iOS

Build the technician, manager and customer app against your server, rebrand it, sign it and publish it.

Item: FieldOps | Multi-Vendor Service Management Platform With Flutter Mobile App  ·  Stack: Laravel 13 · PHP 8.3+ · MySQL 8 · Flutter 3.47 (Dart 3.13)

Before You Start

Placeholders in this guide: app.example.com stands for your own server address, and upload and upload-keystore.jks are sample key names you can change. Paths such as android/key.properties are relative to the AppSourceCode folder.

The app works with your own FieldOps web install: build it with your server’s URL, your own application id, name and icon, your own Firebase project (optional, for push) and your own Google Play and Apple developer accounts. Nothing points at the author’s servers.

The app was built and tested as debug builds against a local server; it was not tested on a real device (Android or iOS). Test a release build on real Android and iOS phones before publishing.

What the App Does

A native Flutter app (no WebView) for technicians, managers and customers: 72 screens in 26 feature folders, talking to the web platform through /api/v1 with Sanctum tokens. It is offline-first, uses GPS for check-in and live tracking, and ships in English, Bangla, Arabic (RTL) and French with light, dark and system themes.

Technician

Jobs, statuses, check-in/out, checklist, photos, parts, signatures, invoice and payment, estimates, AI service report, chat.

Open →
Manager / dispatcher

Dispatch board, team jobs, assign, emergency job, technician ratings, website live-chat inbox.

Open →
Customer

Customer panel: requests, bookings, tracking, quotations, invoices and online payment, PDFs.

Open →
Offline-first

Read-through cache and an outbox with idempotency keys, retries with backoff and a Pending uploads page.

Open →
Push (optional)

Firebase Cloud Messaging and local notifications; tapping a job push opens that job.

Open →

Screens

Sign-in

E-mail or phone login, two-factor code, forgot and change password, fingerprint / Face ID unlock, one-tap demo logins on a demo server (“Try the demo”), a Customer sign in button that opens the customer panel, and a “workspace blocked” screen for vendor accounts with unpaid subscriptions.

Login
Login
Two-factor code
Two-factor code
Technician job flow

Jobs list and job detail; statuses assigned → accepted → on the way → arrived → in progress → awaiting approval → completed; check-in and check-out with GPS; arrival and completion verification codes; checklist; before/after photo evidence; parts used and returned from van stock; job notes and escalation history; complete with customer and technician signatures; invoice, collect payment and share the receipt; on-site estimates from the price book with customer approval; AI service report; job chat.

Jobs
Jobs
Job detail
Job detail
Photo evidence
Photo evidence
Signatures
Signatures
Invoice & payment
Invoice & payment
Technician day

Dashboard and earnings, today’s route, attendance (clock in/out, any number of breaks), timesheets and trips, leave requests, expenses with receipts, warranty claims, stock requests, van stock, my vehicle (fuel, odometer, vehicle checks), my tools (QR check-out and return), asset QR scan with asset detail and service request, checklist templates, scorecard, knowledge-base Q&A, notifications, profile and the More menu. Number fields accept valid values only (amounts with 2 decimals, odometer up to 7 digits).

Dashboard
Dashboard
Today’s route
Today’s route
Attendance
Attendance
My vehicle
My vehicle
Managers and dispatchers

Shown to users with the work-orders.manage permission (More → Dispatcher mode): dispatch board, team jobs, assign with suggested technicians, reschedule, emergency job, technician ratings and the website live-chat inbox. Every user also sees platform announcements and can open support tickets to the platform (vendors).

Dispatch board
Dispatch board
Assign job
Assign job
Live-chat inbox
Live-chat inbox
Offline, GPS and push
  • Offline: jobs, job detail, parts, notes, documents, checklist templates, van stock, invoices and permissions are cached in sqflite. Every field action (status, check-in/out, checklist, evidence, complete, parts, notes, chat, payment, fuel, odometer, vehicle check, tools) goes to an outbox first with an Idempotency-Key, is replayed in order and retried with backoff (5 s doubling, up to 10 minutes). More → Pending uploads shows Sync now / Retry / Discard.
  • GPS: location pings while a job is “on the way” (the customer’s tracking link) and opt-in background location while clocked in or on a trip (Profile → App → Background tracking).
  • Push: FCM and local notifications when Firebase is configured.
Dark theme
Dark theme
Arabic (RTL)
Arabic (RTL)

Customer Panel

Customers use the same app. Customer sign in on the login screen opens /login/customer: e-mail and password of their customer portal account (the same login as /portal). The customer token is stored apart from a staff token; a signed-in customer always lands in the customer panel. “Reset customer password” and “Create a customer account” open the portal pages in the browser; on a demo server “Try the demo · Customer panel” signs in with one tap.

TabWhat the customer does
OverviewAmount due, upcoming visits, open requests, pending quotes, next visit; Request service and Book service (opens the company’s booking page)
ServicesService catalogue (request a service), service requests, appointments (Track the technician, Review after completion), assets, warranty claims, contracts; new request or warranty claim
BillingInvoices with Pay (choose a gateway, pay in the browser, or see offline instructions), quotations (approve a Good/Better/Best option or reject with a reason), payments; PDFs shared from the phone
AccountProfile (name, phone, address, city, postal code), change password, sign out

A company switcher appears when the customer deals with several companies (marketplace). Phones show a bottom navigation bar, tablets (840 px and wider) a navigation rail. The panel talks to /api/v1/customer (29 routes, see REST API v1).

Requirements

ItemVersion
Flutter / DartFlutter 3.47 with Dart 3.13 (pubspec.yaml: sdk: ^3.13.5)
AndroidAndroid Studio with the Android SDK, JDK 17 (the Gradle build targets Java 17); min/target SDK follow Flutter’s defaults
iOSA Mac with Xcode, an Apple Developer account; deployment target iOS 15.0
BackendYour FieldOps web install reachable over HTTPS (or your computer’s IP for local testing)

Setup & Run

cd AppSourceCode
flutter pub get                       # also generates the translations (lib/l10n/gen)
flutter run                           # Android emulator -> http://10.0.2.2:8000
flutter run --dart-define=API_BASE_URL=http://192.168.1.20:8000   # phone or iOS simulator on your network
flutter run --dart-define=API_BASE_URL=https://app.example.com    # your live server

Sign in with a technician or company account of your web install, or tap Customer sign in with a customer portal account. On a demo install (APP_DEMO=true) the login screens offer one-tap demo logins.

API URL Configuration

The server address is a compile-time value (lib/core/config/app_config.dart):

DefineDefaultMeaning
API_BASE_URLhttp://10.0.2.2:8000 on Android (the emulator’s alias for your computer), http://127.0.0.1:8000 on iOS and other platformsRoot URL of the web install, without /api; the app calls {API_BASE_URL}/api/v1 and loads files from {API_BASE_URL}/storage/…

Always pass --dart-define=API_BASE_URL=https://your-domain.com to flutter run and flutter build. A release built without it points at 10.0.2.2 / 127.0.0.1 and cannot sign in.

For a release, use HTTPS. The Android manifest allows plain HTTP (android:usesCleartextTraffic="true") so local testing works; set it to false in android/app/src/main/AndroidManifest.xml once you only use HTTPS. iOS allows plain HTTP only on the local network (NSAllowsLocalNetworking).

Firebase Push (Optional)

No Firebase files are shipped and the app runs without them (push stays off). To turn push on:

  1. Create a Firebase project

    Add an Android app with your application id and an iOS app with your bundle id (both com.fieldops.app until you rename them, Rebrand).

  2. Android

    Download google-services.json into android/app/. The Google Services Gradle plugin is applied automatically when the file exists (android/app/build.gradle.kts).

  3. iOS

    Add GoogleService-Info.plist to ios/Runner/ in Xcode (Runner target). Enable the Push Notifications capability and Background Modes → Remote notifications (the shipped Info.plist only declares location). Upload an APNs auth key (.p8) in Firebase → Cloud Messaging.

  4. Server

    Set FCM_DRIVER=fcm, FCM_PROJECT_ID and FCM_SERVICE_ACCOUNT_PATH on the web server, from the same Firebase project (Web Platform → Push).

  5. Test

    Sign in on a phone (the app registers its token with POST /api/v1/device-tokens), assign that technician a job and check the push arrives in background and foreground.

Rebrand: Name, Bundle Id, Icon

WhatWhere
Name, logo and colour shown inside the appNothing to change in code: the app loads them from your Platform settings (GET /api/v1/branding) and falls back to “FieldOps”
Name under the icon (Android)android:label in android/app/src/main/AndroidManifest.xml
Name under the icon (iOS)CFBundleDisplayName in ios/Runner/Info.plist
Fallback app titleappTitle in lib/l10n/app_en.arb (and app_bn, app_ar, app_fr)
Android application idapplicationId and namespace in android/app/build.gradle.kts; move MainActivity.kt from android/app/src/main/kotlin/com/fieldops/app/ to the matching folder and change its package line
iOS bundle id and teamXcode → Runner target → Signing & Capabilities (PRODUCT_BUNDLE_IDENTIFIER, currently com.fieldops.app; no DEVELOPMENT_TEAM is set: choose your own team)
App icon (Android)Replace the ic_launcher.png files in android/app/src/main/res/mipmap-*
App icon (iOS)Replace the images in ios/Runner/Assets.xcassets/AppIcon.appiconset (Xcode shows each size)
Launch screenandroid/app/src/main/res/drawable*/launch_background.xml, ios/Runner/Assets.xcassets/LaunchImage.imageset
Versionversion: 1.0.0+1 in pubspec.yaml (name + build number)

Choose the final application id before the first store upload; it cannot change afterwards. Firebase apps must be registered with the same id.

Android Signing & Build

Create an upload key once and keep it, with its passwords, backed up outside the project. Losing it means you can no longer update the app unless Play App Signing holds the app key.

keytool -genkey -v -keystore android/upload-keystore.jks -keyalg RSA -keysize 2048 -validity 10000 -alias upload
cp android/key.properties.example android/key.properties    # fill in the passwords

android/key.properties (git-ignored, like *.jks):

storePassword=CHANGE_ME
keyPassword=CHANGE_ME
keyAlias=upload
storeFile=../upload-keystore.jks     # relative to android/app/

Without key.properties the release build stops with an error, preventing an accidental debug-signed upload. For local testing only, the documented Gradle override can allow a debug-signed release; Play Console will reject that build.

# bump version: in pubspec.yaml first (the build number after + must grow with every upload)
flutter build appbundle --release --dart-define=API_BASE_URL=https://app.example.com
# -> build/app/outputs/bundle/release/app-release.aab
flutter build apk --release --dart-define=API_BASE_URL=https://app.example.com    # APK for direct installs

iOS Signing & Build

  1. Open the workspace

    Open ios/Runner.xcworkspace in Xcode.

  2. Signing

    Runner target → Signing & Capabilities: choose your team (none is preset), set your bundle id, keep Automatically manage signing on.

  3. Capabilities

    Background Modes → Location updates is already declared; add Push Notifications and Remote notifications only if you use Firebase.

  4. Permission texts
    ios/Runner/Info.plist explains camera, photo library, location (when in use and always) and Face ID use. Keep them accurate for your app; App Review reads them.
  5. Build and upload
    flutter build ipa --release --dart-define=API_BASE_URL=https://app.example.com
    # -> build/ios/ipa/*.ipa ; upload with Xcode Organizer or the Transporter app

Background Location (Play Console & App Store)

The app asks for location while in use for check-in/out, attendance, photo GPS tags and live tracking while a job is on the way, and in the background only after a technician opts in (Profile → App → Background tracking), while clocked in, on a trip or sharing live location. On Android it then runs a location foreground service with a persistent notification.

Permission (Android manifest)Why
ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATIONCheck-in, attendance, evidence GPS, live tracking
ACCESS_BACKGROUND_LOCATION, FOREGROUND_SERVICE, FOREGROUND_SERVICE_LOCATION, WAKE_LOCKOpt-in background tracking
CAMERAQR scan, evidence and receipt photos
POST_NOTIFICATIONSPush and local notifications
USE_BIOMETRICFingerprint / face login
Google Play declaration
  1. Location permissions form

    Play Console → App content → Location permissions: declare background location access for the core feature “live job tracking and mileage for field technicians while they are clocked in or on a trip”.

  2. Video

    Record a short video showing the in-app explanation, the opt-in in Background tracking, the system permission prompt and the feature in use (tracking link or trip), and add its link.

  3. Foreground service

    Declare the location foreground service type in the Foreground service permissions form with the same use.

  4. Data safety

    List precise location, photos, name, e-mail and phone as collected for app functionality, sent to your own server.

If you do not want background tracking, remove ACCESS_BACKGROUND_LOCATION and the foreground-service permissions from the manifest and location from UIBackgroundModes; the opt-in then cannot be granted, and in-use location still works.

Publishing

Account deletion: customers can delete their account in the app (Account → Delete account) and in the customer portal (Profile → Delete account). In Google Play and App Store Connect, enter https://app.example.com/portal/delete-account as the account-deletion URL, using your own domain in place of the placeholder.

  1. Google Play

    Create the app in Play Console, enrol in Play App Signing, upload app-release.aab to Internal testing, complete store listing, content rating, data safety and the location declaration, then promote to production.

  2. App Store

    Create the app in App Store Connect with the same bundle id, upload the .ipa, test with TestFlight, add screenshots, privacy details and a demo account for App Review (a technician login on your server), then submit.

  3. Review accounts

    Give reviewers a working technician account on your live server; the app has no content without a backend.

  4. Updates

    Raise the build number in pubspec.yaml for every upload; keep the same signing key and bundle id.

Accounts & Costs

Google Play Console (one-time registration fee) and the Apple Developer Program (yearly fee) to publish. Firebase Cloud Messaging has no charge. A Mac with Xcode is needed for iOS builds.

Project Layout

PathContents
lib/main.dart, app.dart, router.dartEntry point, MaterialApp with go_router and the auth redirect
lib/core/Config (API_BASE_URL), API client (Dio), token storage, location service, branding, shared widgets
lib/core/offline/sqflite cache and outbox, connectivity, file vault
lib/core/push/Firebase Messaging and local notifications
lib/features/<feature>/26 features (auth, customer, dashboard, jobs, assets, checklists, attendance, expenses, leave, warranty, notifications, profile, home, inventory, billing, manager, ai, chat, estimates, fleet, platform, route, scorecard, stock_requests, timesheets, tracking), each with data / domain / presentation
lib/l10n/app_en.arb, app_bn.arb, app_ar.arb, app_fr.arb (generated code in lib/l10n/gen)
test/26 test files (198 tests): flutter test

Troubleshooting

“Cannot reach the server” or the login spins

The build has the wrong API_BASE_URL (default 10.0.2.2), the phone cannot reach that address, or the certificate is invalid. Open https://your-domain/up in the phone’s browser.

Login works on the web but not in the app

The user must be active (an invited user first sets a password from the invite e-mail); technicians with a custom role need inventory.view for the parts list. Customers must use Customer sign in, not the staff form. The account must belong to an active company; vendor accounts with an unpaid subscription see the workspace-blocked screen. Accounts with two-factor authentication get the code step.

Images and photos do not load

Run php artisan storage:link on the server and check APP_URL.

No push notifications

Firebase files missing, a different Firebase project than FCM_PROJECT_ID, FCM_DRIVER still log, no queue worker on the server, or (iOS) no APNs key and capabilities.

Actions stay in “waiting to sync”

The phone is offline or the server answers with an error; More → Pending uploads shows each item and its error. Rejected items (403/404/409/422) are shown in red and can be discarded.

Play Console rejects the bundle

The local debug-signing override was used, the build number was not increased, or the background-location declaration is incomplete. A normal release build stops when android/key.properties is missing.