Real-time notifications are instant, automated messages triggered by specific user actions or system events. They can be delivered through in-app alerts, push notifications, websites, and more to keep users informed and engaged with timely and relevant information.

But they’re more than just alerts—they enhance customer experience, help your business stand out, and drive growth.

##Key Considerations When Implementing Real-time Support

To ensure effective real-time support, keep these factors in mind:

  • Choose the right delivery channel: Users have different preferences; some may prefer SMS over in-app notifications.
  • Offer personalization options: Allow users to customize notifications to receive only relevant updates.
  • Ensure instant delivery: Notifications should be delivered without delays to maintain relevance.
  • Follow compliance and privacy laws: Protect user data and adhere to legal requirements when handling personal information.

##Where Can You Use Real-time Support?

Real-time notifications are valuable across various industries, enhancing user experience and improving business operations. Here are some key use cases:

  1. E-commerce & Retail: Keep customers informed with instant updates on order status, shipping progress, and delivery times. Automated alerts on discounts and promotions can also boost conversions and customer retention.
  2. Fintech & Payments: Improve security and build user trust by notifying customers about transfers, bill payments, and fraud prevention tips.
  3. Software-as-a-Service (SaaS): Enhance user experience by sending notifications about scheduled maintenance, downtime, and performance updates.
  4. Healthcare: Improve patient care with reminders for upcoming appointments, prescription refills, and emergency alerts for medical staff.
  5. Ride-Hailing & Logistics: Optimize operations by notifying users about driver arrivals, trip updates, vehicle status, delays, and route changes.

##Add Real-time Support to your Flutter app with Appwrite

Appwrite is a powerful, developer-friendly backend-as-a-service (BaaS) platform that is open-sourced and offers modern backend services like authentication, databases, serverless functions, storage, real-time, and much more.

In this guide, you’ll use Appwrite’s real-time features to add instant notifications to a sample food tracker project.

##Prerequisite

To follow along with this guide, you’ll need:

  • Basic understanding of Dart and Flutter
  • Appwrite cloud account. Sign-up is free.

##Set up a Database on Appwrite

To get started, log into your Appwrite console and create a project. Next, navigate to the Databases menu, click the Create database button, input orderDB as the database name, and click the Create button.

Create database

Once the database is created, click on the Create collection button, input orders as the collection name, and click the Create button.

Create  collection

Next, switch to the Attributes tab and click the Create attribute button to add attributes as shown below.

TypeAttribute KeySizeRequiredElements
Stringname225YES-
Floatprice-YES-
URLimage_url-YES-
EnumstatusYESpacked, shipped, in-transit, delivered

You should have something similar to the image below:

Attributes

Switch to the Setting tab and scroll down to the Permissions section. Update the collection permission by adding the Any role and check create, read, update, and delete so that anyone can read and write.

Update permission

Finally, switch back to the Document tab, click the Create document button, and populate it as shown below.

##Set up Flutter and add Dependencies

To get started, create a new flutter project by running the command below in your terminal.

bash
flutter create food_tracker

Then change directory into the generated project folder and install the required dependencies.

bash
cd food_tracker
flutter pub add appwrite flutter_dotenv

The command above will add Appwrite Flutter SDK and Dotenv package for loading environment variables.

Configure the Environment Variables

Create a .env file in the root of your project and add the snippet below:

bash
APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1
APPWRITE_PROJECT_ID=<REPLACE WITH YOUR PROJECT ID>
APPWRITE_DATABASE_ID=<REPLACE WITH YOUR DATABASE ID>
APPWRITE_COLLECTION_ID=<REPLACE WITH YOUR COLLECTION ID>

You can get the required IDs from your Appwrite console.

Next, update the assets section of the pubspec.yaml file so that your Flutter application can read the environment variables.

bash
assets:
      - .env

Finally, load the .env file in the main.dart file.

dart
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';

Future main() async {
  await dotenv.load(fileName: ".env");
  runApp(const MyApp());
}

// other parts of the main.dart goes here.

Add Platform Support

To securely connect your Flutter application with Appwrite, you need to add it as a supported platform on the console. To do this, navigate to the Overview menu and click the Flutter button.

Add platform

Next, you must modify the Flutter application as detailed below:

To obtain your Bundle ID, navigate to the path below: ios > Runner.xcodeproj > project.pbxproj Open the project.pbxproj file and search for PRODUCT_BUNDLE_IDENTIFIER.

IOS Flutter app

To obtain your package name for Android, navigate to the path below: android > app > src > debug > AndroidManifest.xml Open the AndroidManifest.xml file and copy the package value.

Android Flutter App

Create a Service

