Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to create a new channel with stream_chat_flutter 9.x? #2080

Open
mauriziopinotti opened this issue Jan 11, 2025 · 6 comments
Open

How to create a new channel with stream_chat_flutter 9.x? #2080

mauriziopinotti opened this issue Jan 11, 2025 · 6 comments

Comments

@mauriziopinotti
Copy link

With stream_chat_flutter 8.x I used to create channels following the example app:

// in initState()
_channel = StreamChat.of(context).client.channel('messaging');

// then after the user selects the members for the new channel
  _channel = chatState.client.channel('messaging', extraData: {
    'members': [
      ..._selectedUsers.map((e) => e.id),
      chatState.currentUser!.id,
    ],
    'team': myteam,
  });

However, this no longer works with 9.x, I get

StreamChatNetworkError(code: 4, message: GetOrCreateChannel failed with error: "When using member based IDs specify at least 2 members", statusCode: 400, data: ErrorResponse(code: 4, message: GetOrCreateChannel failed with error: "When using member based IDs specify at least 2 members", statusCode: 400,

The example app does not have a create feature, how should channels be created in 9.x?

@szechyjs
Copy link

I too am having the issue. For me it is happening on the load of the new chat page, before I even call the api to create a new channel. It looks like the root is coming from loading an empty channel?

The create channel code is essentially copied directly from https://github.com/GetStream/flutter-samples/blob/develop/packages/stream_chat_v1/lib/pages/new_chat_screen.dart perhaps updating that to the latest version will solve this issue.

[ERROR:flutter/runtime/dart_vm_initializer.cc(40)] Unhandled Exception: StreamChatNetworkError(code: 4, message: GetOrCreateChannel failed with error: "When using member based IDs specify at least 2 members", statusCode: 400, data: ErrorResponse(code: 4, message: GetOrCreateChannel failed with error: "When using member based IDs specify at least 2 members", statusCode: 400, moreInfo: https://getstream.io/chat/docs/api_errors_response))
#0      StreamHttpClient.post (package:stream_chat/src/core/http/stream_http_client.dart:150:7)
<asynchronous suspension>
#1      ChannelApi.queryChannel (package:stream_chat/src/core/api/channel_api.dart:35:22)
<asynchronous suspension>
#2      Channel.query (package:stream_chat/src/client/channel.dart:1662:24)
<asynchronous suspension>

@mauriziopinotti
Copy link
Author

mauriziopinotti commented Jan 13, 2025

For anyone stumbling upon this issue, I have sort of fixed mine by changing the way I create new channels.

In 8.x I used to follow the official example that does something like

  // Create channel in initState()
  @override
  void initState() {
    super.initState();
    _channel = StreamChat.of(context).client.channel('messaging');
  }

  // Build the screen with StreamChannel
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: _buildAppBar(),
      body: StreamConnectionStatusBuilder(statusBuilder: (context, status) {
        final (statusString, showMessage) = _statusMessage(status);
        return StreamInfoTile(
            showMessage: showMessage,
            tileAnchor: Alignment.topCenter,
            childAnchor: Alignment.topCenter,
            message: statusString,
            textStyle: snackBarStyle,
            child: StreamChannel(
              showLoading: false,
              channel: _channel!,
              child: _buildBody(),
            ));
      }),
    );
  }

Now with 9.x I conditionally use StreamChannel only after creating the channel:

  // Do not create the channel in initState()
  @override
  void initState() {
    super.initState();
  }

  // Only use StreamChannel when the channel is created:
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: _buildAppBar(),
      body: StreamConnectionStatusBuilder(statusBuilder: (context, status) {
        final (statusString, showMessage) = _statusMessage(status);
        return StreamInfoTile(
          showMessage: showMessage,
          tileAnchor: Alignment.topCenter,
          childAnchor: Alignment.topCenter,
          message: statusString,
          textStyle: snackBarStyle,
          child: _channel != null
              ? StreamChannel(
                  showLoading: false,
                  channel: _channel!,
                  child: _buildBody(),
                )
              : _buildBody(),
        );
      }),
    );
  }

@TheAppCrafter
Copy link

I'm having the same issue. After some investigation it seems this is due to changes in the stream_channel.dart file. In stream_chat_flutter 9.x if (channel.state == null) channel.watch(); is being called in initState now. There are many cases where you want to display a widget for a channel and modify the channel before initializing it with the stream chat backend such as a new chat page. This seems like a bug.. If this is intended behavior please advise us on how we should be editing and creating new channels locally before it's initialized with the stream chat backend.

@xsahil03x
Copy link
Member

xsahil03x commented Jan 17, 2025

Hey @TheAppCrafter , ideally the channel should be created before passing it down to the StreamChannel widget. Can you share a flow where you think it's not valid and how allowing a non-initialized channel fixes it?

@TheAppCrafter
Copy link

@xsahil03x Here is an example from a sample app where you would want to edit the channel before initialization with the stream chat backend: https://github.com/GetStream/flutter-samples/blob/develop/packages/stream_chat_v1/lib/pages/new_chat_screen.dart

On this new chat screen we want to allow the user to edit the members of the chat before initializing the channel. Here the _messageInputFocusNode listener updates the channel members whenever a user is added to the chat.

    _messageInputFocusNode.addListener(() async {
      if (_messageInputFocusNode.hasFocus && _selectedUsers.isNotEmpty) {
        final chatState = StreamChat.of(context);

        final res = await chatState.client.queryChannelsOnline(
          state: false,
          watch: false,
          filter: Filter.raw(value: {
            'members': [
              ..._selectedUsers.map((e) => e.id),
              chatState.currentUser!.id,
            ],
            'distinct': true,
          }),
          messageLimit: 0,
          paginationParams: const PaginationParams(
            limit: 1,
          ),
        );

        final channelExisted = res.length == 1;
        if (channelExisted) {
          channel = res.first;
          await channel!.watch();
        } else {
          channel = chatState.client.channel(
            'messaging',
            extraData: {
              'members': [
                ..._selectedUsers.map((e) => e.id),
                chatState.currentUser!.id,
              ],
            },
          );
        }

        setState(() {
          _showUserList = false;
        });
      }
    });

Channel initialization is done when the user decides to send the first message in the channel in the preMessageSending function:

StreamMessageInput(
  focusNode: _messageInputFocusNode,
  preMessageSending: (message) async {
    await channel!.watch();
    return message;
  },
  onMessageSent: (m) {
    GoRouter.of(context).goNamed(
      Routes.CHANNEL_PAGE.name,
      pathParameters: Routes.CHANNEL_PAGE.params(channel!),
    );
  },
),

Delaying channel initialization allows you to also change other channel settings and validate any extraData you are including with your channel before initializing.

Initializing a channel by calling channel.watch() right away will lead to more API calls to the backend since you will have to update the channel with new members/new settings after the channel has been initialized (2 api calls instead of 1). Also, it makes sense to only create a new channel if the user has decided to send a chat in the channel. Many "phantom" channels will created in the backend if it is initialized automatically in initState.

In my app, I have a new chat screen as the root page. This means that every time a user simply opens the app or backs out of the newChat screen without sending a message, a new channel will be created in the backend.

@xsahil03x
Copy link
Member

Hey @TheAppCrafter , I will try out the code you shared once i get some free time and let you know how it goes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants