# 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](https://github.com/GetStream/stream-chat-android/blob/main/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/event/handler/chat/ChatEventHandler.kt#L50).

<admonition type="note">

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.

</admonition>

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:

<tabs>

<tabs-item value="kotlin" label="Kotlin">

```kotlin
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)
    }
}
```

</tabs-item>

<tabs-item value="java" label="Java">

```java
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());
    }
}
```

</tabs-item>

</tabs>

The second one is for `private` channels updates:

<tabs>

<tabs-item value="kotlin" label="Kotlin">

```kotlin
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)
    }
}
```

</tabs-item>

<tabs-item value="java" label="Java">

```java
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());
    }
}
```

</tabs-item>

</tabs>

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

<tabs>

<tabs-item value="kotlin" label="Kotlin">

```kotlin
val factory = ChannelListViewModelFactory(chatEventHandlerFactory = chatEventHandlerFactory)
```

</tabs-item>

<tabs-item value="java" label="Java">

```java
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
);
```

</tabs-item>

</tabs>

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.

---

This page was last updated at 2025-08-15T14:15:40.787Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/android/client/guides/channel-list-updates/](https://getstream.io/chat/docs/sdk/android/client/guides/channel-list-updates/).