Realtime service is one of Appwrite’s most popular features. It allows you to subscribe and react to any Appwrite event using the Realtime API.
In this tutorial, we will get into the details and learn how to develop a Flutter app that leverages Appwrite's real-time capabilities.
Prerequisites
To continue with this tutorial, you'll need access to an Appwrite project. If you don't have one, you can sign up on Appwrite Cloud.
Set up a database
Once you have logged in to the Console and selected your project, click on Databases in the left sidebar of the dashboard.
On the databases page, click on the Create database button.
In the dialog that pops up, enter a name and database ID, and click Create to create the database and show the database page. Make sure to note down the database ID next to the database name as we will need that later in our code.
Once on the database page, click on the Create collection button.
In the dialog that pops up, set the collection name to Items and click on the Create button to create the collection, and you will be redirected to the new collection's page.
Switch to the Attributes tab and create the following attribute. Also note down the Collection ID from the top of the page next to the collection name.
Type: String
Attribute Key: name
Size: 25
Default: null
Required: true
Array: false
Switch to the Settings tab and scroll down to Permissions to configure the permissions for the collection. Add the Any role and check create, read, update, and delete so that anyone can read and write.
Set up Flutter project and dependencies
We will begin by creating a new Flutter project. From your terminal in your project folder, type the following command to create a new Flutter project.
flutter create flappwrite_realtime
Then we add the Appwrite's SDK, to do that from your terminal, in your newly created project directory, type the following command:
cd flappwrite_realtime
flutter pub add appwrite
This command will add Appwrite's latest Flutter SDK with real-time service as a dependency to your Flutter project.
Once you have installed the dependency and run flutter pub get you should be ready to use it.
Add Flutter platforms
To initialize the Appwrite SDK and start interacting with Appwrite services, you first need to add a new Flutter platform to your project. To add a new platform, go to the Appwrite Console, select your project, and click the Add Platform button on the project Overview. Click on the Flutter app to register a Flutter platform.
In the wizard that appears, choose the platform you plan to run on. If you want to run on another platform, add another platform
If you choose to add an Android platform, on the dialog box, add the details. Add your app name and package name. Your package name is generally the applicationId in your app-level build.gradle
file. You may also find your package name in your AndroidManifest.xml file.
By registering a new platform, you are allowing your app to communicate with the Appwrite API.
Home page
We will start by creating a simple stateful widget that will list all the items from our items collection and allow adding new items and deleting existing items. Our home page will also connect to Appwrite's Realtime service and display changes in the collection of items by updating the UI as they happen. So, let's create our HomePage widget. Modify the code in lib/main.dart as follows:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'FlAppwrite Realtime Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<Map<String, dynamic>> items = [];
final TextEditingController _nameController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('FlAppwrite Realtime Demo'),
),
body: ListView(children: [
...items.map((item) => ListTile(
title: Text(item['name']),
)),
]),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
// dialog to add new item
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Add new item'),
content: TextField(
controller: _nameController,
),
actions: [
TextButton(
child: const Text('Cancel'),
onPressed: () => Navigator.of(context).pop(),
),
TextButton(
child: const Text('Add'),
onPressed: () {
// add new item
final name = _nameController.text;
if (name.isNotEmpty) {
_nameController.clear();
_addItem(name);
}
Navigator.of(context).pop();
},
),
],
),
);
},
),
);
}
void _addItem(String name) {
setState(() {
items.add({'name': name, 'id': DateTime.now().millisecondsSinceEpoch});
});
}
}
In the initState function of the HomePage, we will create and initialize our Appwrite client, as well as subscribe to real-time changes in documents in our items collection.
RealtimeSubscription? subscription;
late final Client client;
@override
initState() {
super.initState();
client = Client().setProject('<PROJECT_ID>'); // your project id
}
And in dispose method, close the subscription.
dispose(){
subscription?.close();
super.dispose();
}
Now, let us set up different variables and functions to load the initial data, listen to changes in the collection documents, and update the UI to reflect the changes in real time.
First, initialize our database ID and items collection ID and set up a function to load initial data when the application first starts. For that, we will also set up an Appwrite database service.
final database = 'default'; // your database id
final itemsCollection = 'items'; // your collection id
late final Databases databases;
@override
initState() {
super.initState();
client = Client().setProject('delete'); // your project id
databases = Databases(client);
loadItems();
subscribe();
}
loadItems() async {
try {
final res = await databases.listDocuments(
databaseId: database,
collectionId: itemsCollection,
);
setState(() {
items =
List<Map<String, dynamic>>.from(res.documents.map((e) => e.data));
});
} on AppwriteException catch (e) {
print(e.message);
}
}
Now, we will set up our subscribe function, which will listen to changes to documents in our items collection.
void subscribe() {
final realtime = Realtime(client);
subscription = realtime.subscribe([
'documents' // subscribe to all documents in every database and collection
]);
// listen to changes
subscription!.stream.listen((data) {
// data will consist of `events` and a `payload`
final event = data.events.first;
if (data.payload.isNotEmpty) {
if (event.endsWith('.create')) {
var item = data.payload;
items.add(item);
setState(() {});
} else if (event.endsWith('.delete')) {
var item = data.payload;
items.removeWhere((it) => it['\$id'] == item['\$id']);
setState(() {});
}
}
});
}
Finally, let's modify our _addItem function to add items to Appwrite's database and see how the view updates in real time.
void _addItem(String name) async {
try {
await databases.createDocument(
databaseId: database,
collectionId: itemsCollection,
documentId: ID.unique(),
data: {'name': name},
);
} on AppwriteException catch (e) {
print(e.message);
}
}
Let us also modify our ListTile widget to add a delete button that will allow us to delete the item.
ListTile(
title: Text(item['name']),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () async {
await databases.deleteDocument(
databaseId: database,
collectionId: itemsCollection,
documentId: item['\$id'],
);
},
),
)
Complete example
import 'package:appwrite/appwrite.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'FlAppwrite Realtime Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<Map<String, dynamic>> items = [];
final TextEditingController _nameController = TextEditingController();
RealtimeSubscription? subscription;
late final Client client;
final database = 'default'; // your database id
final itemsCollection = 'items'; // your collection id
late final Databases databases;
@override
initState() {
super.initState();
client = Client().setProject('delete'); // your project id
databases = Databases(client);
loadItems();
subscribe();
}
loadItems() async {
try {
final res = await databases.listDocuments(
databaseId: database,
collectionId: itemsCollection,
);
setState(() {
items =
List<Map<String, dynamic>>.from(res.documents.map((e) => e.data));
});
} on AppwriteException catch (e) {
print(e.message);
}
}
void subscribe() {
final realtime = Realtime(client);
subscription = realtime.subscribe([
'documents' // subscribe to all documents in every database and collection
]);
// listen to changes
subscription!.stream.listen((data) {
// data will consist of `events` and a `payload`
final event = data.events.first;
if (data.payload.isNotEmpty) {
if (event.endsWith('.create')) {
var item = data.payload;
items.add(item);
setState(() {});
} else if (event.endsWith('.delete')) {
var item = data.payload;
items.removeWhere((it) => it['\$id'] == item['\$id']);
setState(() {});
}
}
});
}
@override
dispose() {
subscription?.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('FlAppwrite Realtime Demo'),
),
body: ListView(children: [
...items.map((item) => ListTile(
title: Text(item['name']),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () async {
await databases.deleteDocument(
databaseId: database,
collectionId: itemsCollection,
documentId: item['\$id'],
);
},
),
)),
]),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
// dialog to add new item
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Add new item'),
content: TextField(
controller: _nameController,
),
actions: [
TextButton(
child: const Text('Cancel'),
onPressed: () => Navigator.of(context).pop(),
),
TextButton(
child: const Text('Add'),
onPressed: () {
// add new item
final name = _nameController.text;
if (name.isNotEmpty) {
_nameController.clear();
_addItem(name);
}
Navigator.of(context).pop();
},
),
],
),
);
},
),
);
}
void _addItem(String name) async {
try {
await databases.createDocument(
databaseId: database,
collectionId: itemsCollection,
documentId: ID.unique(),
data: {'name': name},
);
} on AppwriteException catch (e) {
print(e.message);
}
}
}
Conclusion
I hope you enjoyed learning and building Flutter applications with Appwrite Realtime service.
If you have any questions, feel free to ask on our Discord server. You can also share your apps built Flutter and Appwrite Realtime on Built with Appwrite, and we'll feature it on our socials!
Happy coding!