01 / PROJECT

ECARRGO

A logistics reverse-auction marketplace with a controlled shipment state machine.

PLATFORMFull Stack Engineer2026
ECARRGO - cover visual
PROJECT ARTIFACTPRIMARY VISUAL

A two-role mobile app that turns shipping orders into a rate auction: senders post an order, logistics vendors bid, and the system locks the winner, payment, and tracking status into one deterministic flow.

02 / THE CHALLENGE

Orders were simple on paper.
Fulfillment was fragmented.

Shipping used to run through intermediaries: senders contacted several vendors separately and negotiated prices with no structured record. That pattern breaks at volume because prices cannot be compared, one order risks being held by more than one vendor, and shipment status has no single authority. When the vendor, sender, and admin see different states, an order stalls with no clear owner. Without an explicit state machine and a single data source, every status change can produce two contradicting truths.

KEY FAILURE MODES ADDRESSED
01

Shipping rates were negotiated manually over chat and phone, so prices went unrecorded, could not be compared, and orders were at risk of being placed with more than one vendor.

02

With no single source, shipment_id, auction_id, and waybill number were easily swapped between the fill-in, payment, and tracking screens, breaking the integrity chain of the shipment data.

03

Transfer proofs were received without a record tying them to a transaction, so payment verification depended on manual matching by an admin and could not be audited.

03 / SYSTEM FLOW & DECISIONS

One order. Multiple fulfillment paths.

Explore how the system splits each line item into independent operational branches while enforcing strict transactional convergence before dispatch.

Order Creation & Auction

Runs when a sender creates a new shipment and the system must produce one shipment with one linked auction.

Primary Case
STEP 01Sender

Select Route & Schedule

The app captures pickup and destination points via map and geolocation, then stores the schedule in FillDataProvider.

STEP 02Sender

Enter Cargo Details

Per-item validation rejects weight over 100 tons, or empty volume or value, before the data is provisionally locked.

STEP 03System

Set Auction Parameters

Fetches a rate reference from the generate-rates endpoint and ensures the auction duration does not exceed the shipping time.

STEP 04Sender

Submit Order

POST /auctions creates the shipment and auction together, returning auction_id, shipment_id, and resi_number that are shared to the providers.

KEY ARCHITECTURAL DECISIONS & TRADE-OFFS
01

SERVER STATUS AS A STATE MACHINE, NOT A CLIENT CACHE

UI steps are recomputed from the shipment status on the server, and local progress cache is reset when it is not backed by a confirmation.

RATIONALE

If the client decides its own steps, an order can stall on a stale step after the server status changed. Mapping status to steps and validating the cache keeps navigation consistent with backend authority even when the app is closed mid-flow.

02

ONE ACCUMULATOR PROVIDER FOR THE MULTI-STEP WIZARD

All five steps of input are stored in a single FillDataProvider with per-step validation before the payload is sent.

RATIONALE

Sending data per step invites partial orders and orphan shipment_ids. A single accumulation ensures the auction is created only when every field is valid, at the cost of holding wizard state longer on the client.

03

REVERSE AUCTION WITH SERVER-SIDE WINNER LOCKING

Winner selection happens through the sender's select-winner endpoint, not free negotiation on the client.

RATIONALE

A reverse auction lowers prices naturally, but without locking, one order can be held by many vendors. A dedicated endpoint ensures only the order owner sets the winner and prevents double assignment.

04

SECURE TOKENS AND AUTH-BOUND DEVICE SYNC

The access token is stored in secure storage with centralized Bearer injection, and the FCM token is registered following the login cycle.

RATIONALE

Storing the token in plain storage or spreading headers across every call increases leak risk and inconsistency. Centralized injection simplifies authorization, while post-login device-token registration keeps notifications reaching the right user.

04 / ARCHITECTURE & STACK

Engineered for relational integrity.

The Flutter client separates data sources, domain, and presentation; Dio handles transport with an automatically injected Bearer token. A single FillDataProvider accumulates all wizard input into one object before it is sent as one auction payload, preventing half-sent orders. On the server, shipment status is the source of truth; the select-winner, payments, and tracking endpoints enforce per-owner authorization. The status transition history (status_history) becomes an audit trail that is rendered back to the user timeline.

ARCHITECTURE TIERS
01|

CLIENT LAYER

  • Flutter (Dart) multi-platform
  • Provider & Riverpod for state
  • flutter_map + geolocator + latlong2
  • flutter_secure_storage & shared_preferences
02|

BUSINESS & DOMAIN

  • Per-feature data-domain-presentation architecture
  • Data source layer + Dio HTTP client
  • Shipment status to flow-step mapping
  • Role-based navigation (customer/vendor) + l10n en/id
03|

DATA & STORAGE

  • REST API with Bearer authentication
  • Secure storage for the access token
  • Local progress store as cache
  • Object storage for transfer proofs + status_history as audit trail
CORE PRODUCTION STACK
FlutterDartDioProviderRiverpodFirebase Cloud Messagingflutter_secure_storageflutter_map
OPERATIONAL OUTCOME & RESULT

ECARRGO turns shipping that was negotiated manually into a structured auction flow with a single status authority on the server. Every order now has a linked shipment and auction, a selected winner that cannot be duplicated, and payments that can be verified through a traceable transfer proof. Mapping status to flow steps makes the process deterministic and resumable across sessions, while the status audit trail gives operations a clear basis and room to grow shipping volume.

✓UI steps are recomputed from server status and the local progress cache is reset when unconfirmed, avoiding users stuck on a stale step after a status change.
✓Layered per-step validation (maximum weight 100 tons and auction duration not exceeding the shipping time) rejects inconsistent auction payloads before they are sent.
✓Winner selection through an owner-only endpoint with a deterministic transaction_id derived from the payment proof file name, traceable back to the record.
✓Status transitions are recorded as status_history on the waybill tracking and rendered back into a user-side timeline.
NEXT WORK

More systems worth exploring.

Explore another project where software, business processes, and practical engineering meet.

BACK TO WORKS