Skip to content

Commit 9452ad4

Browse files
xsahil03xclaude
andcommitted
fix(core): truncate channel state before reloading the latest messages
`StreamChannel.reloadChannel` was leaving the previously loaded window in place and merging the latest page on top of it via `channel.query`. After a `loadChannelAtMessage(...)` jump, that left the around-message window glued to the freshly loaded latest 30 — older messages from before the reload were still in `channel.state.messages`, and the "scroll to bottom" FAB landed the user on a state that didn't match a fresh open of the channel. Truncate the existing state before the `_queryAtMessage()` call so a reload behaves like the initial open. Adds two tests: - `truncates the existing window before querying the latest messages` - `queries with no around-anchor (loads the latest page)` Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 508c019 commit 9452ad4

3 files changed

Lines changed: 90 additions & 2 deletions

File tree

packages/stream_chat_flutter_core/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## Upcoming
2+
3+
🐞 Fixed
4+
5+
- Fixed `StreamChannel.reloadChannel` merging the latest page on top of the previously loaded
6+
window instead of replacing it. The reload now matches a fresh open of the channel.
7+
18
## 9.24.0
29

310
✅ Added

packages/stream_chat_flutter_core/lib/src/stream_channel.dart

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -776,8 +776,11 @@ class StreamChannelState extends State<StreamChannel> {
776776
});
777777
}
778778

779-
/// Reloads the channel with latest message
780-
Future<void> reloadChannel() => _queryAtMessage();
779+
/// Reloads the channel with latest messages, replacing the loaded window.
780+
Future<void> reloadChannel() {
781+
channel.state?.truncate();
782+
return _queryAtMessage();
783+
}
781784

782785
Future<void> _maybeInitChannel() async {
783786
// If the channel doesn't have an CID yet, it hasn't been created on the

packages/stream_chat_flutter_core/test/stream_channel_test.dart

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -908,4 +908,82 @@ void main() {
908908
},
909909
);
910910
});
911+
912+
group('reloadChannel', () {
913+
final mockChannel = MockChannel();
914+
tearDownAll(mockChannel.dispose);
915+
916+
setUp(() {
917+
when(() => mockChannel.cid).thenReturn('test:channel');
918+
when(() => mockChannel.state.messages).thenReturn([]);
919+
when(() => mockChannel.state.isUpToDate).thenReturn(true);
920+
when(() => mockChannel.state.unreadCount).thenReturn(0);
921+
when(() => mockChannel.state.truncate()).thenAnswer((_) {});
922+
when(
923+
() => mockChannel.query(
924+
preferOffline: any(named: 'preferOffline'),
925+
messagesPagination: any(named: 'messagesPagination'),
926+
),
927+
).thenAnswer((_) async => const ChannelState());
928+
});
929+
930+
tearDown(() => reset(mockChannel));
931+
932+
Future<StreamChannelState> _pumpStreamChannel(WidgetTester tester) async {
933+
StreamChannelState? channelState;
934+
await tester.pumpWidget(
935+
MaterialApp(
936+
home: Scaffold(
937+
body: StreamChannel(
938+
channel: mockChannel,
939+
child: Builder(
940+
builder: (context) {
941+
channelState = StreamChannel.of(context);
942+
return const Text('Channel Content');
943+
},
944+
),
945+
),
946+
),
947+
),
948+
);
949+
await tester.pumpAndSettle();
950+
return channelState!;
951+
}
952+
953+
testWidgets(
954+
'truncates the existing window before querying the latest messages',
955+
(tester) async {
956+
final streamChannel = await _pumpStreamChannel(tester);
957+
958+
await streamChannel.reloadChannel();
959+
960+
verifyInOrder([
961+
() => mockChannel.state.truncate(),
962+
() => mockChannel.query(
963+
preferOffline: any(named: 'preferOffline'),
964+
messagesPagination: any(named: 'messagesPagination'),
965+
),
966+
]);
967+
},
968+
);
969+
970+
testWidgets(
971+
'queries with no around-anchor (loads the latest page)',
972+
(tester) async {
973+
final streamChannel = await _pumpStreamChannel(tester);
974+
975+
await streamChannel.reloadChannel();
976+
977+
final captured = verify(
978+
() => mockChannel.query(
979+
preferOffline: any(named: 'preferOffline'),
980+
messagesPagination: captureAny(named: 'messagesPagination'),
981+
),
982+
).captured.single as PaginationParams;
983+
984+
expect(captured.idAround, isNull);
985+
expect(captured.createdAtAround, isNull);
986+
},
987+
);
988+
});
911989
}

0 commit comments

Comments
 (0)