How to build a functional food delivery tracker app with Appwrite relationship database and Flutter

Femi-ige Muyiwa - Jun 14 '23 - - Dev Community

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.

Create project

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

Create database

Create collection

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

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

permission2

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

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.

clone

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

flutter pub get
Enter fullscreen mode Exit fullscreen mode

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

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
Enter fullscreen mode Exit fullscreen mode

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.

ios

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

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>";
}
Enter fullscreen mode Exit fullscreen mode

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

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:

results
results

results

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

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .