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)
}
}
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 betweenCidEvent
andHasChannel
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:
public final class PublicChatEventHandler extends DefaultChatEventHandler {
public PublicChatEventHandler(@NonNull StateFlow<? extends Map<String, Channel>> channels, @NonNull ClientState clientState) {
super(channels, clientState);
}
@NonNull
@Override
public EventHandlingResult handleChannelEvent(@NonNull HasChannel event, @NonNull FilterObject filter) {
// If the channel event matches "public" type, handle it
if (event.getChannel().getCid().startsWith("public")) {
return super.handleChannelEvent(event, filter);
} else {
// Otherwise skip
return EventHandlingResult.Skip.INSTANCE;
}
}
@NonNull
@Override
public EventHandlingResult handleCidEvent(@NonNull CidEvent event, @NonNull FilterObject filter, @Nullable Channel cachedChannel) {
// If the channel event matches "public" type, handle it
if (event.getCid().startsWith("public")) {
return super.handleCidEvent(event, filter, cachedChannel);
} else {
// Otherwise skip
return EventHandlingResult.Skip.INSTANCE;
}
}
}
public final class PublicChatEventHandlerFactory extends ChatEventHandlerFactory {
@NonNull
@Override
public ChatEventHandler chatEventHandler(@NonNull StateFlow<? extends Map<String, Channel>> channels) {
return new PublicChatEventHandler(channels, ChatClient.instance().getClientState());
}
}
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)
}
}
public final class PrivateChatEventHandler extends DefaultChatEventHandler {
public PrivateChatEventHandler(@NonNull StateFlow<? extends Map<String, Channel>> channels, @NonNull ClientState clientState) {
super(channels, clientState);
}
@NonNull
@Override
public EventHandlingResult handleChannelEvent(@NonNull HasChannel event, @NonNull FilterObject filter) {
// If the channel event matches "private" type, handle it
if (event.getChannel().getCid().startsWith("private")) {
return super.handleChannelEvent(event, filter);
} else {
// Otherwise skip
return EventHandlingResult.Skip.INSTANCE;
}
}
@NonNull
@Override
public EventHandlingResult handleCidEvent(@NonNull CidEvent event, @NonNull FilterObject filter, @Nullable Channel cachedChannel) {
// If the channel event matches "private" type, handle it
if (event.getCid().startsWith("private")) {
return super.handleCidEvent(event, filter, cachedChannel);
} else {
// Otherwise skip
return EventHandlingResult.Skip.INSTANCE;
}
}
}
public final class PrivateChatEventHandlerFactory extends ChatEventHandlerFactory {
@NonNull
@Override
public ChatEventHandler chatEventHandler(@NonNull StateFlow<? extends Map<String, Channel>> channels) {
return new PrivateChatEventHandler(channels, ChatClient.instance().getClientState());
}
}
After that, we just need to apply the handlers to the proper ChannelListViewModel
:
val factory = ChannelListViewModelFactory(chatEventHandlerFactory = chatEventHandlerFactory)
FilterObject filter = null;
QuerySorter<Channel> sort = ChannelListViewModel.DEFAULT_SORT;
int limit = 30;
int messageLimit = 1;
int memberLimit = 30;
ChannelListViewModelFactory factory = new ChannelListViewModelFactory(
filter,
sort,
limit,
messageLimit,
memberLimit,
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.