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:
- 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.
- Fintech & Payments: Improve security and build user trust by notifying customers about transfers, bill payments, and fraud prevention tips.
- Software-as-a-Service (SaaS): Enhance user experience by sending notifications about scheduled maintenance, downtime, and performance updates.
- Healthcare: Improve patient care with reminders for upcoming appointments, prescription refills, and emergency alerts for medical staff.
- 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.

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

Next, switch to the Attributes tab and click the Create attribute button to add attributes as shown below.
| Type | Attribute Key | Size | Required | Elements |
|---|---|---|---|---|
| String | name | 225 | YES | - |
| Float | price | - | YES | - |
| URL | image_url | - | YES | - |
| Enum | status | YES | packed, shipped, in-transit, delivered |
You should have something similar to the image below:

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.

Finally, switch back to the Document tab, click the Create document button, and populate it as shown below.
| Key | Data |
|---|---|
| name | spaghetti bolognese |
| price | 20 |
| image_url | https://res.cloudinary.com/dtgbzmpca/image/upload/v1737422520/di8w324-73ebb36d-325a-48e5-9c01-20fec04fa02a.jpg_qb9qqu.jpg |
| status | packed |
##Set up Flutter and add Dependencies
To get started, create a new flutter project by running the command below in your terminal.
flutter create food_trackerThen change directory into the generated project folder and install the required dependencies.
cd food_tracker
flutter pub add appwrite flutter_dotenvThe 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:
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.
assets:
- .envFinally, load the .env file in the main.dart file.
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.

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.

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.

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:
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
Orderclass to represent the application data and map the corresponding attributes from a JSON. - Creates a
FoodTrackerServiceclass that initializes Appwrite by creating an anonymous user that can securely connect to the server and adds agetFoodTrackermethods 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.
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_isErrorproperties to manage the application state. - Creates a
getOrdersmethod to get the list of orders from the database using theFoodTrackerService().getFoodTrackerservice and set states accordingly.
Lastly, use the return data to populate the UI.
//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.
//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
disposemethod to close the connection, preventing memory leaks when the widget is removed from the UI. - Line 22 - 27: Creates a
_subscribemethod that connects to the Appwrite realtime API and susbcribes to thedocument. It also continuously listen to the data stream and update theordersstatus properties whenever an update event is triggered. - Line 42: Adds the
_subscribeto run when the app starts running.
Lastly, update the main.dart as show below:
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.
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:

