Swift

Chat events

Earlier you created a conversation in the Vonage CLI and added the two users to that conversation. Conversations are how the users will communicate. You can learn more about conversations in the Conversation API documentation. Chat events, or VGConversationEvent objects, are sent using the conversation that you created, so to get chat event you will first need to join the conversation. To implement this, update the ChatViewModel class.

Add the getMemberIDIfNeeded function:

@MainActor
final class ChatViewModel: NSObject, ObservableObject {
    ...
    
    func getMemberIDIfNeeded() async {
        guard memberID == nil else { return }
        await getMemberID()
    }   
}

This will check if the memberID, which is generated when you join a conversation, has been set yet. If not it calls getMemberID. Create getMemberID:

@MainActor
final class ChatViewModel: NSObject, ObservableObject {
    ...
    
    private func getMemberID() async {
        let member = try? await client.getConversationMember(conversationID, memberId: "me")
        memberID = member?.id
        
        if memberID == nil {
            memberID = try? await client.joinConversation(conversationID)
        }
    }
}

This function tries to get the member ID of this user first with getConversationMember, if it fails then it will join the conversation with joinConversation which returns the member ID.

Now that that user is guaranteed to be a member of the conversation, you can get the conversation events using the client. Create a function called getConversationEvents:

@MainActor
final class ChatViewModel: NSObject, ObservableObject {
    ...
    func getConversationEvents() async {
        let params = VGGetConversationEventsParameters(order: .asc, pageSize: 100)
        let eventsPage = try? await client.getConversationEvents(conversationID, parameters: params)
        self.events = eventsPage?.events ?? []
    }
}

getConversationEvents takes a conversation ID and VGGetConversationEventsParameters. The parameters allows you to customize how the events are returned to you. This function returns a paginated response. To learn more about pagination you can read the pagination guide. To display the events, create a helper function which turns the event objects into a display string:

@MainActor
final class ChatViewModel: NSObject, ObservableObject {
    ...
    func generateDisplayText(_ event: VGPersistentConversationEvent) -> (body: String, isUser: Bool) {
        var from = "System"
        
        switch event.kind {
        case .memberJoined:
            let memberJoinedEvent = event as! VGMemberJoinedEvent
            from = memberJoinedEvent.body.user.name
            return ("\(from) joined", false)
        case .memberLeft:
            let memberLeftEvent = event as! VGMemberLeftEvent
            from = memberLeftEvent.body.user.name
            return ("\(from) left", false)
        case .messageText:
            let messageTextEvent = event as! VGMessageTextEvent
            var isUser = false
            
            if let userInfo = messageTextEvent.from as? VGEmbeddedInfo {
                isUser = userInfo.memberId == memberID
                from = isUser ? "" : "\(userInfo.user.name): "
            }
            
            return ("\(from) \(messageTextEvent.body.text)", isUser)
        default:
            return ("", false)
        }
    }
}

The three events that are being used in this tutorial are memberJoined, memberLeft, and messageText. In the case of messageText the function uses the memberID property to determine if the message what sent by the current logged in user. This will allow the UI to pin the users sent messages to the right and received messages to the left.

The VGChatClientDelegate

The application also needs to react to events in a conversation after loading initially so you need to implement the VGChatClientDelegate didReceiveConversationEvent event.

extension ChatViewModel: VGChatClientDelegate {
    nonisolated func chatClient(_ client: VGChatClient, didReceiveConversationEvent event: VGConversationEvent) {
        Task { @MainActor in
            self.events.append(event as! VGPersistentConversationEvent)
        }
    }
    
    nonisolated func client(_ client: VGBaseClient, didReceiveSessionErrorWith reason: VGSessionErrorReason) {}
}

When a new event is received it is appended to the events which is automatically published so the UI will update.

Updating the UI

Now that the ChatViewModel can retrieve and listen for new conversation events, update the view code to display them:

struct ChatView: View {
    @StateObject var chatViewModel: ChatViewModel
    @State private var message: String = ""
    
    var body: some View {
        VStack {
            if chatViewModel.events.isEmpty {
                ProgressView()
            } else {
                VStack {
                    List {
                        ForEach(chatViewModel.events, id: \.id) { event in
                            switch event.kind {
                            case .memberJoined, .memberLeft:
                                let displayText = chatViewModel.generateDisplayText(event)
                                Text(displayText.body)
                                    .frame(maxWidth: .infinity, alignment: .center)
                            case.messageText:
                                let displayText = chatViewModel.generateDisplayText(event)
                                Text(displayText.body)
                                    .frame(maxWidth: .infinity, alignment: displayText.isUser ? .trailing : .leading)
                            default:
                                EmptyView()
                            }
                        }.listRowSeparator(.hidden)
                    }.listStyle(.plain)
                    
                    Spacer()
                    
                    HStack {
                        TextField("Message", text: $message)
                        Button("Send") {
                            Task {
                                
                            }
                        }.buttonStyle(.bordered)
                    }.padding(8)
                }
            }
        }.onAppear {
            Task {
                await chatViewModel.getMemberIDIfNeeded()
                await chatViewModel.getConversationEvents()
            }
        }
    }
}

Now in the List, switch statement has updated to call generateDisplayText for the three different types of conversation events the tutorial is using. There is also a ProgressView for when the ChatViewModel is loading. When the view loads, onAppear is called which then calls the functions you created on the ChatViewModel.

Build and Run

Press Cmd + R to build and run again. After logging in you will see that Alice has joined the conversation:

Chat interface with connection events