With that done, you need to create a service file to separate the application core logic from the UI. To do this, create a utils.dart file inside the lib directory. Then, add the snippet below:

dart
import 'package:appwrite/appwrite.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';

class Order {
  String id;
  String name;
  int price;
  String imageUrl;
  String status;

  Order({
    required this.id,
    required this.name,
    required this.price,
    required this.imageUrl,
    required this.status,
  });

  factory Order.fromJson(Map<dynamic, dynamic> json) {
    return Order(
      id: json['\$id'],
      name: json['name'],
      price: json['price'],
      imageUrl: json['image_url'],
      status: json['status'],
    );
  }
}

class FoodTrackerService {
  static final String _endPoint = dotenv.get("APPWRITE_ENDPOINT");
  static final String _projectId = dotenv.get("APPWRITE_PROJECT_ID");
  static final String _databaseId = dotenv.get("APPWRITE_DATABASE_ID");
  static final String _collectionId = dotenv.get("APPWRITE_COLLECTION_ID");

  final Client client = Client();
  late Databases _database;

  FoodTrackerService() {
    _init();
  }

  //initialize the Appwrite client
  _init() async {
    client.setEndpoint(_endPoint).setProject(_projectId);
    _database = Databases(client);
    //get current session
    Account account = Account(client);
    try {
      await account.get();
    } on AppwriteException catch (e) {
      if (e.code == 401) {
        account
            .createAnonymousSession()
            .then((value) => value)
            .catchError((e) => e);
      }
    }
  }

  Future<List<Order>> getFoodTracker() async {
    List<Order> foodTrackerList = [];
    try {
      final response = await _database.listDocuments(
        databaseId: _databaseId,
        collectionId: _collectionId,
      );
      foodTrackerList = response.documents
          .map<Order>((json) => Order.fromJson(json.data))
          .toList();
    } catch (e) {
      print(e);
    }
    return foodTrackerList;
  }
}

The snippet above does the following:

  • Imports the required dependencies.
  • Creates an Order class to represent the application data and map the corresponding attributes from a JSON.
  • Creates a FoodTrackerService class that initializes Appwrite by creating an anonymous user that can securely connect to the server and adds a getFoodTracker methods that returns the list of available orders in the database.

Consuming the Service

Start by creating a home.dart file to display the current order saved on Appwrite.

dart
import 'package:flutter/material.dart';
import 'package:food_tracker/utils.dart';

class Home extends StatefulWidget {
  const Home({super.key});
  
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  late List<Order> orders;
  bool _isLoading = false;
  bool _isError = false;
  
  
  void initState() {
    getOrders();
    super.initState();
  }

  getOrders() {
    setState(() {
      _isLoading = true;
    });
    FoodTrackerService().getFoodTracker().then((value) {
      setState(() {
        orders = value;
        _isLoading = false;
      });
    }).catchError((e) {
      setState(() {
        _isError = true;
        _isLoading = false;
      });
    });
  }

  
  Widget build(BuildContext context) {
    // UI code goes here
  }
}

The snippet above does the following:

  • Imports the required dependencies.
  • Creates orders, _isLoading, and _isError properties to manage the application state.
  • Creates a getOrders method to get the list of orders from the database using the FoodTrackerService().getFoodTracker service and set states accordingly.

Lastly, use the return data to populate the UI.

dart
//import goes here

class Home extends StatefulWidget {
  const Home({super.key});
  
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  //properties
  
  
  void initState() {
    getOrders();
    super.initState();
  }

