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'),
Filter.in_(
'members',
[
StreamChatCore.of(context).currentUser!.id,
],
),
]),
);
...
}Channel State & Filtering
StreamChannelListController manages fetching, filtering, sorting, and real-time updates for a list of channels. It is required by StreamChannelListView and automatically responds to Stream Chat events to keep the channel list up to date. See the pub.dev documentation for the full API reference.
Background
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.
Make sure you call channelListController.doInitialLoad() to load the initial data and channelListController.dispose() when the controller is no longer required.
@override
void initState() {
channelListController.doInitialLoad();
super.initState();
}
@override
void dispose() {
channelListController.dispose();
super.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.
@override
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) {
channelListController.loadMore(nextPageKey);
}
},
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: () {
channelListController.retry();
},
child: Text(error.message),
);
}
return const CircularProgressIndicator();
}
final _item = channels[index];
return ListTile(
title: Text(_item.name ?? ''),
subtitle: StreamBuilder<Message?>(
stream: _item.state!.lastMessageStream,
initialData: _item.state!.lastMessage,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!.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.
Navigator.of(context).push(
MaterialPageRoute(
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.
Filtering and Sorting
StreamChannelListView uses StreamChannelListController to fetch the list of channels matching your query and to stay up-to-date with all changes.
Here's an example on how to create a StreamChannelListController and pass it to the StreamChannelListView, configuring your own query and sorting criteria:
final controller = StreamChannelListController(
client: StreamChat.of(context).client,
filter: Filter.and([
Filter.equal('type', 'messaging'),
Filter.in_('members', [StreamChat.of(context).currentUser!.id]),
]),
channelStateSort: const [SortOption<ChannelState>.asc('last_message_at')],
limit: 10,
);In the example, we're changing the sorting in the opposite order — getting the oldest channels first based on last_message_at.
Filter
filter is the main parameter for a query. You can define different filters to fetch different sets of channels for a user.
Examples of some most commonly used filters:
// Assume we've already created and configured our StreamChatClient
// Filter for channels where our user is a member
final filter = Filter.in_('members', [client.state.currentUser!.id]);
// Filter for channels where the name starts with "Group"
final filter = Filter.autoComplete('name', 'Group');
// Compound filter for channels where team is "red" and our user is a member
final filter = Filter.and([
Filter.equal('team', 'red'),
Filter.in_('members', [client.state.currentUser!.id]),
]);Sorting
channelStateSort is used to sort the list of channels returned. By default, the channel list will be sorted by their last message date (or channel creation date, if the channel is empty).
Most commonly, you don't need to specify any sorting, the SDK handles this. If you'd like, you can create a custom sorting, such as:
// Sorting for always showing most crowded channels first
final sort = const [
SortOption<ChannelState>.desc('member_count'),
SortOption<ChannelState>.desc('last_message_at'),
];Page Size
limit is used to specify how many channels the initial page will show. You can specify an integer value for advanced use-cases. Most commonly, you don't need to touch this.
Message Limit
messageLimit is used to specify how many messages the initial fetch will return per channel. When null (the default), the backend uses its own default.
Member Limit
memberLimit is used to specify how many members are included per channel in the response. When null (the default), the backend uses its own default.
Using Predefined Filters
For frequently used queries, reference a predefined filter created in the Stream Dashboard instead of constructing a Filter on the client. The filter and sort templates are stored server-side; you pass values for any placeholders via filterValues (and sortValues for templated sort fields). When predefinedFilter is set, the query's filter and channelStateSort are ignored. See Querying Channels for more details.
// Reference a predefined filter by name and substitute its placeholder values.
final controller = StreamChannelListController(
client: StreamChat.of(context).client,
predefinedFilter: 'user_messaging_channels',
filterValues: {'user_id': StreamChat.of(context).currentUser!.id},
);
// If the predefined filter templates a sort field, supply it via `sortValues`.
final teamController = StreamChannelListController(
client: StreamChat.of(context).client,
predefinedFilter: 'team_channels',
filterValues: {'team_id': 'engineering'},
sortValues: {'sort_field': 'last_message_at'},
);