Overview
Trackmart is built with Flutter and Firebase, providing a foundation for ordering, tracking, and delivering goods. This guide will help you extend the app with new features.
App Architecture
Core Structure
Entry Point main.dart - App initialization and theme setup
Authentication root_page.dart and login_page.dart - User authentication flow
Home Interface home_page.dart - Main tabbed interface with Chats, Request, and Orders
Feature Pages map.dart, chat.dart, settings.dart, about.dart, contact.dart, support.dart
Firebase Integration
Trackmart uses multiple Firebase services:
Firebase Realtime Database - Real-time data synchronization for orders and drivers
Cloud Firestore - Document-based storage for driver profiles
Firebase Authentication - User authentication
Firebase Storage - Image and file storage
Firebase Messaging - Push notifications
Adding a New Page
Create the Page File
Create a new Dart file in lib/ directory: touch lib/my_new_page.dart
Define the Page Widget
Create a StatelessWidget or StatefulWidget: import 'package:flutter/material.dart' ;
class MyNewPage extends StatelessWidget {
@override
Widget build ( BuildContext context) {
return Scaffold (
appBar : AppBar (
title : Text ( 'My New Feature' ),
),
body : Center (
child : Text ( 'Your content here' ),
),
);
}
}
Apply App Theme
Use the app’s theme for consistency: return MaterialApp (
theme : new ThemeData (
primaryColor : const Color ( 0xff004d40 ),
primaryColorDark : const Color ( 0xff003e33 ),
accentColor : const Color ( 0xff005B9A ),
),
home : Scaffold (
// Your page content
),
);
Navigate to the Page
Add navigation from an existing page: Navigator . push (
context,
MaterialPageRoute (builder : (context) => MyNewPage ()),
);
Example: Adding to Navigation Drawer
The app uses a drawer menu in home_page.dart:325-442. To add your page to the drawer:
ListTile (
title : Text ( 'My New Feature' ),
trailing : Icon (
Icons .new_releases,
color : Theme . of (context).accentColor,
),
onTap : () {
Navigator . push (
context,
MaterialPageRoute (builder : (context) => MyNewPage ()),
);
},
),
Add this ListTile within the ListView children in the Drawer widget around line 377-441.
Integrating Firebase Features
Setting Up Firebase References
In home_page.dart:182-188, the app initializes Firebase:
Future < bool > _startup () async {
database = FirebaseDatabase .instance;
database. setPersistenceEnabled ( true );
database. setPersistenceCacheSizeBytes ( 10000000 );
databaseReference = database. reference ();
await cf. Firestore .instance. settings (persistenceEnabled : true );
firestore = cf. Firestore .instance;
// ...
}
Using Realtime Database
Reading Data
Writing Data
StreamBuilder Pattern
databaseReference
. child ( 'buyers' )
. child (currentUserId)
. child ( 'orders' )
.onValue
. listen ((event) {
var snapshot = event.snapshot;
// Process data
});
await databaseReference
. child ( 'buyers' )
. child (currentUserId)
. update ({
'fieldName' : value,
});
StreamBuilder (
stream : databaseReference
. child ( 'buyers' )
. child (currentUserId)
. child ( 'requests' )
.onValue,
builder : (context, snap) {
if ( ! snap.hasData || snap.hasError) {
return CircularProgressIndicator ();
}
// Build UI with data
return ListView ( /* ... */ );
},
)
See home_page.dart:1204-1298 for a complete example of using StreamBuilder with Realtime Database.
Using Cloud Firestore
Reading Collection
Writing Document
StreamBuilder (
stream : firestore. collection ( 'drivers' ). snapshots (),
builder : (context, snapshot) {
if ( ! snapshot.hasData) {
return CircularProgressIndicator ();
}
return ListView . builder (
itemCount : snapshot.data.documents.length,
itemBuilder : (context, index) {
var document = snapshot.data.documents[index];
return ListTile (
title : Text (document[ 'displayName' ]),
);
},
);
},
)
await firestore
. collection ( 'myCollection' )
. document ( 'documentId' )
. setData ({
'field1' : 'value1' ,
'field2' : 'value2' ,
});
See home_page.dart:989-1007 for Firestore collection streaming example.
Adding a New Screen with Tabs
The home page uses a TabController pattern (see home_page.dart:253):
_tabController = TabController (
vsync : this ,
length : 3 ,
initialIndex : 1
);
Creating a Tabbed Interface
Initialize TabController
class _MyPageState extends State < MyPage > with SingleTickerProviderStateMixin {
TabController _tabController;
@override
void initState () {
super . initState ();
_tabController = TabController (
vsync : this ,
length : 2 , // Number of tabs
initialIndex : 0 ,
);
}
}
Add TabBar to AppBar
AppBar (
title : Text ( 'My Tabbed Page' ),
bottom : TabBar (
controller : _tabController,
tabs : [
Tab (text : 'Tab 1' ),
Tab (text : 'Tab 2' ),
],
),
)
Create TabBarView
body : TabBarView (
controller : _tabController,
children : [
// Tab 1 content
Center (child : Text ( 'Tab 1' )),
// Tab 2 content
Center (child : Text ( 'Tab 2' )),
],
)
Dispose Controller
@override
void dispose () {
_tabController. dispose ();
super . dispose ();
}
Working with Location Services
Trackmart uses the geolocator package for location tracking. See home_page.dart:193-204:
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 location is automatically updated in Firebase when it changes (see home_page.dart:169-179).
Adding Dependencies
Update pubspec.yaml
Add your dependency to pubspec.yaml: dependencies :
your_package : ^1.0.0
Import in Your Code
import 'package:your_package/your_package.dart' ;
State Management Patterns
The app primarily uses setState for state management:
void _updateValue () {
if (mounted) {
setState (() {
// Update your state variables
myValue = newValue;
});
}
}
Always check mounted before calling setState to avoid errors when the widget is no longer in the tree.
SharedPreferences for Persistence
User data is cached using SharedPreferences (see home_page.dart:189-192):
prefs = await SharedPreferences . getInstance ();
currentUserName = prefs. getString ( 'displayName' );
currentUserPhone = prefs. getString ( 'phoneNo' );
currentUserPhoto = prefs. getString ( 'photoUrl' );
Common UI Patterns
Loading States
_stillLoading
? Center (child : CircularProgressIndicator ())
: YourContentWidget ()
Modal Dialogs
showDialog < void >(
context : context,
builder : ( BuildContext context) {
return AlertDialog (
title : Text (
'Dialog Title' ,
style : TextStyle (color : Theme . of (context).accentColor),
),
content : Text ( 'Dialog content' ),
actions : < Widget > [
FlatButton (
child : Text ( 'OK' ),
onPressed : () {
Navigator . of (context). pop ();
},
),
],
);
},
);
See home_page.dart:876-928 for a complete confirmation dialog example.
Form Input with TextEditingController
final TextEditingController _controller = TextEditingController ();
TextField (
controller : _controller,
decoration : InputDecoration (
labelText : 'Enter value' ,
),
onChanged : (value) {
// Handle changes
},
)
Best Practices
Hot Reload : Use Flutter’s hot reload during development to see changes instantly without losing app state.
Error Handling : Always wrap Firebase calls in try-catch blocks or use .catchError() to handle errors gracefully.
Theme Consistency : Use Theme.of(context) to access theme colors for consistent UI across all features.
Firebase Security Rules : Update your Firebase security rules when adding new data structures to ensure proper access control.