# Channel

`StreamChannel` is an `InheritedWidget` that provides a `Channel` instance to its subtree. Every screen that shows channel content (message list, header, composer) must be wrapped in a `StreamChannel`.

Find the pub.dev documentation [here](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChannel-class.html)

### Setting up a channel screen

The typical pattern is to wrap the destination page with `StreamChannel` when navigating from a channel list:

```dart
StreamChannelListView(
  controller: _controller,
  onChannelTap: (channel) => Navigator.push(
    context,
    MaterialPageRoute(
      builder: (_) => StreamChannel(
        channel: channel,
        child: const ChannelPage(),
      ),
    ),
  ),
)
```

Inside `ChannelPage`, all Stream widgets automatically read the channel from `StreamChannel.of(context)`:

```dart
class ChannelPage extends StatelessWidget {
  const ChannelPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: const StreamChannelHeader(),
      body: Column(
        children: [
          const Expanded(child: StreamMessageListView()),
          const StreamMessageComposer(),
        ],
      ),
    );
  }
}
```

### Accessing the channel in custom widgets

Use `StreamChannel.of(context)` to read the current channel anywhere inside the subtree:

```dart
final channel = StreamChannel.of(context).channel;
final state = channel.state; // ChannelClientState
```

`Channel.state` (a `ChannelClientState`) is where reactive data lives, exposing unread counts, pinned messages, typing users, and more.

### Initial message loading

By default, `StreamChannel` loads the most recent messages when it mounts. Pass `initialMessageId` to load the channel starting at a specific message (for example, when navigating from a push notification):

```dart
StreamChannel(
  channel: channel,
  initialMessageId: notification.messageId,
  child: const ChannelPage(),
)
```

### Default constructor vs `StreamChannel.value`

`StreamChannel` ships two constructors with different semantics:

- **`StreamChannel(...)`** — the default. Initializes the channel and **positions the loaded message window** on mount (jumping to `initialMessageId`, the last-read marker, or the latest message). Use this on the main channel-page route.
- **`StreamChannel.value(...)`** — provides the same `Channel` to the subtree without repositioning the loaded window. Use this when wrapping a sub-route or overlay just for context access — for example a thread page, channel info screen, long-press modal, attachment viewer, or any nested route that should not re-run channel positioning and overwrite what the parent route already loaded.

```dart
// Main channel route — positions the window.
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (_) => StreamChannel(
      channel: channel,
      child: const ChannelPage(),
    ),
  ),
);

// Sub-route that needs the same channel context but should not reposition.
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (_) => StreamChannel.value(
      channel: channel,
      child: const ChannelInfoPage(),
    ),
  ),
);
```

### Composing a channel screen

Build your channel screen by combining `StreamChannelHeader`, `StreamMessageListView`, and `StreamMessageComposer` inside a `StreamChannel` ancestor. Each widget is independent, so you can lay them out however your design requires and swap individual parts via the typed builders and `componentBuilders` slots described on the linked component pages.

### Threads

Thread pages should also be wrapped in a dedicated `StreamChannel` (or use `StreamMessageListView`'s built-in thread support via the `parentMessage` parameter). The composition mirrors the regular channel page — `StreamThreadHeader` instead of `StreamChannelHeader`, and `StreamMessageListView(parentMessage: parent)` to scope the list:

```dart
class ThreadPage extends StatefulWidget {
  const ThreadPage({super.key, required this.parent});

  final Message parent;

  @override
  State<ThreadPage> createState() => _ThreadPageState();
}

class _ThreadPageState extends State<ThreadPage> {
  late final StreamMessageComposerController _composerController;

  @override
  void initState() {
    super.initState();
    // Seeding the controller with parentId puts the composer in thread mode —
    // outgoing messages are posted as replies to widget.parent.
    _composerController = StreamMessageComposerController(
      message: Message(parentId: widget.parent.id),
    );
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: StreamThreadHeader(parent: widget.parent),
      body: Column(
        children: [
          Expanded(child: StreamMessageListView(parentMessage: widget.parent)),
          StreamMessageComposer(messageComposerController: _composerController),
        ],
      ),
    );
  }
}
```

See the [Thread List](/chat/docs/sdk/flutter/stream-chat-flutter/stream-thread-list-view/) documentation for details on listing threads.


---

This page was last updated at 2026-06-09T15:44:03.728Z.

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/flutter/stream-chat-flutter/channel/](https://getstream.io/chat/docs/sdk/flutter/stream-chat-flutter/channel/).