Skip to content

Fix mention suggestion filter to align with Swift SDK#6358

Open
andremion wants to merge 5 commits intov7from
fix-default-query-filter
Open

Fix mention suggestion filter to align with Swift SDK#6358
andremion wants to merge 5 commits intov7from
fix-default-query-filter

Conversation

@andremion
Copy link
Copy Markdown
Contributor

@andremion andremion commented Apr 10, 2026

Goal

Fix the mention suggestion filter (@mentions) which returned unrelated users for short queries. For example, typing @jc in a channel with "Aleksandar Apostolov" and "Jc Minarro" showed both users instead of only "Jc Minarro". Aligns the filtering behavior with the Swift SDK.

Implementation

Replace fuzzy Levenshtein filtering with substring matching (DefaultUserQueryFilter):

  • The old DefaultQueryFilter filtered by Levenshtein distance < threshold, which was too permissive for short queries (e.g. distance between "jc" and "al" is 2, below the threshold of 3).
  • Now filters by normalized substring containment and sorts matches by Levenshtein distance so the closest results appear first. This matches the Swift SDK approach.

Merge DefaultQueryFilter into DefaultUserQueryFilter:

  • DefaultQueryFilter<T> was a generic internal class only ever used with User. Merged into DefaultUserQueryFilter (the public class) to eliminate the unnecessary abstraction.
  • Moved DefaultUserQueryFilter from mention package to query.filter package where it belongs alongside QueryFilter.

Reduce visibility of internal classes:

  • DefaultQueryFilter → removed (merged into DefaultUserQueryFilter)
  • Combine, Transliterateinternal (only used by DefaultUserQueryFilter)
  • LocalUserLookupHandlerinternal (no external consumers)

Fix handler defaults:

  • LocalUserLookupHandler and DefaultUserLookupHandler now use DefaultUserQueryFilter() as default instead of creating DefaultQueryFilter directly, ensuring consistent behavior.

Testing

  1. Open a channel with multiple members in the sample app.
  2. Type @ followed by a short query (e.g. @jc, @al, @ale) — verify only users whose name or id contains the typed substring are shown.
  3. Verify that case and diacritics are ignored (e.g. @jose matches "José").
  4. Verify that results are sorted by relevance (exact name match appears before a longer name containing the substring).
  5. Run ./gradlew :stream-chat-android-ui-common:testDebugUnitTest — all unit tests pass.

Summary by CodeRabbit

  • Refactor

    • Reorganized mention filtering implementation into a dedicated package structure for improved code organization.
    • Reduced public API surface by making internal helper classes private to the module.
  • Tests

    • Added comprehensive test coverage for mention filtering logic, including edge cases and matching behavior validation.

@andremion andremion requested a review from a team as a code owner April 10, 2026 13:15
@andremion andremion added the pr:improvement Improvement label Apr 10, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 10, 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.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 10, 2026

Walkthrough

The PR refactors the mention/user query filtering system by moving DefaultUserQueryFilter to a new package location, consolidating filtering logic into a single simplified implementation, making internal implementation classes non-public, and removing generic filtering infrastructure in favor of user-specific filtering. Documentation, sample code, and tests are updated accordingly.

Changes

Cohort / File(s) Summary
Documentation & Sample Import Updates
stream-chat-android-docs/src/main/java/.../MessageComposer.java, stream-chat-android-docs/src/main/kotlin/.../MessageComposer.kt, stream-chat-android-ui-components-sample/src/main/kotlin/.../ChatFragment.kt
Updated imports to reference DefaultUserQueryFilter from the new package location io.getstream.chat.android.ui.common.feature.messages.composer.query.filter.
Query Filter Implementation Consolidation
stream-chat-android-ui-common/src/main/kotlin/.../mention/DefaultUserQueryFilter.kt, stream-chat-android-ui-common/src/main/kotlin/.../query/filter/DefaultUserQueryFilter.kt, stream-chat-android-ui-common/src/main/kotlin/.../query/filter/DefaultQueryFilter.kt
Removed generic DefaultQueryFilter<T> (163 lines) from query/filter package and old DefaultUserQueryFilter from mention package. Added new simplified DefaultUserQueryFilter (100 lines) in query/filter package with updated constructors and integrated Levenshtein distance calculation.
User Lookup Handler Updates
stream-chat-android-ui-common/src/main/kotlin/.../mention/DefaultUserLookupHandler.kt, stream-chat-android-ui-common/src/main/kotlin/.../mention/LocalUserLookupHandler.kt
Changed LocalUserLookupHandler visibility from public to internal and removed @JvmOverloads. Updated default filter in both handlers from inline lambda to DefaultUserQueryFilter().
Formatter Visibility Restrictions
stream-chat-android-ui-common/src/main/kotlin/.../query/formatter/Combine.kt, stream-chat-android-ui-common/src/main/kotlin/.../query/formatter/Transliterate.kt
Reduced visibility from public to internal for Combine and Transliterate formatter classes.
Test Coverage
stream-chat-android-ui-common/src/test/kotlin/.../query/filter/DefaultUserQueryFilterTest.kt
Added comprehensive test suite (94 lines) covering empty queries, case-insensitivity, diacritic handling, substring matching, distance-based sorting, and fallback to user id when name is blank.
API Manifest
stream-chat-android-ui-common/api/stream-chat-android-ui-common.api
Removed exports for old DefaultUserQueryFilter, LocalUserLookupHandler, Combine, Transliterate, and generic DefaultQueryFilter. Added export for renamed DefaultUserQueryFilter with simplified constructor signatures (no-arg and transliterator-only).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Query filters dance in new locations,
Old generic chains dissolve to simplification,
User lookups now lean and bright,
Distance measured true, results sorted right,
Hopping forward with tests in sight! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: replacing fuzzy filtering with substring-based mention suggestions to align with the Swift SDK.
Description check ✅ Passed The description covers Goal, Implementation, and Testing sections comprehensively. Goal explains the problem and intent, Implementation details the refactoring and filtering logic changes, and Testing provides clear verification steps. All critical information is present.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-default-query-filter

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
Copy Markdown

