Code With Bisky

Effortless Message Persistence: Mastering Appwrite Database for Seamless Data Storage Episode [10]

Key Topics covered:

  • Save message and chat head
  • Retrieving messages
  • Appwrite Query
  • Appwrite Database
  • Create MessageViewModel
  • Displaying Messages

Description:

In this tutorial, we will walk you through how to save messages chat heads in Appwrite Database. You will also able to see the message we sent on the ListView. We will reverse the messages and sorting them by descending order to get the latest messages.

  • Add the following code below in lib/constant/strings.dart. You can update with your own id's from Appwrite dashboard

 static const collectionMessagesId = "64937c79c45263421fc0";
 static const collectionChatsId = "64937f479aeeb519fdbf";
        
        

Code Snippet(DatabaseProvider.dart):

Create an DatabaseProvider.dart. We are using riverpod Provider to manage DatabaseProvider. We will use it to save data into Appwrite database.

  • lib/core/providers/DatabaseProvider.dart

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

final databaseProvider=  Provider((ref) => Databases(ref.read(appwriteClientProvider)));
        
        

Code Snippet(ChatAppwrite.dart):

Create a new model called ChatAppwrite.dart. We will use this model to persist the chat head in Appwrite database collection. Create a chats collection on Appwrite dashboard with the same attributes that are in ChatAppwrite.dart

  • lib/model/ChatAppwrite.dart

import 'package:freezed_annotation/freezed_annotation.dart';

part 'ChatAppwrite.g.dart';

@JsonSerializable()
class ChatAppwrite {
  String? senderUserId;
  String? receiverUserId;
  String? message;
  String? type;
  DateTime? sendDate;
  bool? read;
  String? key;
  String? displayName;
  int? count;

  ChatAppwrite({
    this.senderUserId,
    this.receiverUserId,
    this.message,
    this.type,
    this.sendDate,
    this.read,
    this.key,
    this.displayName,
    this.count,
  });

  factory ChatAppwrite.fromJson(Map<String, dynamic> json) =>
      _$ChatAppwriteFromJson(json);

  Map<String, dynamic> toJson() => _$ChatAppwriteToJson(this);
}
        
        

Don't forget to run the following command in your terminal to generate ChatAppwrite.g.dart flutter packages pub run build_runner build

Code Snippet(MessageAppwrite.dart):

Create a new model called MessageAppwrite.dart. We will use this model to persist the message in Appwrite database collection. Create a messages collection on Appwrite dashboard with the same attributes that are in MessageAppwrite.dart

  • lib/model/MessageAppwrite.dart

import 'package:freezed_annotation/freezed_annotation.dart';

part 'MessageAppwrite.g.dart';

@JsonSerializable()
class MessageAppwrite {
  String? senderUserId;
  String? receiverUserId;
  String? message;
  String? type;
  DateTime? sendDate;
  bool? read;

  MessageAppwrite({
    this.senderUserId,
    this.receiverUserId,
    this.message,
    this.type,
    this.sendDate,
    this.read,
  });

  factory MessageAppwrite.fromJson(Map<String, dynamic> json) =>
      _$MessageAppwriteFromJson(json);

  Map<String, dynamic> toJson() => _$MessageAppwriteToJson(this);
}
        
        

Don't forget to run the following command in your terminal to generate MessageAppwrite.g.dart flutter packages pub run build_runner build

Code Snippet(MessageState.dart):

Create a new model called MessageState.dart. We will use this model to store the state of the message.

  • lib/model/MessageState.dart

import 'package:freezed_annotation/freezed_annotation.dart';

part 'MessageState.freezed.dart';

@freezed
class MessageState with _$MessageState {
  factory MessageState({
    @Default('') String myUserId,
    @Default('') String friendUserId,
    @Default('') String message,
    @Default('') String messageType,
    @Default(false) bool loading,
  }) = _MessageState;
}
        
        

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

Code Snippet(MessageViewModel.dart):

Create a new class MessageViewModel.dart. We will add implementation to send messages, create chats head and retrieving messages

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

import 'package:appwrite/appwrite.dart';
import 'package:appwrite/models.dart';
import 'package:chat_with_bisky/constant/strings.dart';
import 'package:chat_with_bisky/core/providers/DatabaseProvider.dart';
import 'package:chat_with_bisky/model/ChatAppwrite.dart';
import 'package:chat_with_bisky/model/MessageAppwrite.dart';
import 'package:chat_with_bisky/model/MessageState.dart';
import 'package:chat_with_bisky/model/UserAppwrite.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:uuid/uuid.dart';

