Docs / Install the Driver App
🏠 Home

Install the Driver App

Build the Flutter driver app for Android and iOS, point it at your CargoPRO server, and rebrand the icon and splash screen.

Flutter 3.41+ Dart 3.11+ Android Β· iOS Portrait only Offline sync

Setup

Install Flutter 3.41+

The driver app lives in the cargoprodriver/ folder of your package. Its pubspec.yaml requires flutter: ">=3.41.0" and sdk: ^3.11.0. Run flutter doctor and fix any blocking issues before continuing.

File: cargoprodriver/pubspec.yaml

Get the packages

cd cargoprodriver
flutter pub get

Point the app at your server

The base URL is defined once, in cargoprodriver/lib/core/api_config.dart, as the baseUrl field:

static const String baseUrl = String.fromEnvironment(
  'CARGOPRO_API_BASE',
  defaultValue: 'https://sorry-conferences-eating-ranks.trycloudflare.com/api',
);

The default value is a temporary Cloudflare tunnel used during development β€” it will stop working. Override it per build with a dart-define; the URL must include the /api prefix:

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

Alternatively, edit the defaultValue directly in lib/core/api_config.dart so every build targets your server without extra flags. The same value must be passed on flutter build commands, or the release APK will still point at the tunnel.

Field: ApiConfig.baseUrl β€” read by every endpoint path in the same file

Add Firebase (optional β€” for push only)

Push notifications activate only when a Firebase config is bundled: android/app/google-services.json on Android or GoogleService-Info.plist on iOS. Without one, PushService catches the failed Firebase.initializeApp() and disables itself β€” the app builds and runs exactly as before. See the Firebase & Push guide.

File: cargoprodriver/lib/data/push_service.dart

Run and build

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

flutter build apk --release --dart-define=CARGOPRO_API_BASE=https://your-domain.com/api
flutter build appbundle --release --dart-define=CARGOPRO_API_BASE=https://your-domain.com/api

Use appbundle for the Google Play Store and apk for direct distribution. iOS builds require a Mac with Xcode; run flutter build ipa there and upload the result to App Store Connect.

Build a signed release (APK & app bundle)

Release builds must be signed with your own keystore. The driver app ships with the Gradle signing configuration already in place β€” it reads android/key.properties, so you only create the keystore and the properties file:

Create the keystore and key.properties

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

Create cargoprodriver/android/key.properties:

storePassword=YOUR_STORE_PASSWORD
keyPassword=YOUR_KEY_PASSWORD
keyAlias=upload
storeFile=upload-keystore.jks

storeFile resolves relative to android/app/ β€” copy the keystore there. Use a different keystore than the user app's, and back it up: every store update must be signed with the same key.

Never commit or ship these files key.properties and .jks files are your private signing credentials β€” the app's .gitignore already excludes them; keep them out of any zip you publish too.

Build signed artifacts

flutter build apk --release --dart-define=CARGOPRO_API_BASE=https://your-domain.com/api
flutter build appbundle --release --dart-define=CARGOPRO_API_BASE=https://your-domain.com/api

Outputs: build/app/outputs/flutter-apk/app-release.apk and build/app/outputs/bundle/release/app-release.aab. Remember the --dart-define on every build β€” a release without it silently points at the default URL. Verify with jarsigner -verify and upload the .aab in Play Console, bumping the build number each time. The same steps with the user app's folder produce its artifacts (user app build guide).

DEMO_MODE β€” demo shift without a backend

The app can run a fully seeded demo shift offline, which is handy for sales demos and UI walkthroughs when no backend is reachable. Build with the DEMO_MODE dart-define, declared in cargoprodriver/lib/main.dart:

/// Build with `--dart-define=DEMO_MODE=true` to run the seeded shift instead of
/// talking to a backend β€” useful for showing the app without a reachable API.
const bool _demoMode = bool.fromEnvironment('DEMO_MODE');
flutter run --dart-define=DEMO_MODE=true

