Channel List Updates

We update the ChannelList and ChannelListView components based on data from two sources:

  • Query channels request, with given filter and query sort, when the component is initialized.
  • Channel-related events that come from the WebSocket when the socket is connected.

Unlike query channel requests, where you can filter particular channels, channel-related events are not being filtered and need additional attention to handle the list updates correctly. By default, the SDK updates the list based on membership - the channel will be added if the currentUser is a member and will be removed otherwise.

Custom ChatEventHandler

The default event handling logic might not fit into your use case and you can spot that ChannelListView is not updated properly. This means you should provide a custom ChatEventHandler. You can choose between overriding:

  • DefaultChatEventHandler - when you need to extend member-based behavior by adding additional logic.
  • BaseChatEventHandler - when you need to support different use cases, for example, non-member based but still a separation between CidEvent and HasChannel events is useful.
  • ChatEventHandler - if you want to have full control over events’ types.

In each case, you need to decide what to do with the particular event by returning EventHandlingResult that matches an action. You can find more details about the result class here.

The ChatEventHandlerFactory provides a ChatEventHandler, which gives you access to the visible channels map. By using the map, you can skip unwanted channel list updates.

Both XML and Compose ChannelListViewModel and ChannelListViewModelFactory allow you to pass chatEventHandlerFactory via constructor. Alternatively, you can pass a custom handler to ChatClient::queryChannelsAsState function calls, if you are not using our UI SDKs.

Multiple ChannelListView

If your application contains more than one ChannelListView, each of them should implement its own, custom, ChatEventHandler. Let’s say we want to have two ChannelListView:

  • The first displays public channels
  • The second displays private channels

Both channel types require a user to be a member to interact with the channel. As mentioned above, by default, channel-related events are not filtered, so if you would use the default ChatEventHandler, both lists would be updated regardless of the event’s channel type.

You can fix that by providing two different event handlers.

The first one is going to handle public channel updates:

class PublicChatEventHandler(
    channels: StateFlow<Map<String, Channel>?>,
    clientState: ClientState,
) : DefaultChatEventHandler(channels, clientState) {

    override fun handleChannelEvent(event: HasChannel, filter: FilterObject): EventHandlingResult {
        // If the channel event matches "public" type, handle it
        return if (event.channel.cid.startsWith("public")) {
            super.handleChannelEvent(event, filter)
        } else {
            // Otherwise skip
            EventHandlingResult.Skip
        }
    }

    override fun handleCidEvent(
        event: CidEvent,
        filter: FilterObject,
        cachedChannel: Channel?,
    ): EventHandlingResult {
        // If the cid event matches "public" type, handle it
        return if (event.cid.startsWith("public")) {
            super.handleCidEvent(event, filter, cachedChannel)
        } else {
            // Otherwise skip
            EventHandlingResult.Skip
        }
    }
}

class PublicChatEventHandlerFactory : ChatEventHandlerFactory() {
    override fun chatEventHandler(channels: StateFlow<Map<String, Channel>?>): ChatEventHandler {
        return PublicChatEventHandler(channels, ChatClient.instance().clientState)
    }
}

The second one is for private channels updates:

class PrivateChatEventHandler(
    channels: StateFlow<Map<String, Channel>?>,
    clientState: ClientState,
) : DefaultChatEventHandler(channels, clientState) {

    override fun handleChannelEvent(event: HasChannel, filter: FilterObject): EventHandlingResult {
        // If the channel event matches "private" type, handle it
        return if (event.channel.cid.startsWith("private")) {
            super.handleChannelEvent(event, filter)
        } else {
            // Otherwise skip
            EventHandlingResult.Skip
        }
    }

    override fun handleCidEvent(
        event: CidEvent,
        filter: FilterObject,
        cachedChannel: Channel?,
    ): EventHandlingResult {
        // If the cid event matches "private" type, handle it
        return if (event.cid.startsWith("private")) {
            super.handleCidEvent(event, filter, cachedChannel)
        } else {
            // Otherwise skip
            EventHandlingResult.Skip
        }
    }
}

class PrivateChatEventHandlerFactory : ChatEventHandlerFactory() {
    override fun chatEventHandler(channels: StateFlow<Map<String, Channel>?>): ChatEventHandler {
        return PrivateChatEventHandler(channels, ChatClient.instance().clientState)
    }
}

After that, we just need to apply the handlers to the proper ChannelListViewModel:

val factory = ChannelListViewModelFactory(chatEventHandlerFactory = chatEventHandlerFactory)

With this approach, you can make sure each list in your app only receives and handles the events it should be aware of. The public channel list will ignore private channel events and vice-versa.

You can use this for many different use cases, like handling different channel categories, groups, or custom types of metadata that describe your channels.

© Getstream.io, Inc. All Rights Reserved.