Skip to content

fix(ui, core): decreased cognitive complexity - #2879

Open
renefloor wants to merge 2 commits into
masterfrom
fix/dart-complexity
Open

fix(ui, core): decreased cognitive complexity#2879
renefloor wants to merge 2 commits into
masterfrom
fix/dart-complexity

Conversation

@renefloor

@renefloor renefloor commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Submit a pull request

Linear: FLU-480

CLA

  • I have signed the Stream CLA (required).
  • The code changes follow best practices
  • Code changes are tested (add some information if not applicable)

Description of the pull request

I made this PR by running the cognitive_complexity plugin from Kevin Moore.
See also: https://x.com/CFDevelop/status/2084725359942250776

This PR also adds a github action step in the analysis workflow to make sure new features don't have a big complexity.

The PR tried to address the complexity of the most important and most complex widget, the MessageListView.
This was the result:

Actionable hotspots (hand-written)

Scanned with dart run cognitive_complexity@0.2.3 --threshold 15 over all package
lib/ roots. Production target: score <= 15.

# Score Declaration Location Strategy Tests
1 107 _StreamMessageListViewState._buildListView packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart:591 A + Tier 2 yes
2 46 StreamMessageActionsBuilder.buildActions packages/stream_chat_flutter/lib/src/message_action/message_actions_builder.dart:54 Tier 2 (grouped helpers) — low value yes
3 41 _PagedValueGridViewState.build packages/stream_chat_flutter_core/lib/src/paged_value_scroll_view.dart:651 A (state -> switch) partial
4 38 LoggingInterceptor._printPrettyMap packages/stream_chat/lib/src/core/http/interceptor/logging_interceptor.dart:245 B + E none
5 33 DefaultStreamMessageItem.build packages/stream_chat_flutter/lib/src/message_widget/stream_message_item.dart:453 Tier 2 (extract sub-widgets) yes
6 30 streamChatComponentBuilders packages/stream_chat_flutter/lib/src/components/stream_chat_component_builders.dart:4 false positive — see below n/a
7 29 _PagedValueListViewState.build packages/stream_chat_flutter_core/lib/src/paged_value_scroll_view.dart:290 A (same shape as #3) none
8 29 tabbedAttachmentPickerBuilder packages/stream_chat_flutter/lib/src/message_input/attachment_picker/stream_attachment_picker.dart:369 A (tab table) yes
9 28 StreamPollController.validateGranularly packages/stream_chat_flutter_core/lib/src/stream_poll_controller.dart:114 B (guard clauses) yes
10 28 FloatingDateDivider._floatingDividerOpacity packages/stream_chat_flutter/lib/src/message_list_view/floating_date_divider.dart:98 Tier 1 (record) yes
11 27 _LazyLoadScrollViewState._onNotification packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart:61 A (notification switch) yes
12 26 Channel._uploadAttachments packages/stream_chat/lib/src/client/channel.dart:611 E (loop-body signal) yes
13 23 Channel.query packages/stream_chat/lib/src/client/channel.dart:2101 B yes

Tail (15–27, 20 more declarations) is mostly Flutter build methods in
stream_chat_flutter. Lower priority, but 4 breach the 5-level nesting ceiling.

Strategy key

  • A — replace nested if/else or switch statements with a Dart 3 switch expression
  • B — guard-clause inversion (early returns to flatten nesting)
  • E — loop-body extraction with an explicit signal return
  • Tier 1 — pure decomposition into top-level/static helpers returning named records
  • Tier 2 — standard extract-method into private helpers

Notes

  • Update README.md #1 is the outlier by 2.3x. A 250-line method whose real cost is hand-rolled
    index-slot arithmetic (itemCount - 1/-2/-3, i == 0/1, i - 2) duplicated across
    three nested closures — itemKeyBuilder, separatorBuilder, itemBuilder — each
    re-deriving the same layout, guided by a 16-line ASCII comment block.
  • ChannelListView NULL error when a channel has no messages #6 is a metric artifact, not debt. 30 flat if (x != null) collection-ifs in one
    list literal, zero nesting. The per-parameter static type is required to construct
    StreamComponentBuilderExtension, so a .nonNulls collapse isn't available.
    Recommend accepting as-is.
  • Example: single and multiple conversations #2 scores high for a benign reason. ~14 sibling if (capability) add(action) blocks
    at depth 1 — breadth, not nesting. Satisfying the metric here wouldn't make it easier
    to read.

Excluded from remediation

Category Count Why
Drift / json_serializable .g.dart 31 in stream_chat_persistence, 2 in stream_chat Generated; already excluded from analysis_options.yaml
Vendored scrollable_positioned_list/ 6 (44, 23, 22, 18, 18, 16) Third-party fork ((c) Fuchsia Authors, BSD) — divergence costs more than the score

Summary by CodeRabbit

  • Improvements

    • Improved message-list layout handling for headers, footers, pagination, parent messages, threads, spacing, and unread separators.
    • Enhanced unread-message scrolling and message positioning for more accurate navigation.
    • Improved paged list and grid behavior across loading, empty, error, and successful states.
    • Added more reliable pagination indicators and safeguards against duplicate or invalid load-more requests.
  • Bug Fixes

    • Improved handling of load-more errors and leading items in paged lists and grids.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b8c8f8af-1b69-49a0-bb6b-a345e4598b83

📥 Commits

Reviewing files that changed from the base of the PR and between 4085207 and 069c643.

📒 Files selected for processing (5)
  • .github/actions/pana/action.yml
  • .github/workflows/stream_flutter_workflow.yml
  • packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart
  • packages/stream_chat_flutter/lib/src/message_list_view/message_list_view_layout.dart
  • packages/stream_chat_flutter/test/src/message_list_view/message_list_view_layout_test.dart
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/stream_chat_flutter/test/src/message_list_view/message_list_view_layout_test.dart
  • packages/stream_chat_flutter/lib/src/message_list_view/message_list_view_layout.dart

📝 Walkthrough

Walkthrough

The change adds MessageListLayout for message-list index mapping, refactors list and grid rendering into helpers, expands pagination tests, and adds workflow checks for cognitive complexity and the Pana project root.

Changes

Message list layout

Layer / File(s) Summary
Define and validate message layout
packages/stream_chat_flutter/lib/src/message_list_view/message_list_view_layout.dart, packages/stream_chat_flutter/test/src/message_list_view/message_list_view_layout_test.dart
Adds slot enums, index conversions, equality, hashing, and tests against the previous index arithmetic.
Integrate layout into message list
packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart
Uses MessageListLayout for item construction, separators, pagination slots, message anchoring, unread scrolling, and bottom-position detection.

Paged list and grid views

Layer / File(s) Summary
Extract paged rendering and pagination
packages/stream_chat_flutter_core/lib/src/paged_value_scroll_view.dart
Uses pattern matching and helper methods for list and grid states, item rendering, pagination triggers, offsets, and duplicate-request guards.
Validate paged view states
packages/stream_chat_flutter_core/test/paged_value_list_view_test.dart, packages/stream_chat_flutter_core/test/paged_value_grid_view_test.dart
Tests loading, error, empty, item, separator, pagination, leading-item, and request-suppression behavior.

Workflow quality checks

Layer / File(s) Summary
Add workflow quality checks
.github/workflows/stream_flutter_workflow.yml, .github/actions/pana/action.yml
Adds pull-request cognitive-complexity validation and passes the GitHub workspace as the Pana project root.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: xsahil03x, velikovpetar

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: reducing cognitive complexity in the UI and core packages.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/dart-complexity

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.

@renefloor renefloor changed the title decreased cognitive complexity fix(ui, core): decreased cognitive complexity Aug 12, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart (3)

787-794: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Resolve the two message indices through the layout.

_buildMessageSeparator is the last place that keeps raw slot arithmetic. Separator index divides items index and index + 1, so the two message positions are layout.messageIndexAt(index) and layout.messageIndexAt(index + 1). Passing layout here removes the remaining literal offsets and keeps every index conversion in one type.

The bounds stay safe either way: betweenMessages separators only occur for index in [2, messageCount].

♻️ Proposed refactor
-      case MessageListSeparatorSlot.betweenMessages:
-        return _buildMessageSeparator(context, index);
+      case MessageListSeparatorSlot.betweenMessages:
+        return _buildMessageSeparator(context, layout, index);
-  Widget _buildMessageSeparator(BuildContext context, int index) {
+  Widget _buildMessageSeparator(BuildContext context, MessageListLayout layout, int index) {
     // Separator `index` sits between items `index` and `index + 1`, so the two
     // messages it divides are offset by one and two message slots — in the
     // order they are rendered, which the list direction flips.
+    final first = layout.messageIndexAt(index);
+    final second = layout.messageIndexAt(index + 1);
     final (message, nextMessage) = switch (widget.config.reverse) {
-      true => (messages[index - 1], messages[index - 2]),
-      false => (messages[index - 2], messages[index - 1]),
+      true => (messages[second], messages[first]),
+      false => (messages[first], messages[second]),
     };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart`
around lines 787 - 794, Update _buildMessageSeparator to accept or access the
layout object and resolve both messages via layout.messageIndexAt(index) and
layout.messageIndexAt(index + 1), removing the reverse-dependent raw index
arithmetic. Preserve the existing separator behavior and safe bounds for
betweenMessages separators.

890-893: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Route the remaining fixed offsets through the layout too.

This call site now uses layout.itemIndexOfMessage. Three sibling sites still hard-code the same offset: _scrollToMessage uses messages.length + 2 and index + 2, and _handleItemPositionsChanged uses const lastItemIndex = 2. Convert them in this PR so a future slot change cannot desynchronize scrolling from rendering.

#!/bin/bash
# Find remaining hard-coded item-index offsets in the message list view.
rg -n -C2 '\+ 2|lastItemIndex' packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart`
around lines 890 - 893, Update _scrollToMessage and _handleItemPositionsChanged
to derive all item-index offsets through MessageListLayout, replacing
messages.length + 2, index + 2, and the hard-coded lastItemIndex value. Reuse
the layout’s existing item-index conversion so scrolling remains synchronized
with rendered slots.

751-754: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use a spacing token for the edge gap height.

Both gap builders return SizedBox(height: 8). The rest of the file resolves sizes from context.streamSpacing, for example in _defaultSpacingWidget and the list padding. _buildEndEdgeGap has no BuildContext parameter, so pass one if you adopt the token.

Also applies to: 775-777

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart`
around lines 751 - 754, Replace the hardcoded 8-pixel edge-gap height in the
start and end gap builders, including _buildEndEdgeGap, with the spacing token
from context.streamSpacing. Pass BuildContext into _buildEndEdgeGap if needed,
while preserving the existing builder selection and Empty behavior.
packages/stream_chat_flutter/lib/src/message_list_view/message_list_view_layout.dart (1)

104-108: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use a runtime type check for strict value equality. MessageListLayout is not final, so the current check accepts subclasses with additional state. Use other is MessageListLayout && other.runtimeType == runtimeType, or make the class final. equatable is not a direct dependency of stream_chat_flutter; do not use Equatable without adding the dependency and its import.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/stream_chat_flutter/lib/src/message_list_view/message_list_view_layout.dart`
around lines 104 - 108, Update MessageListLayout.operator == to require matching
runtimeType in addition to the MessageListLayout type check, so subclasses with
additional state are not considered equal; keep hashCode consistent with the
existing messageCount-based equality and do not introduce Equatable.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/stream_flutter_workflow.yml:
- Line 79: Update the workflow step using the diff-base setting so push runs do
not construct origin/ from an empty github.base_ref. Either restrict the step to
pull_request events or select origin/master when running on push while
preserving the existing pull-request base behavior.
- Around line 77-81: Update the cognitive complexity action configuration in the
workflow so diff-base is only supplied for pull_request events, or otherwise
resolves to a valid branch/revision for push events; ensure pushes never pass
the empty origin/ value while preserving the existing threshold and
increase-failure behavior.

---

Nitpick comments:
In
`@packages/stream_chat_flutter/lib/src/message_list_view/message_list_view_layout.dart`:
- Around line 104-108: Update MessageListLayout.operator == to require matching
runtimeType in addition to the MessageListLayout type check, so subclasses with
additional state are not considered equal; keep hashCode consistent with the
existing messageCount-based equality and do not introduce Equatable.

In
`@packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart`:
- Around line 787-794: Update _buildMessageSeparator to accept or access the
layout object and resolve both messages via layout.messageIndexAt(index) and
layout.messageIndexAt(index + 1), removing the reverse-dependent raw index
arithmetic. Preserve the existing separator behavior and safe bounds for
betweenMessages separators.
- Around line 890-893: Update _scrollToMessage and _handleItemPositionsChanged
to derive all item-index offsets through MessageListLayout, replacing
messages.length + 2, index + 2, and the hard-coded lastItemIndex value. Reuse
the layout’s existing item-index conversion so scrolling remains synchronized
with rendered slots.
- Around line 751-754: Replace the hardcoded 8-pixel edge-gap height in the
start and end gap builders, including _buildEndEdgeGap, with the spacing token
from context.streamSpacing. Pass BuildContext into _buildEndEdgeGap if needed,
while preserving the existing builder selection and Empty behavior.
🪄 Autofix

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a57f6650-82e7-4c6a-95bb-7a1ea736cc1d

📥 Commits

Reviewing files that changed from the base of the PR and between 56462e1 and 4085207.

📒 Files selected for processing (7)
  • .github/workflows/stream_flutter_workflow.yml
  • packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart
  • packages/stream_chat_flutter/lib/src/message_list_view/message_list_view_layout.dart
  • packages/stream_chat_flutter/test/src/message_list_view/message_list_view_layout_test.dart
  • packages/stream_chat_flutter_core/lib/src/paged_value_scroll_view.dart
  • packages/stream_chat_flutter_core/test/paged_value_grid_view_test.dart
  • packages/stream_chat_flutter_core/test/paged_value_list_view_test.dart

Comment thread .github/workflows/stream_flutter_workflow.yml Outdated
Comment thread .github/workflows/stream_flutter_workflow.yml
@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.41232% with 35 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.68%. Comparing base (56462e1) to head (069c643).

Files with missing lines Patch % Lines
...r/lib/src/message_list_view/message_list_view.dart 70.75% 31 Missing ⚠️
...rc/message_list_view/message_list_view_layout.dart 80.95% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2879      +/-   ##
==========================================
+ Coverage   73.47%   73.68%   +0.21%     
==========================================
  Files         431      432       +1     
  Lines       27827    27871      +44     
==========================================
+ Hits        20445    20536      +91     
+ Misses       7382     7335      -47     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions

Copy link
Copy Markdown

📊 Cognitive Complexity Analysis

Net Delta: -111 | Added: 23 | Increased: 0 | Improved: 4 | Violations: 0

Status Declaration Location Delta Score
🔵 _StreamMessageListViewState._resolveScrollTargetIndex packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart:L483-514 +7 7
🔵 _PagedValueGridViewState._buildItem packages/stream_chat_flutter_core/lib/src/paged_value_scroll_view.dart:L694-714 +5 5
🔵 _StreamMessageListViewState._buildSeparator packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart:L759-778 +5 5
🔵 _PagedValueGridViewState._maybeRequestNextPage packages/stream_chat_flutter_core/lib/src/paged_value_scroll_view.dart:L716-731 +5 5
🔵 _PagedValueListViewState._maybeRequestNextPage packages/stream_chat_flutter_core/lib/src/paged_value_scroll_view.dart:L342-357 +5 5
🔵 _StreamMessageListViewState._buildConnectionStatusTile packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart:L674-716 +4 4
🔵 _StreamMessageListViewState._buildItem packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart:L728-757 +4 4
🔵 MessageListLayout.itemSlotAt packages/stream_chat_flutter/lib/src/message_list_view/message_list_view_layout.dart:L79-87 +4 4
🔵 MessageListLayout.separatorSlotAt packages/stream_chat_flutter/lib/src/message_list_view/message_list_view_layout.dart:L89-97 +4 4
🔵 _StreamMessageListViewState._messageIdAt packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart:L720-726 +3 3
🔵 _StreamMessageListViewState._buildEndEdgeGap packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart:L791-801 +3 3
🔵 _PagedValueListViewState._buildItem packages/stream_chat_flutter_core/lib/src/paged_value_scroll_view.dart:L329-340 +2 2
🔵 _StreamMessageListViewState._buildThreadSeparator packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart:L780-787 +2 2
🔵 _StreamMessageListViewState._buildMessageSeparator packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart:L803-826 +2 2
🔵 _PagedValueGridViewState._buildGridView packages/stream_chat_flutter_core/lib/src/paged_value_scroll_view.dart:L671-692 +1 1
🔵 MessageListLayout.== packages/stream_chat_flutter/lib/src/message_list_view/message_list_view_layout.dart:L109-110 +1 1
🔵 MessageListLayout packages/stream_chat_flutter/lib/src/message_list_view/message_list_view_layout.dart:L58-59 0 0
🔵 get MessageListLayout.itemCount packages/stream_chat_flutter/lib/src/message_list_view/message_list_view_layout.dart:L72-73 0 0
🔵 MessageListLayout.messageIndexAt packages/stream_chat_flutter/lib/src/message_list_view/message_list_view_layout.dart:L99-102 0 0
🔵 MessageListLayout.itemIndexOfMessage packages/stream_chat_flutter/lib/src/message_list_view/message_list_view_layout.dart:L104-107 0 0
🔵 get MessageListLayout.hashCode packages/stream_chat_flutter/lib/src/message_list_view/message_list_view_layout.dart:L112-113 0 0
🔵 _PagedValueListViewState._buildListView packages/stream_chat_flutter_core/lib/src/paged_value_scroll_view.dart:L303-327 0 0
🔵 get MessageListLayout.parentMessageIndex packages/stream_chat_flutter/lib/src/message_list_view/message_list_view_layout.dart:L75-77 0 0
🟢 _StreamMessageListViewState._scrollToMessage packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart:L454-477 -10 15 -> 5
🟢 _PagedValueListViewState.build packages/stream_chat_flutter_core/lib/src/paged_value_scroll_view.dart:L292-301 -26 29 -> 3
🟢 _PagedValueGridViewState.build packages/stream_chat_flutter_core/lib/src/paged_value_scroll_view.dart:L660-669 -38 41 -> 3
🟢 _StreamMessageListViewState._buildListView packages/stream_chat_flutter/lib/src/message_list_view/message_list_view.dart:L608-672 -94 107 -> 13

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant