Code With Bisky

Build Real-Time Messaging in Flutter with Appwrite: Create a Live Chat Feature for Your App! Episode [11]

Key Topics covered:

  • Updating the UI in real-time to display new messages
  • Query messages
  • Introduction to Real-Time

Description:

In this tutorial, we dive into the world of instant communication, enabling you to create a seamless chat experience. By harnessing Flutter's robust framework and Appwrite's powerful Realtime capabilities, you'll discover how to implement live chat functionality that keeps users engaged and connected.

  • Create lib/core/providers/RealtimeProvider.dart

import 'package:appwrite/appwrite.dart';
import 'package:chat_with_bisky/core/providers/AppwriteClientProvider.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

final realtimeProvider=  Provider((ref) => Realtime(ref.read(appwriteClientProvider)));

        
        

Add Set<MessageAppwrite> messages in MessageState.dart

  • lib/model/MessageState.dart

@Default({}) Set<MessageAppwrite> messages,
        
        

Don't forget to run the following command in your terminal to regenerate MessageState.freezed.dart with messagesflutter packages pub run build_runner build

Code Snippet(MessageScreen.dart):

Refactor MessageScreen to extend ConsumerStatefulWidget and _MessageScreenState to extend ConsumerState

  • lib/pages/dashboard/chat/MessageScreen.dart

class MessageScreen extends ConsumerStatefulWidget {
        
        

Declare RealtimeSubscription, Realtime and ScrollController in _MessageScreenState



class _MessageScreenState extends ConsumerState<MessageScreen> {
            RealtimeSubscription? subscription;
  Realtime? realtime;
  final ScrollController _scrollController = ScrollController();

        
        

Create a ListView to load messages from MessageViewModel state. Replace Flexible(child: Container()), chatInputWidget() with code below


Flexible(
         child: ListView.builder(
         controller: _scrollController,
         padding: const EdgeInsets.all(10.0),
         itemBuilder: (context, index) {
                MessageAppwrite message =
                    messageState?.messages.elementAt(index) ??
                        MessageAppwrite();

                return chatMessageItem(message);
              },
              itemCount: messageState?.messages.length,
              reverse: true,
            )),
            chatInputWidget()

        
        

Delay by 3 seconds in initState() method


  @override
  void initState() {
    super.initState();

    Future.delayed(
      const Duration(seconds: 3),
      () {
        getMessages();
      },
    );
  }

        
        

Create a getMessages() method


void getMessages() {
    messageNotifier?.friendUserIdChanged(friendUserId);
    messageNotifier?.myUserIdChanged(myUserId);
    messageNotifier?.getMessages();
  }
        

Create a listenRealTimeMessages() method to listen incoming messages


void listenRealTimeMessages() {
    String key = '$friendUserId$myUserId';
    String key2 = '$myUserId$friendUserId';
    subscription = realtime?.subscribe([
      'databases.${Strings.databaseId}.collections.${Strings.collectionChatsId}.documents.${key}',
      'databases.${Strings.databaseId}.collections.${Strings.collectionChatsId}.documents.${key2}',
    ]);

    subscription?.stream.listen((response) {
      print(response.payload);
      getMessages(); // we will refactor this later
    });
  }

        

Create a build chat layout widget


Widget chatMessageItem(MessageAppwrite documentSnapshot) {
    return buildChatLayout(documentSnapshot);
  }

Widget buildChatLayout(MessageAppwrite snapshot) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.start,
      children: <Widget>[
        Padding(
          padding: const EdgeInsets.all(12.0),
          child: Row(
            mainAxisAlignment: snapshot.senderUserId == myUserId
                ? MainAxisAlignment.end
                : MainAxisAlignment.start,
            children: <Widget>[
              const SizedBox(
                width: 10.0,
              ),
              Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: <Widget>[
                  snapshot.senderUserId != friendUserId
                      ? const Text(
                          "",
                          style: TextStyle(
                              color: Colors.black,
                              fontSize: 16.0,
                              fontWeight: FontWeight.bold),
                        )
                      : Text(
                          displayName,
                          style: const TextStyle(
                              color: Colors.black,
                              fontSize: 16.0,
                              fontWeight: FontWeight.bold),
                        ),
                  messageWidget(snapshot)
                ],
              )
            ],
          ),
        ),
      ],
    );
  }


        

Create Message Widget


  Widget messageWidget(MessageAppwrite messageAppwrite) {
    if (messageAppwrite.type == 'TEXT') {
      return Text(
        messageAppwrite.message ?? "",
        style: const TextStyle(color: Colors.black, fontSize: 14.0),
      );
    }
    return Container();
  }
 
        

Code Snippet(MessageViewModel.dart) modification:

Add method to get messages from Appwrite database

  • lib/pages/dashboard/chat/MessageViewModel.dart

  getMessages() async {
    try {
      DocumentList documentList = await _databases.listDocuments(
          databaseId: Strings.databaseId,
          collectionId: Strings.collectionMessagesId,
          queries: [
            Query.equal("senderUserId", [state.myUserId, state.friendUserId]),
            Query.equal("receiverUserId", [state.myUserId, state.friendUserId]),
            Query.orderDesc('\$createdAt')
          ]);

      if (documentList.total > 0) {
        Set<MessageAppwrite> messages = {};
        for (Document document in documentList.documents) {
          MessageAppwrite message = MessageAppwrite.fromJson(document.data);
          messages.add(message);
        }
        state = state.copyWith(messages: messages);
      }
    } on AppwriteException catch (exception) {
      print(exception);
    }
  } 
        

Conclusion:

We managed to retrieve messages. We added logic in MessageViewModel to handle our business logic for getting messages. We will refactor realtime messages on the later videos. We added Appwrite Realtime implementation in MessagesScreen. In the next tutorial, we are going to persist these messages in local storage using realm database. Don't forget to share and join our Discord Channel. May you please subscribe to our YouTube Channel.