In demo mode the app swaps the API repository for an InMemoryDriverRepository β€” sign in with any non-empty username and password and work a pre-seeded route. Nothing leaves the phone and nothing needs a server. Never ship a DEMO_MODE=true build to real drivers.

What is built in (and affects setup)

πŸ“±

Portrait-locked UI

main.dart forces DeviceOrientation.portraitUp β€” drivers work one-handed in a van. You do not need to configure anything; do not be surprised that the app refuses to rotate.

πŸ“€

Offline sync queue (outbox)

A close-out is always sent as it happens; with no connection it is persisted on the phone (SharedPreferences key cargopro.driver.outbox) and replayed on the next successful refresh. Deliveries and failed attempts queue; the queue is cleared on sign-out.

✍️

Signature capture

Proof of delivery captures the recipient's name, an on-screen signature (sent as a PNG data URL), camera photos and any cash collected β€” all in one POST /api/shipments/{id}/deliver multipart call.

πŸ“·

Scanning and photo proof

Label scanning (mobile_scanner) and camera photos (image_picker) need the camera permission declared in the AndroidManifest β€” see the table below.

Android permissions already declared

cargoprodriver/android/app/src/main/AndroidManifest.xml ships with everything the app needs β€” you should not have to touch it:

PermissionWhy
android.permission.CAMERABarcode scanning and photo proof of delivery. The uses-feature for the camera is marked required="false", so the app still installs on camera-less devices.
android.permission.INTERNETQueueing work while out of signal, then syncing when it returns.
android.permission.POST_NOTIFICATIONSShowing delivery notifications on Android 13+.
<queries> DIAL / VIEWOpening the customer's address in Google Maps and dialling their number from the stop screen (url_launcher).
App display name The launcher label is set in the same manifest: android:label="cargoprodriver". Change it to your brand name before release. The iOS display name is set in Xcode under Runner β†’ Target β†’ General β†’ Display Name.

Changing the app icon & splash

Replace the source logo

Everything is derived from one file: cargoprodriver/assets/logo.png. Replace it with your logo (keep the same path). The login screen brand mark loads assets/logo.png automatically, so the in-app branding updates with no code change.

Regenerate the Android launcher icons

There is no icon-generator package configured β€” the icons are plain PNGs checked into the repo. Export your logo at five densities and overwrite these files:

File (under android/app/src/main/res/)Size
mipmap-mdpi/ic_launcher.png48Γ—48
mipmap-hdpi/ic_launcher.png72Γ—72
mipmap-xhdpi/ic_launcher.png96Γ—96
mipmap-xxhdpi/ic_launcher.png144Γ—144
mipmap-xxxhdpi/ic_launcher.png192Γ—192

Update the Android splash image

Replace launch_image.png in the same five mipmap-*/ folders. The splash is drawn by drawable/launch_background.xml (and drawable-v21/), which already references @mipmap/launch_image, so no XML editing is needed.

Update the iOS assets

Replace the icons in ios/Runner/Assets.xcassets/AppIcon.appiconset/ (Icon-App-20 through Icon-App-1024, keeping the exact filenames the Contents.json lists) and the launch images in ios/Runner/Assets.xcassets/LaunchImage.imageset/ (LaunchImage.png, @2x, @3x).

Demo & testing accounts

  • Against your backend: if you imported the demo dataset, sign in as driver@devtrixx.com with password 12345678 β€” see Demo Data. That driver already has an assigned, seeded route.
  • Real drivers: driver accounts are created in the admin panel (userlevel driver, id 5 in the API), or a driver can self-register in the app β€” those accounts start inactive until an administrator approves them.
  • No backend at all: use DEMO_MODE above.

A failed attempt never closes the stop β€” the package stays on the driver's list for another attempt, and the stop screen offers a "Try again" action. Deliveries and failed attempts are the only two kinds of work the outbox queues.