Food delivery is one of the most prominent businesses in the digital age. Companies like Uber Eats, Jumia, Mano, etc. have applications to track customers’ orders, and in turn, the customer can view the delivery process of their products. These features can be enabled by establishing a Relationship in databases, thus creating relationships between order and delivery.
With Appwrite, you can now use such features in your project. Thus, this article will demonstrate how to create a functional delivery tracker application using the Appwrite relationship database and Flutter.
What we’ll cover:
A food delivery tracker application should offer the following features:
- Create user’s order
- View the user’s order
- Confirm the order’s delivery
- Cancel the user’s order and delivery.
Prerequisites
This tutorial requires the reader to meet the following requirements:
- Xcode (with developer account for Mac users).
- iOS Simulator, Android Studio, or Chrome web browser to run the application.
- An Appwrite instance running on either Docker, DigitalOcean droplet, or Gitpod. Check out this article for the setup.
Setting up the Appwrite project
Appwrite’s relationship database feature is currently only available locally; thus, you must have an Appwrite local instance running on Docker or DigitalOcean droplet. Once you have this, create a project and fill in the project's name and ID.
You will need to create multiple database collection pairs during this project. The first pair will be between vendors → foodtype, and the second pair order → delivery.
Head to Databases, select Create Database, and fill in the database name and ID. After, create a new collection by selecting Create collection and filling in the subsequent name and ID.
Next, you need to add some attributes as they will serve as parameters to hold data within your collection. To add an attribute to a particular collection, head to any of your newly created collections, select Create attribute and select any of your preferred attribute types from the available options.
Therefore, add the attributes below to the vendors collection:
key | Type |
---|---|
name | String |
foodTypes | Relationship with foodTypes |
The foodtype collection will have the following attributes:
key | type |
---|---|
foodtype | String |
food | String [] |
foodChecked | Boolean [] |
The relationship attribute between the vendors → foodtype has the following settings:
- One-way relationship
- Related Collection → vendor
- Attribute Key → vendors
- Attribute Key (related collection) → foodTypes
- Relation → many to many
- On deleting a document → set Null
For the second pair, the order collection has the following attributes:
key | Type | Default value |
---|---|---|
vendorName | String | - |
orderStatus | Enum(in-transit, payment-acepted, packed, delivered, canceled, not-delivered) | payment-acepted |
orderTime | String | - |
foodItems | String [] | - |
delivery | Relationship with delivery | - |
The delivery collection has the following attributes:
key | Type | Default value |
---|---|---|
OrderID | String | - |
deliveryStatus | Enum (in-progress, completed, failed) | in-progress |
deliveryTime | String | - |
order | Relationship with order | - |
The relationship attribute between the order → delivery has the following settings:
- Two-way relationship
- Related Collection → delivery
- Attribute Key → delivery
- Attribute Key (related collection) → order
- Relation → one to one
- On deleting a document → cascading
Finally, head to the collection settings section and set the collection level permission to role:any
and check the read
CRUD permissions vendors → foodtype pair. For the order → delivery
pair, set the collection level permission to role:user
and check all the CRUD permissions
These permissions allow anyone to create, read, update, and delete documents from the collection.
Adding data to the Vendor and Foodtype collection
Next, you can add sample data by heading to the Documents tab and clicking Add Documents, populating the fields available and clicking Create.
Vendor
document | vendorName | foodTypes |
---|---|---|
document 1 | Karic’s | [document 1, document 2] |
document 2 | Mecurial | [document 2] |
Foodtype
document | foodtype | food | foodChecked |
---|---|---|---|
document 1 | African | [“Eba”, “Semo”] | [false, false] |
document 2 | Rice-dish | [“Jollof”, “Fried rice”] | [false, false] |
Cloning the UI template
This section uses a UI template containing user registration and login code. Let’s clone the repository specified in the prerequisites. Check out the official GitHub docs to learn more about cloning a repository.
After cloning the UI to your PC, open it in your preferred code editor and run the command below:
flutter pub get
This command obtains all the dependencies listed in the pubspec.yaml
file in the current working directory and their transitive dependencies. Next, run the command flutter run
, and your application should look like the image below:
The lib
directory tree should look like this:
lib/
├─ auth/
│ ├─ app_provider.dart
├─ model/
│ ├─ order_model.dart
│ ├─ user_model.dart
│ ├─ vendor_model.dart
├─ pages/
│ ├─ components/
│ │ ├─ HomeComponent/
│ │ │ ├─ first_column_item.dart
│ │ │ ├─ second_column_item.dart
│ │ ├─ registrationloader.dart
│ ├─ views/
│ │ ├─ modal/
│ │ │ ├─ view_one_modal.dart
│ │ ├─ view_one.dart
│ │ ├─ view_three.dart
│ │ ├─ view_two.dart
│ ├─ home.dart
│ ├─ login.dart
│ ├─ signup.dart
├─ main.dart
Connecting a Flutter project to Appwrite
Here’s how to connect a Flutter project to Appwrite for Android and iOS devices.
iOS
First, obtain the bundle ID
by navigating to the project.pbxproj
file (ios > Runner.xcodeproj > project.pbxproj
) and searching for the PRODUCT_BUNDLE_IDENTIFIER
.
Next, head to the Runner.xcworkspace
folder in the application’s iOS folder in the project directory on Xcode. To select the runner target, choose the Runner project in the Xcode project navigator and find the Runner target
. Next, select General and IOS 11.0
in the deployment info section as the target.
Android
For Android, copy the XML script below and paste it below the activity tag in the Androidmanifest.xml
file (to find this file, head to android > app > src > main
).
Note: change [PROJECT-ID] to the ID you used when creating the Appwrite project.
We will also need to set up a platform within the Appwrite console. Follow the steps below to do so.
- Within the Appwrite console, select
Create Platform
and chooseFlutter
for the platform type. - Specify the operating system: in this case, Android.
- Finally, provide the application and package names (found in the app-level
build.gradle
file).
Explaining the UI and Functionalities
Functionalities
Start by creating a folder called appConstants
in the lib
folder, and within the folder, create the app_constants.dart
file. This file will store some important constants for the project in an Appconstants
class.
class Appconstants {
static const String projectid = "<projectID>";
static const String endpoint = "<cloud endpoint>";
static const String dbID = "<database ID>";
static const String ordercollectionID = "<collection ID>";
static const String ordercollectionID = "<collection ID>";
}
The project uses the Provider package for state management, hence the ChangeNotifier
class in the app_provider.dart
file.
Note: The user registration methods will not be the focal point of this article. You can do well to check out the syntax used in the method for personal learning.
The ChangeNotifier
class will retrieve the list of vendors and orders, create new user orders and update the order and delivery status.
user_model.dart
vendor_model.dart
order_model.dart
app_provider.dart
In the code above, the ChangeNotifier
class consists of several instance variables such as Appwrite’s client
, account
and databases
instance, _vendorItems
, _orderItems
and, _user
model classes and _isLoading
boolean. It uses the AppProvider
class constructor to initialize the _isLoading
variable to true
, set the _user
variable to null
, and call the initialize()
method.
It then sets the endpoint and project ID for the client
instance, creates an Account
object, and sets the databases
instance variable in the initialize()
method. It also calls the checkUserSignIn()
method, which tries to get the user account info using the _getUseraccountInfo()
method. If the checkUserSignIn()
method throws an error, it calls the listVendorDocument()
method to list the vendor document. This is because there is no available user session at that moment.
The _getUseraccountInfo()
tries to check whether a recent user session is available using the account.get()
method. If true, it maps the JSON
to the User
model class. It lists the vendor and order documents using the listVendorDocument()
and listOrderDocument()
methods, respectively, while setting the _isLoading
variable to false
. Finally, it returns the User
object.
The listVendorDocument()
method lists the vendor documents from the Appwrite database and maps the document response to the Vendor
model class. It sets the _vendorItems
instance variable to the mapped vendor documents and the _isLoading
variable to false
. The listOrderDocument()
method also lists the order documents from the Appwrite database and maps the document response to the Order
model class. It sets the _orderItems
instance variable to the mapped order documents.
-
createOrderDocument
method creates a new order document using thecreateDocument
method from Appwrite’s database API and notifies listeners of any changes. -
updateOrderDocument
method updates an order document's delivery status and delivery time. -
cancelorder
method cancels an order document by updating its delivery and order information. -
_subscribe
method subscribes to a real-time update stream of order documents and updates the corresponding order item's order status when an update event occurs.
UI (User interface)
The pages folder consists of the entirety of the user interface. The user interface is a single-page interface consisting of a split view with which, when you click the navigation, it redirects to the view linked to the click event.
The first view (view_one.dart) consists of a row of scrollable containers. These containers will list all the items in the vendor
collection and their corresponding relationship with foodtypes
.
When you click on any of the items, you will get an alertdialog
that shows the items of the foodtype
associated with the particular vendor. This is possible because of the many to many
relation between vendors
and foodtypes
. This relation allows each vendor to have many food types and vice versa.
view_one.dart
view_one_modal.dart
Once an order is created, the order and delivery information are displayed in the order section of the UI in the view_three.dart
file. The Order section of the UI contains a ListTile
widget with an onTap
property that displays a modal showing the order details, a button to cancel an order (by calling the cancelorder
function in the ChangeNotifier
class), and the delivery status.
Once the order is marked as delivered, a prompt appears in the modal asking the client if they have received their package. If the option is yes, the corresponding document in the delivery collection is updated.
view_three.dart
When you run the application, your result should look like the gifs below:
Conclusion
Appwrite’s relationship database has shown extreme versatility by providing the option of selecting different relations, which allows for building different scalable applications while being organized. By using the many to many
relation, this tutorial has shown a relationship between different vendors and different food types. Similarly, by using the one to one
relation, we have shown the relationship between one order and one delivery.