How to build a functional food delivery tracker app with Appwrite relationship database and Flutter
Tags: flutter, webdev, appwrite
Published: 2023-06-14 14:12:25

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](https://appwrite.io/docs/databases-relationships?utm_source=hackmamba&utm_medium=hackmamba-blog) 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](https://developer.apple.com/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](https://marketplace.digitalocean.com/apps/appwrite), or [Gitpod](https://gitpod.io/#https://github.com/appwrite/integration-for-gitpod). Check out this [article](https://dev.to/hackmamba/create-a-local-appwrite-instance-in-3-steps-19n9) 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](https://dev.to/hackmamba/create-a-local-appwrite-instance-in-3-steps-19n9) or [DigitalOcean droplet.](https://marketplace.digitalocean.com/apps/appwrite) Once you have this, create a project and fill in the **project's name and ID.**


![Create project](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pr9v8xewexmaqw03h1up.png)

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**. 


![database](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fttywkdxfgfbhgqp0b7m.png)


![Create database](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/of0ldpasyw0fkwnoawaj.png)


![Create collection](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8zpvx3cp8njq5e6fop92.png)

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.


![create attribute](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/69nwbgf89lnl8jtlmpfu.png)

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.


![permission](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z8xol37nt5jm9qzfb1my.png)

![permission2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d1zi7yzi49khbh3gf2al.png)

**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] |

![adding data](https://paper-attachments.dropboxusercontent.com/s_C9A2C2CC022CFC6BA6F8E7F24A7C0256EB4D70A41022499A4DD2A7808912A3C2_1683235918856_Screenshot+303.png)



## Cloning the UI template

This section uses a UI template containing user registration and login code. Let’s clone the [repository](https://github.com/muyiwexy/delivery_tracker) specified in the prerequisites. Check out the official GitHub [docs](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) to learn more about cloning a repository.


![clone](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/befvm2clcodpofxhnrm5.PNG)

After cloning the UI to your PC, open it in your preferred code editor and run the command below:

``` javascript
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:


![clone result](https://paper-attachments.dropboxusercontent.com/s_C9A2C2CC022CFC6BA6F8E7F24A7C0256EB4D70A41022499A4DD2A7808912A3C2_1683062163209_Screenshot+300.png)


The `lib` directory tree should look like this:

```javascript
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](https://developer.apple.com/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.


![ios](https://paper-attachments.dropboxusercontent.com/s_973FE6248EEDD7529E7A11435E297ADACD4E1B7483EB53A8303EEED9486C6A4E_1677590542497_file.jpeg)


**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`).

{% embed https://gist.github.com/muyiwexy/f1a0f6c59329d25d37c353f34a4fa9aa %}

> 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 choose `Flutter` 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.

```javascript
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**
{% embed https://gist.github.com/muyiwexy/5b66461cbc57a4d6527d332454c493e0 %}


**vendor_model.dart**
{% embed https://gist.github.com/muyiwexy/66f6aaaa5fd2bf1ed9d1735f5568fa34 %}

**order_model.dart**
{% embed  https://gist.github.com/muyiwexy/cb3e8bfebeda9360ff15381fe4744ce7
%}

**app_provider.dart**
{% embed  https://gist.github.com/muyiwexy/e61b6c97151b8718081d5c000e0586cb
%} 

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.

{% embed  https://gist.github.com/muyiwexy/14a391bbd7833e55d19a11f7edc2af3c
%}


- `createOrderDocument` method creates a new order document using the `createDocument` 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**
{% embed  https://gist.github.com/muyiwexy/39c881cc31aab3151aa634fb29c0edcf
%}


**view_one_modal.dart**
{% embed  https://gist.github.com/muyiwexy/c42e0d21388bc9d41ec661e92e766a75
%}


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**

{% embed  https://gist.github.com/muyiwexy/36c5bb8120a64b7d41a6bffd07e1e96a
%}

{% embed  https://gist.github.com/muyiwexy/0913080b81fe816aa9cdb560826cc090
%}


When you run the application, your result should look like the gifs below:

![results](https://paper-attachments.dropboxusercontent.com/s_C9A2C2CC022CFC6BA6F8E7F24A7C0256EB4D70A41022499A4DD2A7808912A3C2_1683060682134_ezgif.com-video-to-gif+6.gif)
![results](https://paper-attachments.dropboxusercontent.com/s_C9A2C2CC022CFC6BA6F8E7F24A7C0256EB4D70A41022499A4DD2A7808912A3C2_1683302821675_ezgif.com-video-to-gif+8.gif)

![results](https://paper-attachments.dropboxusercontent.com/s_C9A2C2CC022CFC6BA6F8E7F24A7C0256EB4D70A41022499A4DD2A7808912A3C2_1683303338022_ezgif.com-video-to-gif+9.gif)

## 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.

## Resources
- [Appwrite](https://appwrite.io/?utm_source=hackmamba&utm_medium=hackmamba-blog)
- [Appwrite Databases API](https://appwrite.io/docs/client/databases/?utm_source=hackmamba&utm_medium=hackmamba-blog)
- [Appwrite Relationship Databases](https://appwrite.io/docs/databases-relationships?utm_source=hackmamba&utm_medium=hackmamba-blog)
- [Create a real-time parcel tracking system in Flutter](https://dev.to/hackmamba/create-a-real-time-parcel-tracking-system-in-flutter-3li3)