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.

avatar

Demola Malomo

Feb 03 2025

7 min read

avatar

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.

KeyData
namespaghetti bolognese
price20
image_urlhttps://res.cloudinary.com/dtgbzmpca/image/upload/v1737422520/di8w324-73ebb36d-325a-48e5-9c01-20fec04fa02a.jpg_qb9qqu.jpg
statuspacked

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.

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:

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

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 the FoodTrackerService().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 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:

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:

Related posts