Realtime service is one of the most sought after features of Appwrite and it's now ready to play with! It's been a while as we already had realtime alpha release and a getting started tutorial to go with it. In this tutorial, we will dive into the details and understand how to develop a Flutter app leveraging Appwrite's realtime capabilities.
📝 Prerequisites
In order to continue with this tutorial, you need to have access to an Appwrite console with a project. If you have not already installed Appwrite, please do so. Installing Appwrite is really simple following Appwrite's official installation docs. Installation should only take around 2 minutes. Once installed, login to your console and create a new Project.
💾 Setup Database
Once you have logged in to the console and selected your project, from the left sidebar in the dashboard click on the Database option to get to the database page.
Once on the database page, click on the Add 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 where we can define its rules. Define the following rules, then click the Update button. Also note down the Collection ID from the right side of the settings page as we will need that later in our code.
-
Name
- label: Name
- Key: name
- Rule Type: Text
- Required: true
- Array: false
In the permissions, set the read and right permission both to *
so that anyone can read and write.
Now that the collection is created we need to create a user. This user will be used to create sessions when we authenticate with the realtime API.
⚙️ Setup 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 realtime 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. If you are running on Flutter web, you can simply add web platform instead of Flutter platforms. To add a new platform, go to your Appwrite console, select your project, and click the Add Platform button on the project Dashboard. Choose either Flutter or web platform.
If you choose web, add localhost as the host name. If you choose Flutter, from the dialog, choose one of the tabs, based on which platform you plan to run on. You can add multiple platforms similarly.
If you choose to add a 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 also allow adding new items as well as deleting existing items. Our Home page will also connect to Appwrite's realtime service and display changes in the items collection 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(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'FlAppwrite Realtime Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<Map<String, dynamic>> items = [];
TextEditingController _nameController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('FlAppwrite Realtime Demo'),
),
body: ListView(children: [
...items.map((item) => ListTile(
title: Text(item['name']),
)),
]),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
// dialog to add new item
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Add new item'),
content: TextField(
controller: _nameController,
),
actions: [
TextButton(
child: Text('Cancel'),
onPressed: () => Navigator.of(context).pop(),
),
TextButton(
child: 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 realtime changes in documents in our items collection.
RealtimeSubscription? subscription;
late final Client client;
initState() {
super.initState();
client = Client()
.setEndpoint('<http://localhost/v1>') // your endpoint
.setProject('5df5acd0d48c2') //your project id
;
subscribe();
}
And in dispose method, close the subscription.
dispose(){
subscription?.close();
super.dispose();
}
Now let us setup 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 realtime.
First, initialize our items collection id and and setup a function to load initial data when the application first starts. For that we will also setup Appwrite database service.
final itemsCollection = "<collectionId>"; //replace with your collection id, which can be found in your collection's settings page.
late final Database database;
@override
void initState() {
super.initState();
client = Client()
.setEndpoint('<http://localhost/v1>') // your endpoint
.setProject('5df5acd0d48c2') //your project id
;
database = Database(client);
loadItems();
}
loadItems() async {
try {
final res = await database.listDocuments(collectionId: itemsCollection);
setState(() {
items = List<Map<String, dynamic>>.from(res.data['documents']);
});
} on AppwriteException catch (e) {
print(e.message);
}
}
In order to be able to add data to our collection, we must first create a session. Let's add a login function and call it from our initState
function.
@override
void initState() {
super.initState();
//...
login();
// ..
}
login() async {
try {
await Account(client).createAnonymousSession();
} on AppwriteException catch (e) {
print(e.message);
}
}
Now, we will setup our subscribe function that will listen to changes to documents in our items collection.
void subscribe() {
final realtime = Realtime(client);
subscription = realtime.subscribe([
'collections.<collectionId>.documents'
]); //replace <collectionId> with the ID of your items collection, which can be found in your collection's settings page.
// listen to changes
subscription!.stream.listen((data) {
// data will consist of `event` and a `payload`
if (data.payload.isNotEmpty) {
switch (data.event) {
case "database.documents.create":
var item = data.payload;
items.add(item);
setState(() {});
break;
case "database.documents.delete":
var item = data.payload;
items.removeWhere((it) => it['\$id'] == item['\$id']);
setState(() {});
break;
default:
break;
}
}
});
}
Finally, let's modify our _addItem
function to add item to Appwrite's database and see how the view updates in realtime.
void _addItem(String name) async {
try {
await database.createDocument(
collectionId: itemsCollection,
data: {'name': name},
read: ['*'],
write: ['*']
);
} 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: Icon(Icons.delete),
onPressed: () async {
await database.deleteDocument(
collectionId: itemsCollection,
documentId: item['\$id'],
);
},
),
)
Complete Example
import 'package:appwrite/appwrite.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'FlAppwrite Realtime Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<Map<String, dynamic>> items = [];
TextEditingController _nameController = TextEditingController();
RealtimeSubscription? subscription;
late final Client client;
final itemsCollection = 'COLLECTION_ID';
late final Database database;
@override
void initState() {
super.initState();
client = Client()
.setEndpoint('<http://localhost/v1>') // your endpoint
.setProject('YOUR_PROJECT_ID') //your project id
;
database = Database(client);
login();
loadItems();
subscribe();
}
login() async {
try {
await Account(client).createAnonymousSession();
} on AppwriteException catch (e) {
print(e.message);
}
}
loadItems() async {
try {
final res = await database.listDocuments(collectionId: itemsCollection);
setState(() {
items = List<Map<String, dynamic>>.from(res.data['documents']);
});
} on AppwriteException catch (e) {
print(e.message);
}
}
void subscribe() {
final realtime = Realtime(client);
subscription = realtime.subscribe([
'collections.<collectionId>.documents'
]); //replace <collectionId> with the ID of your items collection, which can be found in your collection's settings page.
// listen to changes
subscription!.stream.listen((data) {
// data will consist of `event` and a `payload`
if (data.payload.isNotEmpty) {
switch (data.event) {
case "database.documents.create":
var item = data.payload;
items.add(item);
setState(() {});
break;
case "database.documents.delete":
var item = data.payload;
items.removeWhere((it) => it['\$id'] == item['\$id']);
setState(() {});
break;
default:
break;
}
}
});
}
@override
void dispose() {
subscription?.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('FlAppwrite Realtime Demo'),
),
body: ListView(children: [
...items.map((item) => ListTile(
title: Text(item['name']),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () async {
await database.deleteDocument(
collectionId: itemsCollection,
documentId: item['\$id'],
);
},
),
)),
]),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
// dialog to add new item
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Add new item'),
content: TextField(
controller: _nameController,
),
actions: [
TextButton(
child: Text('Cancel'),
onPressed: () => Navigator.of(context).pop(),
),
TextButton(
child: 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 database.createDocument(
collectionId: itemsCollection,
data: {'name': name},
read: ['*'],
write: ['*']);
} on AppwriteException catch (e) {
print(e.message);
}
}
}
🥂 Conclusion
I enjoyed a lot writing tutorial and I hope you enjoyed learning and building Flutter application with Appwrite Realtime service. The full source code for this application is available on my GitHub repository. Feel free to get back to us if you have any queries or comments. Excited to see what the community will build with Flutter and Appwrite Realtime.
Top comments (3)
Here are my changes for working with new SDKs versions so far:
Database changed to databases:
late final Databases database;
....
database = Databases(client, **databaseId: 'XXXX'**);
on loadItems():
items = List<Map<String, dynamic>>.from(
res.documents.map((doc) => Map<String, dynamic>.from(doc.data)));
// this way you will get all properties defined for the document on your items[]
on subscribe():
// new channel name
subscription = realtime.subscribe([
'databases.$itemsDatabase.collections.$itemsCollection.documents'
]);
// _event changed to List events_
for (var ev in data.events) {
switch (ev) {
// new event name???
case "databases.*.collections.*.documents.*.create":
.......
case "databases.*.collections.*.documents.*.delete":
on _addItem():
// new key needed
documentId: 'unique()',
// permission "*" deprecated
read: ['role:all'],
write: ['role:all'],
For some reason, the _loadItems method could not work for me. However, when I assigned items to this it worked.
items = results.documents
.map((document) => {"name": document.data['name']})
.toList();
I guess because of different versions of AppWrite.
Yes actually the new Appwrite SDK has the response models so we no longer receive map