  getOrders() {
    //getOrders code ge jeer
  }

  
  Widget build(BuildContext context) {
    return _isLoading
        ? const Center(
            child: CircularProgressIndicator(
            color: Colors.blue,
          ))
        : _isError
            ? const Center(
                child: Text(
                  'Error getting orders',
                  style: TextStyle(
                    color: Colors.red,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              )
            : Scaffold(
                appBar: AppBar(
                  title: const Text('Food Delivery Tracker',
                      style: TextStyle(color: Colors.white)),
                  primary: true,
                  backgroundColor: Colors.deepPurple,
                ),
                body: ListView.builder(
                  itemCount: orders.length,
                  itemBuilder: (context, index) {
                    return Padding(
                      padding: EdgeInsets.all(30.0),
                      child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text("Order Details",
                                style: TextStyle(
                                    fontSize: 20.0,
                                    fontWeight: FontWeight.w600)),
                            const SizedBox(height: 10.0),
                            Container(
                              padding: const EdgeInsets.all(10.0),
                              decoration: BoxDecoration(
                                color: Colors.grey[200],
                                borderRadius:
                                    const BorderRadius.all(Radius.circular(20)),
                              ),
                              child: Column(
                                crossAxisAlignment: CrossAxisAlignment.start,
                                children: [
                                  Container(
                                    padding: const EdgeInsets.all(10.0),
                                    decoration: BoxDecoration(
                                      color: Colors.grey[300],
                                      borderRadius: const BorderRadius.all(
                                          Radius.circular(20)),
                                    ),
                                    child: Row(
                                      mainAxisSize: MainAxisSize.min,
                                      mainAxisAlignment:
                                          MainAxisAlignment.start,
                                      children: [
                                        Icon(Icons.hourglass_bottom_sharp,
                                            size: 16.0,
                                            color: Colors.deepPurple),
                                        const SizedBox(width: 5.0),
                                        Text(orders[index].status,
                                            style: TextStyle(
                                                fontWeight: FontWeight.w700,
                                                color: Colors.deepPurple)),
                                      ],
                                    ),
                                  ),
                                  const SizedBox(height: 10.0),
                                  Row(
                                    mainAxisAlignment:
                                        MainAxisAlignment.spaceBetween,
                                    children: [
                                      Column(
                                        crossAxisAlignment:
                                            CrossAxisAlignment.start,
                                        children: [
                                          Text(orders[index].name,
                                              style: TextStyle(
                                                  fontSize: 16.0,
                                                  fontWeight: FontWeight.w700)),
                                          const SizedBox(height: 5.0),
                                          Text("Your delivery,",
                                              style: TextStyle(
                                                  fontSize: 12.0,
                                                  fontWeight:
                                                      FontWeight.normal)),
                                          Text("#${orders[index].id},",
                                              style: TextStyle(
                                                  fontSize: 12.0,
                                                  fontWeight:
                                                      FontWeight.normal)),
                                          Text("is on the way",
                                              style: TextStyle(
                                                  fontSize: 12.0,
                                                  fontWeight:
                                                      FontWeight.normal)),
                                          const SizedBox(height: 10.0),
                                          Text("\$${orders[index].price}",
                                              style: TextStyle(
                                                  fontSize: 14.0,
                                                  fontWeight: FontWeight.w700)),
                                        ],
                                      ),
                                      const SizedBox(width: 10.0),
                                      Expanded(
                                        child: ClipRRect(
                                          borderRadius:
                                              BorderRadius.circular(5.0),
                                          child: Image.network(
                                            orders[index].imageUrl,
                                            fit: BoxFit.contain,
                                          ),
                                        ),
                                      ),
                                    ],
                                  ),
                                ],
                              ),
                            ),
                          ]),
                    );
                  },
                ));
  }
}

Add Real-time Feature with Appwrite

With the UI done and getting data from the database, modify the home.dart file to support Real-time.

dart
//other import goes here
import 'package:appwrite/appwrite.dart';

class Home extends StatefulWidget {
  const Home({super.key});
  
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  //properties

  //add real-time capability
  RealtimeSubscription? subscription;

  
  void dispose() {
    subscription?.close();
    super.dispose();
  }

  _subscribe() {
    final realtime = Realtime(FoodTrackerService().client);
    subscription = realtime.subscribe(['documents']);
    //listening to stream
    subscription!.stream.listen((data) {
      final event = data.events.first;
      if (data.payload.isNotEmpty) {
        if (event.endsWith('.update')) {
          orders
              .map((element) => element.status = data.payload['status'])
              .toList();
          setState(() {});
        }
      }
    });
  }
  
  
  void initState() {
    getOrders();
    _subscribe();
    super.initState();
  }

  getOrders() {
    //getOrders code ge jeer
  }

  
  Widget build(BuildContext context) {
    // UI goes here
  }
}

The snippet above does the following:

  • Imports the required dependency.
  • Line 13 - 19: Sets up the real-time subscription and use the dispose method to close the connection, preventing memory leaks when the widget is removed from the UI.
  • Line 22 - 27: Creates a _subscribe method that connects to the Appwrite realtime API and susbcribes to the document. It also continuously listen to the data stream and update the orders status properties whenever an update event is triggered.
  • Line 42: Adds the _subscribe to run when the app starts running.

Lastly, update the main.dart as show below:

dart
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:food_tracker/home.dart';

Future main() async {
  await dotenv.load(fileName: ".env");
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: Home(),
    );
  }
}

With that done, start the application using the code editor or run the command below. You can test it out by changing the status in your Appwrite console.

bash
flutter run

##Conclusion

Real-time features are more than just alerts; they are essential tools for engagement, security, and efficiency. Whether you run an e-commerce store, fintech platform, or SaaS product, Appwrite real-time feature can significantly improve user experience and operational success.

These resources may also be helpful: