Create a real-time parcel tracking system in Flutter
Tags: realtime, appwrite, flutter, dart
Published: 2022-07-05 06:57:36

---
title: Create a real-time parcel tracking system in Flutter
published: true
description: Learn how to create a real-time parcel tracking system using Appwrite and Flutter
tags: #realtime #appwrite #flutter #dart
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j9bk2pvtst1q6tytwvx9.jpg
# Use a ratio of 100:42 for best results.
---

Industries such as gaming, financial services, marketing, internet of things, and supply chain have revolutionized their products' digital experience by leveraging real-time technologies to deliver pieces of information to their users as quickly as it happens. These real-time experiences are an integral part of our favourite applications when processing messages, payments, and calls.

In this post, we will learn how to create a real-time parcel tracking system using Appwrite and Flutter.

[Appwrite](https://appwrite.io/?utm_source=hackmamba&utm_medium=hackmamba) is a development platform that provides a powerful API and management console for building backend servers for web and mobile applications.

The GitHub repository can be found [here](https://github.com/Mr-Malomz/parcel_tracker).

## Prerequisites

To fully grasp the concepts presented in this tutorial, the following requirements apply:

- Basic understanding of Dart and Flutter
- Flutter [SDK](https://flutter.dev/docs/get-started/install) installed
- [Xcode](https://developer.apple.com/xcode/) (with developer account for Mac users)
- Either [IOS Simulator](https://developer.apple.com/documentation/xcode/running-your-app-in-the-simulator-or-on-a-device), [Android Studio](https://developer.android.com/studio), or [Chrome web browser](https://support.google.com/chrome/answer/95346?hl=en-GB&co=GENIE.Platform%3DDesktop) to run our application
- Docker [installation](https://www.docker.com/get-started/)
- An Appwrite instance; check out this [article](https://dev.to/hackmamba/create-a-local-appwrite-instance-in-3-steps-19n9?utm_source=hackmamba&utm_medium=hackmamba) on how to set up an instance locally or one-click install on [DigitalOcean](https://marketplace.digitalocean.com/apps/appwrite)

## Getting started

We need to create a new Flutter project by navigating to the desired directory and running the command below in our terminal.
```bash
    flutter create parcel_tracker && cd parcel_tracker
```
The command creates a Flutter project  called `parcel_tracker` and navigates into the project directory.

We install the required dependency by navigating to the root directory and opening the `pubspec.yaml` file, and then add the Appwrite’s SDK to the `dependency` section.

```bash
    appwrite: ^4.0.2
```
![Install dependencies](https://paper-attachments.dropbox.com/s_8EFF1B60DFE36B93FEF6E1F360D9B02058A7F0176A6FBB3402F95D5C617D66C1_1652730717898_Screenshot+2022-05-16+at+20.50.16.png)


***P******.******S******.******:*** *An editor like Visual Studio Code automatically installs the dependencies for us when we save the file. We might need to stop our project and run* `flutter pub get` *to install the dependency manually for other editors.*

## Creating a new Appwrite project

To create a new project, we need to startup our Appwrite instance on our machine and navigate to the specified hostname and port `http://localhost:80`. Next, we need to log in to our account or create an account if we don’t have one. 


![Appwrite running](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650158621491_screencapture-localhost-auth-signup-2022-04-17-02_22_56.png)


We can learn more on how to set up Appwrite [here](https://appwrite.io/docs/installation?utm_source=hackmamba&utm_medium=hackmamba).

On the console, click on the **Create Project** button, input `flutter_appwrite` as the name, and click **Create**.

![Create project](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650159125946_Screenshot+2022-04-17+at+02.28.39.png)
![Enter project name](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650159125957_Screenshot+2022-04-17+at+02.30.52.png)



Next, we need to create a database to save our notes. Navigate to the **Database** tab, click on **Add Collection**, input `flutter_appwrite_col` as the collection name, and then click on **Create**.

![Navigate to Database Tab](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650179872117_Screenshot+2022-04-17+at+02.34.59.png)
![Click on create project](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650179872134_Screenshot+2022-04-17+at+08.09.34.png)
![Enter project name & create](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650179872126_Screenshot+2022-04-17+at+08.15.42.png)


Appwrite has an advanced yet flexible way to manage access to users, teams, or roles to access specific resources. We will modify the permission `role:all` to enable access from any application. Then click on **Update** to save changes.

![modify permission](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650180737706_Screenshot+2022-04-17+at+08.31.11.png)

## Add attributes

Attributes are fields that our database will possess. Navigate to the Attributes tab, click on **Add Attributes**, add a `New URL Attribute` for  **parcel_img** and a `New Enum Attribute`  for **status** fields, respectively, **mark as required**, and then click on **Create**.

![select attribute and add fields](https://paper-attachments.dropbox.com/s_8EFF1B60DFE36B93FEF6E1F360D9B02058A7F0176A6FBB3402F95D5C617D66C1_1652732556196_Screenshot+2022-05-16+at+21.22.16.png)
![add field](https://paper-attachments.dropbox.com/s_8EFF1B60DFE36B93FEF6E1F360D9B02058A7F0176A6FBB3402F95D5C617D66C1_1652733403523_Screenshot+2022-05-16+at+21.30.33.png)


For the **status** field, we need to add `packed`, `shipped`, `in-transit`, and `delivered` as elements to simulate the parcel tracking progress.  

![input status and click on add element to add it](https://paper-attachments.dropbox.com/s_8EFF1B60DFE36B93FEF6E1F360D9B02058A7F0176A6FBB3402F95D5C617D66C1_1652733715457_Screenshot+2022-05-16+at+21.39.12.png)



## Add sample data

Next, we can add sample data by navigating to the **Documents** tab, clicking on **Add Document**, inputting the required fields, and clicking on **Create**.

| **parcel_img**                                                           | status |
| ------------------------------------------------------------------------ | ------ |
| https://res.cloudinary.com/dtgbzmpca/image/upload/v1652648108/parcel.png | packed |



![Add document](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650182250008_Screenshot+2022-04-17+at+08.55.08.png)
![Input fields and create](https://paper-attachments.dropbox.com/s_8EFF1B60DFE36B93FEF6E1F360D9B02058A7F0176A6FBB3402F95D5C617D66C1_1652734473683_Screenshot+2022-05-16+at+21.54.06.png)



## Connecting Appwrite to Flutter

To add support for our Flutter app, navigate to the **Home** menu, click on the **Add Platform** button, and select **New Flutter App**.

![Add Platform](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650183238195_Screenshot+2022-04-17+at+09.11.20.png)
![Select New Flutter App](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650183238206_Screenshot+2022-04-17+at+09.12.40.png)


Depending on the device on which we are running our Flutter application, we can modify it as shown below.

**i****OS**
To obtain our Bundle ID, we can navigate using the path below, open the `project.pbxproj` file, and search for `PRODUCT_BUNDLE_IDENTIFIER`.
**ios > Runner.xcodeproj > project.pbxproj**

![iOS](https://paper-attachments.dropbox.com/s_8EFF1B60DFE36B93FEF6E1F360D9B02058A7F0176A6FBB3402F95D5C617D66C1_1652741900395_Screenshot+2022-05-16+at+23.57.24.png)


Next, open the project directory on [Xcode](https://developer.apple.com/xcode/), open the `Runner.xcworkspace` folder in the app's iOS folder, select the Runner project in the Xcode project navigator, select Runner target in the main menu sidebar, and select **i****OS 11** in the deployment info’s target.

![Change deployment target](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650208061820_Screenshot+2022-04-17+at+16.05.22.png)


**Android**
To get our package name, we can navigate using the path below, open the `AndroidManifest.xml` file, and copy the `package` value.
**android > app > src > debug > AndroidManifest.xml**

![Android](https://paper-attachments.dropbox.com/s_8EFF1B60DFE36B93FEF6E1F360D9B02058A7F0176A6FBB3402F95D5C617D66C1_1652741912183_Screenshot+2022-05-16+at+23.58.09.png)


Next, we need to modify the `AndroidManifext.xml` as shown below:
```xml
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.note_app">
        <uses-permission android:name="android.permission.INTERNET"/>
        <application ...>
        <activity android:name="com.linusu.flutter_web_auth.CallbackActivity" android:exported="true">
          <intent-filter android:label="flutter_web_auth">
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="appwrite-callback-[PROJECT_ID]" />
          </intent-filter>
        </activity>
      </application>
    </manifest>
```

Then, we need to navigate to the `lib` directory and create a `utils.dart` file, and add the snippet below:
```dart
    class AppConstant {
      final String projectId = "REPLACE WITH YOUR PROJECT ID";
      final String endpoint = "REPLACE WITH YOUR ENPOINT";
      final String collectionId = "REPLACE WITH YOUR COLLECTION ID";
    }
```
Navigate to the **settings** menu for the project and database to copy the **Project ID**, **API Endpoint**, and **Collection ID**.

![](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650212096876_Screenshot+2022-04-17+at+17.11.09.png)
![](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650212096890_Screenshot+2022-04-17+at+17.14.26.png)


For the `endpoint` property, we need to modify it to work with our system's local network address. We can adjust accordingly:

**i****OS**
Navigate to the **Network** section, copy the IP address, and modify as shown below:
```dart
    class AppConstant {
      final String projectId = "REPLACE WITH YOUR PROJECT ID";
      final String endpoint = "http://192.168.1.195/v1";
      final String collectionId = "REPLACE WITH YOUR COLLECTION ID";
    }
```
![](https://paper-attachments.dropbox.com/s_9C6F95223612512F2BFCBBAA719BAF62E5E51518F8EC1B3A16905A2D91336244_1650365320233_Screenshot+2022-04-18+at+19.38.08.png)


**Android**
We can connect our Android emulator to the system’s IP using the `10.0.2.2` IP address.
```dart
    class AppConstant {
      final String projectId = "REPLACE WITH YOUR PROJECT ID";
      final String endpoint = "http://10.0.2.2/v1";
      final String collectionId = "REPLACE WITH YOUR COLLECTION ID";
    }
```

## Building the parcel tracker

To get started, we need to create a model to convert the response sent from Appwrite to a Dart object. The model will also cater to JSON serialization. To do this, add the snippet below in the same `utils.dart` file:
```dart
    class AppConstant {
      //code goes here
    }
    
    class Parcel {
      String? $id;
      String parcel_img;
      String status;
    
      Parcel({this.$id, required this.parcel_img, required this.status});
    
      factory Parcel.fromJson(Map<dynamic, dynamic> json) {
        return Parcel(
            $id: json['\$id'],
            parcel_img: json['parcel_img'],
            status: json['status']);
      }
    
      Map<dynamic, dynamic> toJson() {
        return {'parcel_img': parcel_img, 'status': status};
      }
    }
```
The snippet above does the following:

- Creates a `Parcel` class with required properties
- Adds a constructor with unrequired and required parameters
- Creates a `fromJson` and `toJson` method for JSON serialization

Next, we need to create a `home.dart` file inside the `lib` directory and update it by doing the following:

First, we need to import the required dependencies, create a `Home` view to display the application, and add variables required to perform real-time functionality.
```dart
    import 'package:appwrite/appwrite.dart';
    import 'package:flutter/material.dart';
    import 'package:parcel_tracker/utils.dart';
    
    class Home extends StatefulWidget {
      const Home({Key? key}) : super(key: key);
      @override
      State<Home> createState() => _HomeState();
    }
    
    class _HomeState extends State<Home> {
      List<Parcel>? items;
      bool _isLoading = false;
      bool _isError = false;
      Client client = Client();
      Database? db;
      RealtimeSubscription? realtimeSubscription;
    
      @override
      Widget build(BuildContext context) {
        //widget goes here
      }
    }
```
Secondly, we need to use the `initState` method that configures the `client` and the `db` instances using the `AppConstants` defined earlier. The `initState` also contains a `_loadParcel` and `_subcribe` method. We will create these methods in the next step.
```dart
    //import goes here
    
    class Home extends StatefulWidget {
      const Home({Key? key}) : super(key: key);
      @override
      State<Home> createState() => _HomeState();
    }
    
    class _HomeState extends State<Home> {
      //variables goes here
    
      @override
      void initState() {
        super.initState();
        client
            .setEndpoint(AppConstant().endpoint)
            .setProject(AppConstant().projectId);
        db = Database(client);
        _loadParcel();
        _subscribe();
      }  
    
      @override
      Widget build(BuildContext context) {
        //widget goes here
      }
    }
```
Thirdly, we need to create a `_loadParcel` method to get the list of documents in our Appwrite database using the `listDocuments` function and updating state accordingly.
```dart
    //import goes here
    
    class Home extends StatefulWidget {
      const Home({Key? key}) : super(key: key);
      @override
      State<Home> createState() => _HomeState();
    }
    
    class _HomeState extends State<Home> {
      //variables goes here
    
      @override
      void initState() {
        //initState code goes here
      }  
    
       _loadParcel() async {
        setState(() {
          _isLoading = true;
        });
        try {
          final data =
              await db?.listDocuments(collectionId: AppConstant().collectionId);
          setState(() {
            items = data?.documents
                .map((parcel) => Parcel.fromJson(parcel.data))
                .toList();
            _isLoading = false;
          });
        } catch (e) {
          setState(() {
            _isLoading = false;
            _isError = true;
          });
          throw Exception('Error getting list of parcel');
        }
      }
    
      @override
      Widget build(BuildContext context) {
        //widget goes here
      }
    }
```
Fourthly, we need to create a `_subscribe` method for handling real-time functionality in the application.
```dart
    //import goes here
    
    class Home extends StatefulWidget {
      const Home({Key? key}) : super(key: key);
      @override
      State<Home> createState() => _HomeState();
    }
    
    class _HomeState extends State<Home> {
      //variables goes here
    
      @override
      void initState() {
        //initState code goes here
      }  
    
       _loadParcel() async {
        //code goes here
      }
    
      _subscribe() {
        final realtime = Realtime(client);
        String collectionID = AppConstant().collectionId;
        realtimeSubscription =
            realtime.subscribe(['collections.$collectionID.documents']);
        //listening to stream we can listen to
        realtimeSubscription!.stream.listen((e) {
          if (e.payload.isNotEmpty) {
            if (e.event == 'database.documents.update') {
              items!
                  .map((element) => element.status = e.payload['status'])
                  .toList();
              setState(() {});
            }
          }
        });
      }
    
      @override
      Widget build(BuildContext context) {
        //widget goes here
      }
    }
```
The snippet above does the following:

- Creates a `realtime` and `collectionID` variable that subscribes to the Appwrite event and the collection ID, respectively
- Uses the `realtimeSubscription` variable to subscribe to the document with matching `collectionID`
- Listens to the returned stream to check that it is not empty
- Checks whether the returned event is an **update** action and updates the document based on the payload returned

Lastly, we need to create a `_getStatusColor` helper method to change the status background and update the widgets accordingly.

```dart
//import goes here

class Home extends StatefulWidget {
  const Home({Key? key}) : super(key: key);
  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  //variables goes here

  @override
  void initState() {
    //initState code goes here
  }  

   _loadParcel() async {
    //code goes here
  }

  _subscribe() {
    //code goes here
  }

  _getStatusColor(String status) {
    switch (status.toLowerCase()) {
      case "packed":
        return 0xffAEAEB2;
      case "shipped":
        return 0xffF1CFA0;
      case "in-transit":
        return 0xffD9D9F4;
      case "delivered":
        return 0xff92EAA8;
      default:
        return 0xffAEAEB2;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Parcel Tracker"),
      ),
      body: _isLoading
          ? const Center(child: CircularProgressIndicator())
          : _isError
              ? const Center(
                  child: Text(
                    'Error loading parcels',
                    style: TextStyle(
                      color: Colors.red,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                )
              : Padding(
                  padding: const EdgeInsets.all(30.0),
                  child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Row(
                          children: [
                            const Text(
                              "Order ID:",
                              style: TextStyle(
                                fontWeight: FontWeight.w500,
                              ),
                            ),
                            SizedBox(width: 30.0),
                            Text(
                              items![0].$id!,
                              style: TextStyle(fontWeight: FontWeight.w700),
                            ),
                          ],
                        ),
                        const SizedBox(height: 15.0),
                        Row(
                          children: [
                            const Text(
                              "Status:",
                              style: TextStyle(
                                fontWeight: FontWeight.w500,
                              ),
                            ),
                            const SizedBox(width: 30.0),
                            Container(
                              padding: const EdgeInsets.all(10.0),
                              decoration: BoxDecoration(
                                color: Color(_getStatusColor(items![0].status)),
                                borderRadius:
                                    const BorderRadius.all(Radius.circular(20)),
                              ),
                              child: Text(
                                items![0].status,
                                style: TextStyle(fontWeight: FontWeight.w700),
                              ),
                            ),
                          ],
                        ),
                        const SizedBox(height: 30.0),
                        Image.network(items![0].parcel_img),
                      ]),
                ),
    );
  }
}
```

**Complete  utils.dart code:**
```dart
class AppConstant {
  final String projectId = "YOUR PROJECTID GOES HERE";
  final String endpoint = "http://192.168.1.7/v1";
  final String collectionId = "YOUR COLLECTIONID GOES HERE";
}

class Parcel {
  String? $id;
  String parcel_img;
  String status;

  Parcel({this.$id, required this.parcel_img, required this.status});

  factory Parcel.fromJson(Map<dynamic, dynamic> json) {
    return Parcel(
        $id: json['\$id'],
        parcel_img: json['parcel_img'],
        status: json['status']);
  }

  Map<dynamic, dynamic> toJson() {
    return {'parcel_img': parcel_img, 'status': status};
  }
}
```
**Complete  Home.dart code:**
```dart
import 'package:appwrite/appwrite.dart';
import 'package:flutter/material.dart';
import 'package:parcel_tracker/utils.dart';

class Home extends StatefulWidget {
  const Home({Key? key}) : super(key: key);

  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  List<Parcel>? items;
  bool _isLoading = false;
  bool _isError = false;
  Client client = Client();
  Database? db;
  RealtimeSubscription? realtimeSubscription;

  @override
  void initState() {
    super.initState();
    client
        .setEndpoint(AppConstant().endpoint)
        .setProject(AppConstant().projectId);

    db = Database(client);
    _loadParcel();
    _subscribe();
  }

  _loadParcel() async {
    setState(() {
      _isLoading = true;
    });
    try {
      final data =
          await db?.listDocuments(collectionId: AppConstant().collectionId);
      setState(() {
        items = data?.documents
            .map((parcel) => Parcel.fromJson(parcel.data))
            .toList();
        _isLoading = false;
      });
    } catch (e) {
      setState(() {
        _isLoading = false;
        _isError = true;
      });
      throw Exception('Error getting list of parcel');
    }
  }

  _subscribe() {
    final realtime = Realtime(client);
    String collectionID = AppConstant().collectionId;
    realtimeSubscription =
        realtime.subscribe(['collections.$collectionID.documents']);

    //listening to stream we can listen to
    realtimeSubscription!.stream.listen((e) {
      if (e.payload.isNotEmpty) {
        if (e.event == 'database.documents.update') {
          items!
              .map((element) => element.status = e.payload['status'])
              .toList();
          setState(() {});
        }
      }
    });
  }

  _getStatusColor(String status) {
    switch (status.toLowerCase()) {
      case "packed":
        return 0xffAEAEB2;
      case "shipped":
        return 0xffF1CFA0;
      case "in-transit":
        return 0xffD9D9F4;
      case "delivered":
        return 0xff92EAA8;
      default:
        return 0xffAEAEB2;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Parcel Tracker"),
      ),
      body: _isLoading
          ? const Center(child: CircularProgressIndicator())
          : _isError
              ? const Center(
                  child: Text(
                    'Error loading parcels',
                    style: TextStyle(
                      color: Colors.red,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                )
              : Padding(
                  padding: const EdgeInsets.all(30.0),
                  child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Row(
                          children: [
                            const Text(
                              "Order ID:",
                              style: TextStyle(
                                fontWeight: FontWeight.w500,
                              ),
                            ),
                            SizedBox(width: 30.0),
                            Text(
                              items![0].$id!,
                              style: TextStyle(fontWeight: FontWeight.w700),
                            ),
                          ],
                        ),
                        const SizedBox(height: 15.0),
                        Row(
                          children: [
                            const Text(
                              "Status:",
                              style: TextStyle(
                                fontWeight: FontWeight.w500,
                              ),
                            ),
                            const SizedBox(width: 30.0),
                            Container(
                              padding: const EdgeInsets.all(10.0),
                              decoration: BoxDecoration(
                                color: Color(_getStatusColor(items![0].status)),
                                borderRadius:
                                    const BorderRadius.all(Radius.circular(20)),
                              ),
                              child: Text(
                                items![0].status,
                                style: TextStyle(fontWeight: FontWeight.w700),
                              ),
                            ),
                          ],
                        ),
                        const SizedBox(height: 30.0),
                        Image.network(items![0].parcel_img),
                      ]),
                ),
    );
  }
}
```

Finally, we need to update the `main.dart` file to include the `Home` screen:
```dart
    import 'package:flutter/material.dart';
    import 'package:parcel_tracker/home.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: const Home(),
        );
      }
    }
```

## Testing the application

Updating the document can be performed from any application with access to the project. However, we will be updating the app from the Appwrite console for this tutorial.

We can run the project using the command below:
```bash
    flutter run
```
Upon running the project, we should see our application subscribed to events from Appwrite.

![App subscribed to Appwrite](https://paper-attachments.dropbox.com/s_8EFF1B60DFE36B93FEF6E1F360D9B02058A7F0176A6FBB3402F95D5C617D66C1_1652750419546_Screenshot+2022-05-17+at+00.49.25.png)



![](https://media.giphy.com/media/VPujztpDxQKslvbpKW/giphy.gif)


[https://media.giphy.com/media/VPujztpDxQKslvbpKW/giphy.gif](https://media.giphy.com/media/VPujztpDxQKslvbpKW/giphy.gif)

**P****.****S****.****:** *This demo is a base implementation to demonstrate Appwrite’s real-time capability. Full implementation will require the storage of multiple parcels and the propagation of events for each.*


# Conclusion

This post discussed how to create a real-time app using Flutter and Appwrite. The Appwrite platform ships with a robust SDK for building real-time applications. 

These resources might be helpful:

- [Appwrite official documentation](https://appwrite.io/docs?utm_source=hackmamba&utm_medium=hackmamba)
- [Appwrite Flutter SDK](https://appwrite.io/docs/getting-started-for-flutter?utm_source=hackmamba&utm_medium=hackmamba)
- [Appwrite Real-Time technology](https://appwrite.io/docs/realtime?utm_source=hackmamba&utm_medium=hackmamba)
- [Flutter null safety](https://dart.dev/null-safety)