Message Search State

StreamMessageSearchListController manages search queries, pagination, and result state for cross-channel message search. It is required by StreamMessageSearchListView and can also be used to build fully custom search UIs. See the pub.dev documentation for the full API reference.

Background

The StreamMessageSearchListController is a controller class that allows you to control a list of searched messages. StreamMessageSearchListController is a required parameter of the StreamMessageSearchListView widget. Check the StreamMessageSearchListView documentation to read more about that.

Basic Example

Building a custom message search feature is a common task. Here is an example of how to use the StreamMessageSearchListController to build a simple search list with pagination.

First of all we should create an instance of the StreamMessageSearchListController and provide it with the StreamChatClient instance. We can then add a filter to only get the channels that the current user is a part of. You can also add a list of SortOptions and other pagination-related parameters.

class SearchListPageState extends State<SearchListPage> {
  /// Controller used for loading more data and controlling pagination in
  /// [StreamMessageSearchListController].
  late final messageSearchListController = StreamMessageSearchListController(
    client: StreamChatCore.of(context).client,
    filter: Filter.in_('members', [StreamChat.of(context).currentUser!.id]),
  );

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

@override
void initState() {
  messageSearchListController.doInitialLoad();
  super.initState();
}

@override
void dispose() {
  messageSearchListController.dispose();
  super.dispose();
}

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

@override
Widget build(BuildContext context) => Scaffold(
      body: Column(
        children: [
          TextField(
            /// This is just a sample implementation of a search field.
            /// In a real-world app you should throttle the search requests.
            /// You can use our library [rate_limiter](https://pub.dev/packages/rate_limiter).
            onChanged: (s) {
              messageSearchListController..searchQuery = s..doInitialLoad();
            },
          ),
          Expanded(
            child: PagedValueListenableBuilder<String, GetMessageResponse>(
              valueListenable: messageSearchListController,
              builder: (context, value, child) {
                return value.when(
                  (responses, nextPageKey, error) => LazyLoadScrollView(
                    onEndOfPage: () async {
                      if (nextPageKey != null) {
                        messageSearchListController.loadMore(nextPageKey);
                      }
                    },
                    child: ListView.builder(
                      /// We're using the responses 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)
                          ? responses.length + 1
                          : responses.length,
                      itemBuilder: (BuildContext context, int index) {
                        if (index == responses.length) {
                          if (error != null) {
                            return TextButton(
                              onPressed: () {
                                messageSearchListController.retry();
                              },
                              child: Text(error.message),
                            );
                          }
                          return const CircularProgressIndicator();
                        }

                        final _item = responses[index];
                        return ListTile(
                          title: Text(_item.channel?.name ?? ''),
                          subtitle: Text(_item.message.text ?? ''),
                        );
                      },
                    ),
                  ),
                  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.