Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/stream_chat_flutter_core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
🐞 Fixed

- Fixed `StreamChannel`'s default error state exposing raw error details; it now shows a safe error state (icon, message, and a Try Again button wired to `retry()`) that adapts to the failure type.
- Fixed `StreamChannelListController` crashing with a null-check error when its local sort ran over a channel disposed mid-query (e.g. a client disconnect/logout racing an in-flight `loadMore`); such channels are now sorted last instead.

## 10.2.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,17 @@ class StreamChannelListController extends PagedValueNotifier<int, Channel> {
orElse: () => newValue,
(success) => success.copyWith(
items: success.items.sortedByCompare(
(it) => it.state!.channelState,
channelSort.compare,
// A channel loses its state when it is disposed — e.g. a client
// disconnect/logout or a channel-removal event racing an
// in-flight query — so sort stateless channels last instead of
// null-asserting on them.
(it) => it.state?.channelState,
(a, b) => switch ((a, b)) {
(null, null) => 0,
(null, _) => 1,
(_, null) => -1,
(final a?, final b?) => channelSort.compare(a, b),
},
),
),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,33 @@ void main() {
expect((captured.single as PaginationParams).offset, equals(nextPageKey));
});

test('local sort places channels with disposed state last instead of crashing', () {
ChannelState channelStateFor({required DateTime createdAt}) => ChannelState(
channel: ChannelModel(cid: 'messaging:${createdAt.millisecondsSinceEpoch}', createdAt: createdAt),
);

final older = MockChannel();
when(() => older.state.channelState).thenReturn(channelStateFor(createdAt: DateTime(2026, 1, 1)));

final newer = MockChannel();
when(() => newer.state.channelState).thenReturn(channelStateFor(createdAt: DateTime(2026, 6, 1)));

// A channel disposed while a query is in flight (client disconnect or
// logout, a channel-removal event) has its state nulled out.
final disposed = NonInitializedMockChannel();

final controller = StreamChannelListController(
client: client,
channelStateSort: defaultChannelListSort,
);

expect(
() => controller.value = PagedValue<int, Channel>(items: [disposed, older, newer]),
returnsNormally,
);
expect(controller.value.asSuccess.items, equals([newer, older, disposed]));
});

// The controller's only responsibility for events is routing them to the
// matching handler method. The handler's behavior for each event is covered
// in stream_channel_list_event_handler_test.dart, so here we inject a mock
Expand Down
Loading