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.
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.
cargoprodriver/pubspec.yamlGet 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.
ApiConfig.baseUrl β read by every
endpoint path in the same fileAdd 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.
cargoprodriver/lib/data/push_service.dartRun 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 uploadCreate 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.
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:
| Permission | Why |
|---|---|
android.permission.CAMERA | Barcode 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.INTERNET | Queueing work while out of signal, then syncing when it returns. |
android.permission.POST_NOTIFICATIONS | Showing delivery notifications on Android 13+. |
<queries> DIAL / VIEW | Opening the customer's address in Google Maps and dialling their number from the stop screen (url_launcher). |
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.png | 48Γ48 |
mipmap-hdpi/ic_launcher.png | 72Γ72 |
mipmap-xhdpi/ic_launcher.png | 96Γ96 |
mipmap-xxhdpi/ic_launcher.png | 144Γ144 |
mipmap-xxxhdpi/ic_launcher.png | 192Γ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.comwith password12345678β 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.