Skip to content

Use cursor-based pagination for search messages#6179

Open
VelikovPetar wants to merge 5 commits intodevelopfrom
feature/AND_1088_use_cursor_based_pagination_for_search_messages
Open

Use cursor-based pagination for search messages#6179
VelikovPetar wants to merge 5 commits intodevelopfrom
feature/AND_1088_use_cursor_based_pagination_for_search_messages

Conversation

@VelikovPetar
Copy link
Contributor

@VelikovPetar VelikovPetar commented Feb 23, 2026

Goal

Replace offset-based pagination with cursor-based pagination for message search to improve reliability and performance.

Resolves: https://linear.app/stream/issue/AND-1088/use-cursor-based-pagination-instead-of-offset-based-pagination-in

Implementation

  • Updated SearchViewModel (UI Components) to use next cursor instead of offset
  • Updated ChannelListViewModel (Compose) message search to use next cursor instead of offset
  • Changed canLoadMore logic to check for non-empty next token instead of comparing message count to limit
  • Added next field to state classes to track the pagination cursor
  • Added unit tests for SearchViewModel
  • Enhanced existing tests for ChannelListViewModel message search

UI Changes

No UI changes.

Testing

  • Run unit tests: ./gradlew :stream-chat-android-ui-components:testDebugUnitTest --tests "*.SearchViewModelTest"
  • Run unit tests: ./gradlew :stream-chat-android-compose:testDebugUnitTest --tests "*.ChannelListViewModelTest"
  • Manual testing: Use the search functionality in the demo app and verify pagination works correctly

Summary by CodeRabbit

Release Notes

  • New Features

    • Message search now uses cursor-based pagination for improved reliability and efficiency when loading additional results from large search sets.
  • Tests

    • Comprehensive test coverage added for message search functionality and pagination workflows to ensure stability and correctness across various search scenarios.

Co-Authored-By: Claude <noreply@anthropic.com>
@VelikovPetar VelikovPetar added the pr:improvement Improvement label Feb 23, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Feb 23, 2026

PR checklist ✅

All required conditions are satisfied:

  • Title length is OK (or ignored by label).
  • At least one pr: label exists.
  • Sections ### Goal, ### Implementation, and ### Testing are filled.

🎉 Great job! This PR is ready for review.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 23, 2026

SDK Size Comparison 📏

SDK Before After Difference Status
stream-chat-android-client 5.26 MB 5.26 MB 0.00 MB 🟢
stream-chat-android-offline 5.48 MB 5.48 MB 0.00 MB 🟢
stream-chat-android-ui-components 10.63 MB 10.63 MB 0.00 MB 🟢
stream-chat-android-compose 12.85 MB 12.85 MB 0.00 MB 🟢

@VelikovPetar VelikovPetar marked this pull request as ready for review February 23, 2026 16:34
@VelikovPetar VelikovPetar requested a review from a team as a code owner February 23, 2026 16:34
@coderabbitai
Copy link

coderabbitai bot commented Feb 23, 2026

Walkthrough

The changes replace offset-based pagination with cursor-based (next-token) pagination across message search functionality in the compose and UI components modules. State models are updated to store and propagate the next cursor, API calls adjusted to use next instead of offset, and test coverage added for new search flows.

Changes

Cohort / File(s) Summary
Channel List Search
stream-chat-android-compose/.../ChannelListViewModel.kt, stream-chat-android-compose/.../ChannelListViewModelTest.kt
Replaced offset-based pagination with next-token pagination in searchMessages. Updated SearchMessageState to include next: String? field, response handling to derive canLoadMore from presence of next cursor, and added test helpers (givenSearchMessagesResult, givenRepositorySelectChannels). Replaced Kluent assertions with JUnit assertions and added message search flow tests.
Search ViewModel
stream-chat-android-ui-components/.../SearchViewModel.kt, stream-chat-android-ui-components/api/stream-chat-android-ui-components.api
Shifted from offset-based to cursor-based pagination using next token. Updated state model with new next: String? field. Changed search result handling to consume SearchMessagesResult instead of List. Updated logging and error handling to reflect new pagination approach. API signature expanded to include new next parameter in State constructor and copy methods.
Search ViewModel Tests
stream-chat-android-ui-components/.../SearchViewModelTest.kt
Introduced comprehensive unit test suite covering search behavior: clearing queries, initiating searches with null offset/next, loading more using next cursor from first page, and preventing load-more when next is absent. Includes Fixture utility with mocked ChatClient, ClientState, and RepositoryFacade for test dependencies.

Poem

🐰 Hop along with cursors now, not offsets long and slow,
Next token guides the search results in a graceful, forward flow,
From compose to UI bright, pagination takes its turn,
With tests to light the rabbit's way—new tricks we proudly earn!

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: replacing offset-based pagination with cursor-based pagination for search messages, which is the primary objective across both ChannelListViewModel and SearchViewModel.
Description check ✅ Passed The description includes Goal, Implementation, UI Changes, and Testing sections. All required core sections are present and adequately filled. The Contributor and Reviewer checklists are unchecked but expected to be completed during review.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/AND_1088_use_cursor_based_pagination_for_search_messages

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/search/SearchViewModel.kt (1)

