Skip to main content
Version: v5


A Widget For Controlling A List Of Channels

Find the documentation here


The StreamChannelListController is a controller class that allows you to control a list of channels. StreamChannelListController is a required parameter of the StreamChannelListView widget. Check the StreamChannelListView documentation to read more about that.

The StreamChannelListController also listens for various events and manipulates the current list of channels accordingly. Passing a StreamChannelListEventHandler to the StreamChannelListController will allow you to customize this behaviour.

Basic Example

Building a custom channel list is a very common task. Here is an example of how to use the StreamChannelListController to build a simple list with pagination.

First of all we should create an instance of the StreamChannelListController and provide it with the StreamChatClient instance. You can also add a Filter, a list of SortOptions and other pagination-related parameters.

class _MyChannelListPageState extends State<MyChannelListPage> {
/// Controller used for loading more data and controlling pagination in
/// [StreamChannelListController].
late final channelListController = StreamChannelListController(
client: StreamChatCore.of(context).client,
filter: Filter.and([
Filter.equal('type', 'messaging'),

Make sure you call channelListController.doInitialLoad() to load the initial data and channelListController.dispose() when the controller is no longer required.

void initState() {

void dispose() {

The StreamChannelListController is basically a PagedValueNotifier that notifies you when the list of channels has changed. You can use a PagedValueListenableBuilder to build your UI depending on the latest channels.

Widget build(BuildContext context) => Scaffold(
body: PagedValueListenableBuilder<int, Channel>(
valueListenable: channelListController,
builder: (context, value, child) {
return value.when(
(channels, nextPageKey, error) => LazyLoadScrollView(
onEndOfPage: () async {
if (nextPageKey != null) {
child: ListView.builder(
/// We're using the channels length when there are no more
/// pages to load and there are no errors with pagination.
/// In case we need to show a loading indicator or and error
/// tile we're increasing the count by 1.
itemCount: (nextPageKey != null || error != null)
? channels.length + 1
: channels.length,
itemBuilder: (BuildContext context, int index) {
if (index == channels.length) {
if (error != null) {
return TextButton(
onPressed: () {
child: Text(error.message),
return const CircularProgressIndicator();

final _item = channels[index];
return ListTile(
title: Text( ?? ''),
subtitle: StreamBuilder<Message?>(
stream: _item.state!.lastMessageStream,
initialData: _item.state!.lastMessage,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(!.text!);

return const SizedBox();
onTap: () {
/// Display a list of messages when the user taps on
/// an item. We can use [StreamChannel] to wrap our
/// [MessageScreen] screen with the selected channel.
/// This allows us to use a built-in inherited widget
/// for accessing our `channel` later on.
builder: (context) => StreamChannel(
channel: _item,
child: const MessageScreen(),
loading: () => const Center(
child: SizedBox(
height: 100,
width: 100,
child: CircularProgressIndicator(),
error: (e) => Center(
child: Text(
'Oh no, something went wrong. '
'Please check your config. $e',

In this case we're using the LazyLoadScrollView widget to load more data when the user scrolls to the bottom of the list.

Did you find this page helpful?