@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

🤖 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-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/query/filter/DefaultUserQueryFilter.kt`:
- Around line 29-37: Update the public KDoc on DefaultUserQueryFilter to
explicitly state its thread expectations and whether it holds internal state:
indicate if instances are thread-safe / can be called from any thread or are
confined to a single thread, and clearly state whether the class is stateless
(no mutable instance fields) or is stateful (documents what state is retained
and how to safely reuse instances). Reference the class name
DefaultUserQueryFilter and its constructor/transliterator parameter in the KDoc
so callers know the thread-safety and reusability guarantees.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 213c439c-918a-4868-b1df-fb5ffcc06498

📥 Commits

Reviewing files that changed from the base of the PR and between a71f41d and af9c9ef.

📒 Files selected for processing (12)
  • stream-chat-android-docs/src/main/java/io/getstream/chat/docs/java/ui/messages/MessageComposer.java
  • stream-chat-android-docs/src/main/kotlin/io/getstream/chat/docs/kotlin/ui/messages/MessageComposer.kt
  • stream-chat-android-ui-common/api/stream-chat-android-ui-common.api
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/mention/DefaultUserLookupHandler.kt
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/mention/DefaultUserQueryFilter.kt
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/mention/LocalUserLookupHandler.kt
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/query/filter/DefaultQueryFilter.kt
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/query/filter/DefaultUserQueryFilter.kt
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/query/formatter/Combine.kt
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/query/formatter/Transliterate.kt
  • stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/query/filter/DefaultUserQueryFilterTest.kt
  • stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/chat/ChatFragment.kt
💤 Files with no reviewable changes (2)
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/mention/DefaultUserQueryFilter.kt
  • stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/query/filter/DefaultQueryFilter.kt

Comment on lines +29 to +37
/**
* Default [QueryFilter] for [User] objects used in mention suggestions.
*
* Keeps only users whose normalized name (or id) contains the normalized query as a substring,
* then sorts results by Levenshtein distance so the closest matches appear first. Normalization
* applies lowercasing, diacritics removal, and optional transliteration.
*
* @param transliterator The transliterator to use for normalizing strings.
*/
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add thread/state notes to the public KDoc.

The class KDoc is good, but it should explicitly state thread expectations and statefulness for this public API.

Proposed KDoc update
 /**
  * Default [QueryFilter] for [User] objects used in mention suggestions.
  *
  * Keeps only users whose normalized name (or id) contains the normalized query as a substring,
  * then sorts results by Levenshtein distance so the closest matches appear first. Normalization
  * applies lowercasing, diacritics removal, and optional transliteration.
+ *
+ * Threading: Stateless and safe to call from any thread.
+ * State: This filter keeps no mutable internal state.
  *
  * `@param` transliterator The transliterator to use for normalizing strings.
  */

As per coding guidelines **/*.kt: Document public APIs with KDoc, including thread expectations and state notes.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Default [QueryFilter] for [User] objects used in mention suggestions.
*
* Keeps only users whose normalized name (or id) contains the normalized query as a substring,
* then sorts results by Levenshtein distance so the closest matches appear first. Normalization
* applies lowercasing, diacritics removal, and optional transliteration.
*
* @param transliterator The transliterator to use for normalizing strings.
*/
/**
* Default [QueryFilter] for [User] objects used in mention suggestions.
*
* Keeps only users whose normalized name (or id) contains the normalized query as a substring,
* then sorts results by Levenshtein distance so the closest matches appear first. Normalization
* applies lowercasing, diacritics removal, and optional transliteration.
*
* Threading: Stateless and safe to call from any thread.
* State: This filter keeps no mutable internal state.
*
* `@param` transliterator The transliterator to use for normalizing strings.
*/
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/query/filter/DefaultUserQueryFilter.kt`
around lines 29 - 37, Update the public KDoc on DefaultUserQueryFilter to
explicitly state its thread expectations and whether it holds internal state:
indicate if instances are thread-safe / can be called from any thread or are
confined to a single thread, and clearly state whether the class is stateless
(no mutable instance fields) or is stateful (documents what state is retained
and how to safely reuse instances). Reference the class name
DefaultUserQueryFilter and its constructor/transliterator parameter in the KDoc
so callers know the thread-safety and reusability guarantees.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 10, 2026

SDK Size Comparison 📏

SDK Before After Difference Status
stream-chat-android-client 5.25 MB 5.82 MB 0.57 MB 🔴
stream-chat-android-ui-components 10.60 MB 10.95 MB 0.36 MB 🟡
stream-chat-android-compose 12.81 MB 12.25 MB -0.56 MB 🚀

…zzy Levenshtein filtering. The previous implementation filtered by Levenshtein distance < threshold, causing short queries like @jc to match unrelated names (e.g. "Aleksandar") because the distance was below the cutoff. Now filters by substring containment and sorts by Levenshtein distance, aligning with the Swift SDK.
…tor it to replace the generic `DefaultQueryFilter`. Update all references and tests to use the new `DefaultUserQueryFilter` implementation.
@andremion andremion force-pushed the fix-default-query-filter branch from af9c9ef to 0bd9efe Compare April 10, 2026 15:47
@sonarqubecloud
Copy link
Copy Markdown

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