160-167: Error recovery allows retry — confirm this is intentional.

On failure, canLoadMore and next are preserved (not reset). This means the user can retry the same page by calling loadMore() again, which seems like a reasonable recovery path. Just flagging for confirmation since the old offset-based behavior may have differed here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/search/SearchViewModel.kt`
around lines 160 - 167, The error handler handleSearchMessagesError currently
preserves pagination state (canLoadMore and next), allowing loadMore() retries;
if that is not intended, update handleSearchMessagesError to reset pagination by
copying _state.value with isLoading=false, isLoadingMore=false,
canLoadMore=false and next=null so a failed search does not permit an immediate
retry of the same page; reference handleSearchMessagesError, _state,
canLoadMore, next and loadMore() when making the change.
stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/viewmodel/channels/ChannelListViewModelTest.kt (1)

416-426: Optional: use argumentCaptor<String?>() for the nullable next parameter.

argumentCaptor<String>() works here at runtime (widening StringAny? before assertNull suppresses the null check), but String? matches the actual parameter type of next and makes the captor usage type-safe.

🛠️ Suggested change
-val captor = argumentCaptor<String>()
+val captor = argumentCaptor<String?>()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/viewmodel/channels/ChannelListViewModelTest.kt`
around lines 416 - 426, The test uses argumentCaptor<String>() for the nullable
next parameter in searchMessages which is type-unsafe; replace it with
argumentCaptor<String?>() so the captor's generic matches the actual nullable
parameter type (update the captor declaration and subsequent captor.firstValue /
captor.secondValue assertions to use that captor), referencing the existing
captor variable used in the verify(...) call for searchMessages.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/viewmodel/channels/ChannelListViewModelTest.kt`:
- Around line 529-531: The helper function givenRepositorySelectChannels is
unnecessarily marked suspend even though it only performs synchronous Mockito
stubbing; remove the suspend modifier from its declaration so it becomes a
regular function (match style of other helpers like givenSearchMessagesResult)
and keep the same body that calls
whenever(repositoryFacade.selectChannels(any<List<String>>())) doReturn channels
to stub the repository.

---

Nitpick comments:
In
`@stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/viewmodel/channels/ChannelListViewModelTest.kt`:
- Around line 416-426: The test uses argumentCaptor<String>() for the nullable
next parameter in searchMessages which is type-unsafe; replace it with
argumentCaptor<String?>() so the captor's generic matches the actual nullable
parameter type (update the captor declaration and subsequent captor.firstValue /
captor.secondValue assertions to use that captor), referencing the existing
captor variable used in the verify(...) call for searchMessages.

In
`@stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/search/SearchViewModel.kt`:
- Around line 160-167: The error handler handleSearchMessagesError currently
preserves pagination state (canLoadMore and next), allowing loadMore() retries;
if that is not intended, update handleSearchMessagesError to reset pagination by
copying _state.value with isLoading=false, isLoadingMore=false,
canLoadMore=false and next=null so a failed search does not permit an immediate
retry of the same page; reference handleSearchMessagesError, _state,
canLoadMore, next and loadMore() when making the change.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7abef21 and 4523874.

📒 Files selected for processing (5)
  • stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/channels/ChannelListViewModel.kt
  • stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/viewmodel/channels/ChannelListViewModelTest.kt
  • stream-chat-android-ui-components/api/stream-chat-android-ui-components.api
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/search/SearchViewModel.kt
  • stream-chat-android-ui-components/src/test/kotlin/io/getstream/chat/android/ui/viewmodels/search/SearchViewModelTest.kt

Comment on lines +529 to +531
suspend fun givenRepositorySelectChannels(channels: List<Channel> = emptyList()) = apply {
whenever(repositoryFacade.selectChannels(any<List<String>>())) doReturn channels
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n "fun selectChannels" --type=kt -A 2

Repository: GetStream/stream-chat-android

Length of output: 99


🏁 Script executed:

rg -n "fun selectChannels" -A 2

Repository: GetStream/stream-chat-android

Length of output: 6182


Remove the unnecessary suspend modifier from givenRepositorySelectChannels.

Although RepositoryFacade.selectChannels is a suspend function, the fixture helper only performs mockito-kotlin stubbing via whenever(...) doReturn ..., which is synchronous and does not require a coroutine context. The suspend modifier adds noise and is inconsistent with other fixture helpers like givenSearchMessagesResult. Remove it:

fun givenRepositorySelectChannels(channels: List<Channel> = emptyList()) = apply {
    whenever(repositoryFacade.selectChannels(any<List<String>>())) doReturn channels
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/viewmodel/channels/ChannelListViewModelTest.kt`
around lines 529 - 531, The helper function givenRepositorySelectChannels is
unnecessarily marked suspend even though it only performs synchronous Mockito
stubbing; remove the suspend modifier from its declaration so it becomes a
regular function (match style of other helpers like givenSearchMessagesResult)
and keep the same body that calls
whenever(repositoryFacade.selectChannels(any<List<String>>())) doReturn channels
to stub the repository.

@sonarqubecloud
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:improvement Improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants