Kotlin

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 ConversationEvent 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:

class ChatViewModel(application: Application) : AndroidViewModel(application = application) {
    private var memberID = "" 
    ...
    
    suspend fun getMemberIDIfNeeded(){
        if(memberID.isNotEmpty()) return else 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:

class ChatViewModel(application: Application) : AndroidViewModel(application = application) {
    ...
    
    private suspend fun getMemberID() {
        try {
            val member = client.getConversationMember(conversationID,  "me")
            memberID = member.id
        }
        catch (e: VGError) {
            //User not yet a member of the conversation
            memberID = client.joinConversation(conversationID)
        }
        catch (err:Error) {
            isError = true
            error = err.localizedMessage?.toString() ?: ""
        }
    }
}

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:

class ChatViewModel(application: Application) : AndroidViewModel(application = application) {
    ...
    suspend fun getConversationEvents(){
        val params = GetConversationEventsParameters(PresentingOrder.ASC,100)
        try {
            val eventsPage = client.getConversationEvents(conversationID, params)
            events.clear()
            events.addAll(eventsPage.events.toMutableStateList())
        }
        catch (err:Error) {
            isError = true
            error = err.localizedMessage?.toString() ?: ""
        }
    }
}

getConversationEvents takes a conversation ID and GetConversationEventsParameters. 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:

class ChatViewModel(application: Application) : AndroidViewModel(application = application) {
    ...
    fun generateDisplayedText(event: PersistentConversationEvent): Pair<String, Boolean>{
        var from = "System"
        return when(event){
            is MemberJoinedConversationEvent -> {
                val from = event.body.user.name
                "$from joined" to false
            }
            is MemberLeftConversationEvent -> {
                val from = event.body.user.name
                "$from left" to false
            }
            is MessageTextEvent -> {
                var isUser = false
                val userInfo = event.from as EmbeddedInfo
                isUser = userInfo.memberId == memberID
                from = if(isUser) "" else "${userInfo.user.name}: "
                "$from ${event.body.text}" to isUser
            }
            else -> {
                "" to false
            }
        }
    }
}

The three events that are being used in this tutorial are MemberJoinedConversationEvent, MemberLeftConversationEvent, and MessageTextEvent. In the case of MessageTextEvent 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 ConversationEventListener

The application also needs to react to events in a conversation after loading initially so you need to implement a ConversationEventListener. Update your login method to set this listener once a session is created like:

    fun login(username: String) {
        val jwt = if(username == "Alice") aliceJwt else bobJwt
        client.createSession(jwt) { err, sessionId ->
            when {
                err != null -> {
                    isError = true
                    error = err.localizedMessage?.toString() ?: ""
                }
                else -> {
                    client.setOnConversationEventListener {
                        events.add(it as PersistentConversationEvent)
                    }
                    isLoggedIn = true
                }
            }
        }
    }

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, in MainActivity.kt Update the ChatScreen Composable:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ChatScreen() {
    val vm = LocalChatState.current
    var text by remember { mutableStateOf("") }

    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Bottom
    ) {
        LazyColumn(
            modifier = Modifier.fillMaxWidth()
        ) {
            items(vm.events) { event ->
                val (text, isUser) = vm.generateDisplayedText(event)
                val textAlightment = if (isUser) TextAlign.Right else TextAlign.Left
                val textColor = if (isUser) Color.Blue else Color.Black
                Text(
                    text = text,
                    fontSize = 20.sp,
                    textAlign = textAlightment,
                    color = textColor
                )
            }
        }
        Row(){
            TextField(
                value = text,
                onValueChange = { text = it },
                label = { Text("Message") }
            )
            Button(onClick = {}
            }) {
                Text("Send")
            }
        }
    }

    runBlocking {
        vm.getMemberIDIfNeeded()
        vm.getConversationEvents()
    }
}

Now the LazyColumn will take the event list and generate a Text element for each, here we call generateDisplayText to get the text to display, and if that event is owned by the logged in user or not. Finally we add a runBlocking block to get the member id and then get the conversation events when the view first loads.