How to add real-time support to your Flutter application
This post discusses how to add real-time support to your Flutter app using Appwrite. It explains what real-time means, what to consider, and where it is used. You'll also learn how to integrate Appwrite to enable live updates and improve the overall experience of your app.

Demola Malomo
Feb 03 2025
7 min read

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.
1flutter create food_tracker
Then change directory into the generated project folder and install the required dependencies.
1cd food_tracker 2flutter 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:
1APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1 2APPWRITE_PROJECT_ID=<REPLACE WITH YOUR PROJECT ID> 3APPWRITE_DATABASE_ID=<REPLACE WITH YOUR DATABASE ID> 4APPWRITE_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.
1assets: 2 - .env
Finally, load the .env
file in the main.dart
file.
1import 'package:flutter/material.dart'; 2import 'package:flutter_dotenv/flutter_dotenv.dart'; 3 4Future main() async { 5 await dotenv.load(fileName: ".env"); 6 runApp(const MyApp()); 7} 8 9// 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:
1import 'package:appwrite/appwrite.dart'; 2import 'package:flutter_dotenv/flutter_dotenv.dart'; 3 4class Order { 5 String id; 6 String name; 7 int price; 8 String imageUrl; 9 String status; 10 11 Order({ 12 required this.id, 13 required this.name, 14 required this.price, 15 required this.imageUrl, 16 required this.status, 17 }); 18 19 factory Order.fromJson(Map<dynamic, dynamic> json) { 20 return Order( 21 id: json['\$id'], 22 name: json['name'], 23 price: json['price'], 24 imageUrl: json['image_url'], 25 status: json['status'], 26 ); 27 } 28} 29 30class FoodTrackerService { 31 static final String _endPoint = dotenv.get("APPWRITE_ENDPOINT"); 32 static final String _projectId = dotenv.get("APPWRITE_PROJECT_ID"); 33 static final String _databaseId = dotenv.get("APPWRITE_DATABASE_ID"); 34 static final String _collectionId = dotenv.get("APPWRITE_COLLECTION_ID"); 35 36 final Client client = Client(); 37 late Databases _database; 38 39 FoodTrackerService() { 40 _init(); 41 } 42 43 //initialize the Appwrite client 44 _init() async { 45 client.setEndpoint(_endPoint).setProject(_projectId); 46 _database = Databases(client); 47 //get current session 48 Account account = Account(client); 49 try { 50 await account.get(); 51 } on AppwriteException catch (e) { 52 if (e.code == 401) { 53 account 54 .createAnonymousSession() 55 .then((value) => value) 56 .catchError((e) => e); 57 } 58 } 59 } 60 61 Future<List<Order>> getFoodTracker() async { 62 List<Order> foodTrackerList = []; 63 try { 64 final response = await _database.listDocuments( 65 databaseId: _databaseId, 66 collectionId: _collectionId, 67 ); 68 foodTrackerList = response.documents 69 .map<Order>((json) => Order.fromJson(json.data)) 70 .toList(); 71 } catch (e) { 72 print(e); 73 } 74 return foodTrackerList; 75 } 76}
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 agetFoodTracker
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.
1import 'package:flutter/material.dart'; 2import 'package:food_tracker/utils.dart'; 3 4class Home extends StatefulWidget { 5 const Home({super.key}); 6 7 State<Home> createState() => _HomeState(); 8} 9 10class _HomeState extends State<Home> { 11 late List<Order> orders; 12 bool _isLoading = false; 13 bool _isError = false; 14 15 16 void initState() { 17 getOrders(); 18 super.initState(); 19 } 20 21 getOrders() { 22 setState(() { 23 _isLoading = true; 24 }); 25 FoodTrackerService().getFoodTracker().then((value) { 26 setState(() { 27 orders = value; 28 _isLoading = false; 29 }); 30 }).catchError((e) { 31 setState(() { 32 _isError = true; 33 _isLoading = false; 34 }); 35 }); 36 } 37 38 39 Widget build(BuildContext context) { 40 // UI code goes here 41 } 42}
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 theFoodTrackerService().getFoodTracker
service and set states accordingly.
Lastly, use the return data to populate the UI.
1//import goes here 2 3class Home extends StatefulWidget { 4 const Home({super.key}); 5 6 State<Home> createState() => _HomeState(); 7} 8 9class _HomeState extends State<Home> { 10 //properties 11 12 13 void initState() { 14 getOrders(); 15 super.initState(); 16 } 17 18 getOrders() { 19 //getOrders code ge jeer 20 } 21 22 23 Widget build(BuildContext context) { 24 return _isLoading 25 ? const Center( 26 child: CircularProgressIndicator( 27 color: Colors.blue, 28 )) 29 : _isError 30 ? const Center( 31 child: Text( 32 'Error getting orders', 33 style: TextStyle( 34 color: Colors.red, 35 fontWeight: FontWeight.bold, 36 ), 37 ), 38 ) 39 : Scaffold( 40 appBar: AppBar( 41 title: const Text('Food Delivery Tracker', 42 style: TextStyle(color: Colors.white)), 43 primary: true, 44 backgroundColor: Colors.deepPurple, 45 ), 46 body: ListView.builder( 47 itemCount: orders.length, 48 itemBuilder: (context, index) { 49 return Padding( 50 padding: EdgeInsets.all(30.0), 51 child: Column( 52 crossAxisAlignment: CrossAxisAlignment.start, 53 children: [ 54 Text("Order Details", 55 style: TextStyle( 56 fontSize: 20.0, 57 fontWeight: FontWeight.w600)), 58 const SizedBox(height: 10.0), 59 Container( 60 padding: const EdgeInsets.all(10.0), 61 decoration: BoxDecoration( 62 color: Colors.grey[200], 63 borderRadius: 64 const BorderRadius.all(Radius.circular(20)), 65 ), 66 child: Column( 67 crossAxisAlignment: CrossAxisAlignment.start, 68 children: [ 69 Container( 70 padding: const EdgeInsets.all(10.0), 71 decoration: BoxDecoration( 72 color: Colors.grey[300], 73 borderRadius: const BorderRadius.all( 74 Radius.circular(20)), 75 ), 76 child: Row( 77 mainAxisSize: MainAxisSize.min, 78 mainAxisAlignment: 79 MainAxisAlignment.start, 80 children: [ 81 Icon(Icons.hourglass_bottom_sharp, 82 size: 16.0, 83 color: Colors.deepPurple), 84 const SizedBox(width: 5.0), 85 Text(orders[index].status, 86 style: TextStyle( 87 fontWeight: FontWeight.w700, 88 color: Colors.deepPurple)), 89 ], 90 ), 91 ), 92 const SizedBox(height: 10.0), 93 Row( 94 mainAxisAlignment: 95 MainAxisAlignment.spaceBetween, 96 children: [ 97 Column( 98 crossAxisAlignment: 99 CrossAxisAlignment.start, 100 children: [ 101 Text(orders[index].name, 102 style: TextStyle( 103 fontSize: 16.0, 104 fontWeight: FontWeight.w700)), 105 const SizedBox(height: 5.0), 106 Text("Your delivery,", 107 style: TextStyle( 108 fontSize: 12.0, 109 fontWeight: 110 FontWeight.normal)), 111 Text("#${orders[index].id},", 112 style: TextStyle( 113 fontSize: 12.0, 114 fontWeight: 115 FontWeight.normal)), 116 Text("is on the way", 117 style: TextStyle( 118 fontSize: 12.0, 119 fontWeight: 120 FontWeight.normal)), 121 const SizedBox(height: 10.0), 122 Text("\$${orders[index].price}", 123 style: TextStyle( 124 fontSize: 14.0, 125 fontWeight: FontWeight.w700)), 126 ], 127 ), 128 const SizedBox(width: 10.0), 129 Expanded( 130 child: ClipRRect( 131 borderRadius: 132 BorderRadius.circular(5.0), 133 child: Image.network( 134 orders[index].imageUrl, 135 fit: BoxFit.contain, 136 ), 137 ), 138 ), 139 ], 140 ), 141 ], 142 ), 143 ), 144 ]), 145 ); 146 }, 147 )); 148 } 149}
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.
1//other import goes here 2import 'package:appwrite/appwrite.dart'; 3 4class Home extends StatefulWidget { 5 const Home({super.key}); 6 7 State<Home> createState() => _HomeState(); 8} 9 10class _HomeState extends State<Home> { 11 //properties 12 13 //add real-time capability 14 RealtimeSubscription? subscription; 15 16 17 void dispose() { 18 subscription?.close(); 19 super.dispose(); 20 } 21 22 _subscribe() { 23 final realtime = Realtime(FoodTrackerService().client); 24 subscription = realtime.subscribe(['documents']); 25 //listening to stream 26 subscription!.stream.listen((data) { 27 final event = data.events.first; 28 if (data.payload.isNotEmpty) { 29 if (event.endsWith('.update')) { 30 orders 31 .map((element) => element.status = data.payload['status']) 32 .toList(); 33 setState(() {}); 34 } 35 } 36 }); 37 } 38 39 40 void initState() { 41 getOrders(); 42 _subscribe(); 43 super.initState(); 44 } 45 46 getOrders() { 47 //getOrders code ge jeer 48 } 49 50 51 Widget build(BuildContext context) { 52 // UI goes here 53 } 54}
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 thedocument
. It also continuously listen to the data stream and update theorders
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:
1import 'package:flutter/material.dart'; 2import 'package:flutter_dotenv/flutter_dotenv.dart'; 3import 'package:food_tracker/home.dart'; 4 5Future main() async { 6 await dotenv.load(fileName: ".env"); 7 runApp(const MyApp()); 8} 9 10class MyApp extends StatelessWidget { 11 const MyApp({super.key}); 12 13 Widget build(BuildContext context) { 14 return MaterialApp( 15 title: 'Flutter Demo', 16 theme: ThemeData( 17 colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), 18 useMaterial3: true, 19 ), 20 home: Home(), 21 ); 22 } 23}
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.
1flutter 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: