Skip to main content

Overview

Trackmart manages the complete delivery lifecycle from order placement through completion. The system tracks orders across multiple states and provides real-time updates to both buyers and drivers.

Delivery States

Orders move through three primary states:
1

Requested

Order has been submitted but not yet accepted by the driver
2

In Transit

Driver has accepted and is actively delivering the order
3

Delivered

Order has been completed and delivered to the buyer
Each state is stored separately in Firebase Realtime Database under the buyer’s and driver’s nodes for efficient querying.

Order Database Structure

Orders are stored in multiple Firebase locations:
// Driver's pending requests
databaseReference
    .child('Drivers')
    .child(order.driverId)
    .child('requests')

// Buyer's pending requests  
databaseReference
    .child('buyers')
    .child(currentUserId)
    .child('requests')

// Active deliveries in transit
databaseReference
    .child('buyers')
    .child(currentUserId)
    .child('transit')
Storing orders under both buyer and driver nodes allows:
  • Fast Queries: Each user can query only their relevant orders
  • Real-time Updates: Changes sync automatically to all parties
  • Offline Support: Firebase caching enables offline access
  • Security: Rules can be set per user type

Driver Assignment

Drivers can be assigned through multiple methods:

Order History

The Orders tab displays all orders grouped by status:

Requested Orders

StreamBuilder(
  stream: databaseReference
      .child('buyers')
      .child(currentUserId)
      .child('requests')
      .onValue,
  builder: (context, snap) {
    if (snap.hasData &&
        !snap.hasError &&
        snap.data.snapshot.value != null) {
      List<HistoryItem> items = [];
      Map<String, dynamic> map =
          snap.data.snapshot.value.cast<String, dynamic>();
      map.forEach((key, values) {
        if (values != null) {
          items.add(Order(
            type: Order.REQUESTED,
            key: key,
            userId: values['userId'],
            userName: values['userName'],
            driverId: values['driverId'],
            driverName: values['driverName'],
            driverPhone: values['driverPhone'],
            quantity: values['quantity'].toDouble(),
            payment: values['payment'],
            price: values['price'].toDouble(),
            unit: values['unit'],
            timestamp: values['timestamp'],
          ).toHistoryItem());
        }
      });
      return Column(children: items);
    }
  },
)

In Transit Orders

_buildInTransit() {
  return StreamBuilder(
    stream: databaseReference
        .child('buyers')
        .child(currentUserId)
        .child('transit')
        .onValue,
    builder: (context, snap) {
      if (snap.hasData &&
          !snap.hasError &&
          snap.data.snapshot.value != null) {
        List<HistoryItem> items = [];
        Map<String, dynamic> map =
            snap.data.snapshot.value.cast<String, dynamic>();
        map.forEach((key, values) {
          if (values != null) {
            items.add(Order(
              getHistory: _getHistory,
              key: key,
              type: Order.TRANSIT,
              userId: values['userId'],
              userName: values['userName'],
              driverId: values['driverId'],
              driverName: values['driverName'],
              driverPhone: values['driverPhone'],
              quantity: values['quantity'].toDouble(),
              payment: values['payment'],
              price: values['price'].toDouble(),
              unit: values['unit'],
              timestamp: values['timestamp'],
            ).toHistoryItem());
          }
        });
        return Column(children: items);
      }
    },
  );
}

Collapsible Sections

Each status group can be expanded or collapsed for cleaner UI

Real-time Updates

Orders automatically appear when status changes

Timestamp Sorting

Orders displayed with newest first

Order Information Display

Each order card shows:
  • Driver Name: Who is handling the delivery
  • Order Date: When the order was placed
  • Quantity & Unit: Amount and measurement type
  • Payment Method: Mobile money or cash
  • Total Price: Calculated from quantity × unit price
  • Status Actions: Context-specific buttons (track, cancel, etc.)
Orders can be searched and filtered in the Orders tab using the search icon in the app bar.

Order Workflow Actions

Depending on order status, different actions are available:
Requested Orders:
  • Cancel request
  • View driver profile
  • Modify order (if not yet accepted)
In Transit Orders:
  • Track on map
  • Chat with driver
  • View ETA
  • Call driver
Delivered Orders:
  • Rate driver
  • View receipt
  • Reorder
  • Report issue

Status Transition Logic

While the buyer app focuses on viewing orders, status transitions occur based on driver actions:
1

Driver Accepts

Order moves from requests to transit node
2

Driver En Route

Location updates stream to buyer’s tracking view
3

Driver Completes

Order moves to history or delivered node
4

Buyer Confirms

Final confirmation and rating submission

Delivery Notifications

The app supports various notification triggers:
  • Order accepted by driver
  • Driver is approaching (geofence)
  • Delivery completed
  • Payment confirmation
  • Driver messages
Ensure Firebase Cloud Messaging is properly configured to receive push notifications for order updates.

Location Requirements

For successful delivery:
var geolocator = Geolocator();
await geolocator
    .getCurrentPosition(desiredAccuracy: LocationAccuracy.best)
    .then((value) {
  Position position = value;
  _updateLocation(position);
  var locationOptions =
      LocationOptions(accuracy: LocationAccuracy.high, distanceFilter: 10);
  geolocator.getPositionStream(locationOptions).listen((Position position) {
    _updateLocation(position);
  });
});
The app requires location permissions to:
  • Set delivery destination
  • Show buyer location to driver
  • Calculate accurate ETAs
  • Update positions during delivery

Delivery Confirmation

Before submitting an order, buyers see a confirmation dialog:
AlertDialog(
  contentPadding: const EdgeInsets.all(16.0),
  title: Text(
    'Confirm',
    style: TextStyle(color: Theme.of(context).accentColor),
  ),
  content: Text(
    'Request delivery of ${_moneyController.text} $_unit${double.parse(_moneyController.text) > 1 ? 's' : ''} of $_product from ${filteredDrivers[_selected].name} for ${_moneyController2.text}',
  ),
  actions: <Widget>[
    new FlatButton(
        child: const Text('Cancel'),
        onPressed: () {
          deselect();
          Navigator.of(context).pop();
        }),
    new FlatButton(
      child: const Text('Request'),
      onPressed: () {
        _requestDelivery(order, context)
            .then((v) {
          Navigator.of(context).pop();
          deselect();
          _tabController.animateTo(2);  // Switch to Orders tab
        });
      },
    ),
  ],
)

Error Handling

The delivery system handles various error scenarios:
if (map == null) {
  setState(() {
    nodrivers=true;
  });
  showDialog(context: context,
      builder: (context){
        return AlertDialog(
          title: Text('No drivers!'),
        );
      }
  );
}

Order Search and Filtering

Buyers can search through order history:
if (_searchText.isNotEmpty) {
  List<HistoryItem> tempList = new List<HistoryItem>();
  for (int i = 0; i < _history.length; i++) {
    if (_history[i]
            .driver
            .toLowerCase()
            .contains(_searchText.toLowerCase()) ||
        _history[i]
            .date
            .toLowerCase()
            .contains(_searchText.toLowerCase())) {
      tempList.add(_history[i]);
    }
  }
  _filteredHistory = tempList;
}

Driver Ratings

After a delivery is completed, buyers can rate their driver experience. This helps maintain service quality and helps other buyers make informed decisions.

Rating Process

When an order is delivered, buyers can:
  1. Open the rating dialog from the completed order
  2. Select a star rating from 1-5 stars using the smooth_star_rating widget
  3. Submit the rating which updates the driver’s total rating in Firebase
lib/home_page.dart:2025-2050
Text('Give ${widget.driver} a rating'),
// Star rating widget allows selection
if (MyDialogContent.rating > 0) {
  // Update driver's total rating in database
  database
      .child('Drivers')
      .child(widget.driverId)
      .update({
    'totalrating': (postSnapshot.data['totalrating'] ?? 0) +
        MyDialogContent.rating
  });
}

Viewing Driver Ratings

Driver ratings are displayed in the driver selection interface:
  • Average rating is calculated as totalrating / totalnumber
  • Display format: “Stars: X.XX/5”
  • Ratings help buyers choose reliable drivers
Driver ratings are stored in Firebase Realtime Database under each driver’s profile and updated in real-time after each completed delivery.

Next Steps

Track Live Deliveries

Monitor active orders on the map

Chat with Drivers

Communicate about delivery details

Build docs developers (and LLMs) love