DEV Community

Cover image for Getting Started with Appwrite Realtime for Flutter
Damodar Lohani for Appwrite

Posted on

Getting Started with Appwrite Realtime for Flutter

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.

Create Collection

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

Add Collection Rules

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Add Flutter Platform

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});
    });
  }
}

Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

And in dispose method, close the subscription.

dispose(){
    subscription?.close();
    super.dispose();
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
        }
      }
    });
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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'],
            );
        },
    ),
)
Enter fullscreen mode Exit fullscreen mode

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);
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

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

🎓 Learn More

Top comments (3)

Collapse
 
elbriga profile image
Gabriel Lour • Edited

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'],

Collapse
 
akamaicloud profile image
Otto Akama

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.

Collapse
 
lohanidamodar profile image
Damodar Lohani

Yes actually the new Appwrite SDK has the response models so we no longer receive map