part 'MessageViewModel.g.dart';

@riverpod
class MessageNotifier extends _$MessageNotifier {
  Databases get _databases => ref.read(databaseProvider);

  @override
  MessageState build() {
    return MessageState();
  }

  void messageTypeChanged(String input) {
    state = state.copyWith(messageType: input);
  }

  void messageChanged(String input) {
    state = state.copyWith(message: input);
  }

  void friendUserIdChanged(String input) {
    state = state.copyWith(friendUserId: input);
  }

  void myUserIdChanged(String input) {
    state = state.copyWith(myUserId: input);
  }

  Future<void> sendMessage() async {
    try {
      MessageAppwrite message = MessageAppwrite();
      message.senderUserId = state.myUserId;
      message.receiverUserId = state.friendUserId;
      message.read = false;
      message.message = state.message;
      message.type = state.messageType;
      message.sendDate = DateTime.now();

      Document document = await _databases.createDocument(
          databaseId: Strings.databaseId,
          collectionId: Strings.collectionMessagesId,
          documentId: const Uuid().v4(),
          data: message.toJson());

      print("message sent ${document.$id}");

      String key1 = "${message.receiverUserId}${message.senderUserId}";
      String key2 = "${message.senderUserId}${message.receiverUserId}";

      createOrUpdateChatHead(message,key1,state.friendUserId,state.myUserId);
      createOrUpdateChatHead(message,key2,state.myUserId,state.friendUserId);

    } on AppwriteException catch (exception) {
      print(exception);
    }
  }

  Future<void> createOrUpdateChatHead(MessageAppwrite message, String key,
      String friendUserId, String myUserId) async {

    ChatAppwrite chatAppwrite = ChatAppwrite(
        message: message.message,
        displayName: await getDisplayName(myUserId),
        sendDate: DateTime.now(),
        receiverUserId: friendUserId,
        senderUserId: myUserId,
        type: message.type,
        count: 1,
        key: key,
        read: false);

    try {
      Document document = await _databases.getDocument(
          databaseId: Strings.databaseId,
          collectionId: Strings.collectionChatsId,
          documentId: key);

      ChatAppwrite retrieved = ChatAppwrite.fromJson(document.data);
      chatAppwrite.count = retrieved.count ?? 1 + 1;


      Document documentChatUpdate = await _databases.updateDocument(
          databaseId: Strings.databaseId,
          collectionId: Strings.collectionChatsId,
          documentId:key,
          data: chatAppwrite.toJson());


      print("chat head updated ${documentChatUpdate.$id}");

    } on AppwriteException catch (exception) {
      print(exception);

      if(exception.code == 404){

        Document documentChatUpdate = await _databases.createDocument(
            databaseId: Strings.databaseId,
            collectionId: Strings.collectionChatsId,
            documentId:key,
            data: chatAppwrite.toJson());


        print("chat head created ${documentChatUpdate.$id}");
      }
    }
  }

  getDisplayName(String userId) async {
    try {
      Document document = await _databases.getDocument(
          databaseId: Strings.databaseId,
          collectionId: Strings.collectionUsersId,
          documentId: userId);

      UserAppwrite userAppwrite = UserAppwrite.fromJson(document.data);

      return userAppwrite.name;
    } on AppwriteException catch (exception) {
      print(exception);
    }

    return userId;
  }
} 
        

Don't forget to run the following command in your terminal to generate MessageViewModel.g.dart flutter packages pub run build_runner build

Code Snippet(MessageScreen.dart) modification:

Let's a add an event to send the message to a send button

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

 MessageNotifier? messageNotifier;
 MessageState? messageState;
// Global declaration the above variables in MessageScreen

// initialize the variables in build method
    messageNotifier = ref.read(messageNotifierProvider.notifier);
    messageState = ref.watch(messageNotifierProvider);

// add below code in sendMessageMethod() method

messageNotifier?.friendUserIdChanged(friendUserId);
messageNotifier?.myUserIdChanged(myUserId);
messageNotifier?.messageTypeChanged(type);
messageNotifier?.messageChanged(message);
messageNotifier?.sendMessage();

        
        

Conclusion:

We managed to send a Message to a friend. We created MessageViewModel to handle our business logic. We will refactor it in the later videos. We didn't manage to retrieve messages on this tutorial. Let's continue in the next video with realtime messaging. Don't forget to share and join our Discord Channel. May you please subscribe to our YouTube Channel.