Skip to content

feat(ui): give CallParticipantSort an identity - #1364

Merged
renefloor merged 1 commit into
v2from
fix/participant-sort-drop-update-check
Sep 22, 2026
Merged

renefloor merged 1 commit into
v2from
fix/participant-sort-drop-update-check

Conversation

@renefloor

@renefloor renefloor commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Two PRs landed a re-sort rule in StreamCallParticipants.didUpdateWidget, each asserting the opposite of the other, and each with a test:

They only met when main was merged into v2, where one had to give. This PR is the resolution that satisfies both.

Supersedes the two earlier revisions of this PR. Those each traded one behaviour away; this gets all three, at the cost of a breaking type change.

The problem

StreamCallParticipants.didUpdateWidget has to answer "has the sorting changed?" — a comparator cannot answer it. A closure written inline is a new function on every build, so comparing them reorders the list every time. Comparing layoutMode.sorting instead avoids that, but then a caller replacing their own comparator is never noticed, and nothing else picks it up: Call.participantsStream is throttled and silent in a quiet call.

The change

CallParticipantSort becomes a class pairing the comparator with an identity:

StreamCallParticipants(
  sort: CallParticipantSort(byName, identity: 'by-name'),
)

Two sorts with equal identities are the same sort, whatever functions they hold — so one constructed inline on every build reorders nothing, and one with a new identity reorders on the spot. Without an identity a sort is only equal to itself, which is the old behaviour, documented as such.

The SDK's own sorts move to CallParticipantSorts (regular, speaker, pictureInPicture, livestreamOrAudioRoom), held once and identified, so layoutMode.sorting hands back the same instance for an unchanged layout.

layout change re-sorts caller's new sort applies inline sort re-sorts every build
sort identity (on v2 now) yes yes yes
layoutMode.sorting (rev 2) yes no no
filter only (rev 1) no no no
this PR yes yes no

Breaking

A bare closure no longer assigns to sort. There is no dart fix for it — fix_data.yaml can rename and drop parameters but cannot wrap an expression — so call sites are migrated by hand:

- sort: (a, b) => a.name.compareTo(b.name),
+ sort: CallParticipantSort(
+   (a, b) => a.name.compareTo(b.name),
+   identity: 'by-name',
+ ),

PictureInPictureConfiguration.sort and StreamPictureInPictureUiKitView.participantSort take a CallParticipantSort too, rather than a bare Comparator, so there is one type across the API.

Why the identity has to live on the sort

A wrapper that kept CallParticipantSort = Comparator does not work. Dart implicitly tears off call when an object is assigned to a function type, and the tear-off drops the custom equality:

wrapper == wrapper      : true
torn-off == torn-off    : false   ← assigned to the Comparator typedef
same instance tear-offs : true

So the field has to hold the class, which is what makes this breaking rather than additive. An optional sortKey parameter alongside the comparator was the non-breaking alternative; it was passed over in favour of one coherent type while v2 is still open for breaking changes.

Testing

melos run analyze:error clean across the workspace. 501 tests pass in stream_video_flutter — the same count as v2, with #1332's two tests and #1354's all passing together for the first time. Goldens not run locally.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: c35da53d-d86a-4bb3-9ce0-88b6adcf0b4a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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 force-pushed the fix/participant-sort-drop-update-check branch from 7d1e529 to e0e44d3 Compare September 22, 2026 10:14
@renefloor renefloor changed the title fix(ui): stop re-sorting participants on a comparator change fix(ui): re-sort participants on a layout change, not a comparator change Sep 22, 2026
@codecov

codecov Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 79.16667% with 5 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (v2@cdff31d). Learn more about missing BASE report.

Files with missing lines Patch % Lines
...r/lib/src/call_participants/call_participants.dart 68.75% 5 Missing ⚠️
Additional details and impacted files
@@          Coverage Diff          @@
##             v2    #1364   +/-   ##
=====================================
  Coverage      ?   35.21%           
=====================================
  Files         ?      387           
  Lines         ?    29567           
  Branches      ?        0           
=====================================
  Hits          ?    10413           
  Misses        ?    19154           
  Partials      ?        0           

☔ 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.

`didUpdateWidget` compared `sort` to decide whether the list had to be ordered
again, and a comparator cannot answer that: a closure written inline is a new
function on every build, so comparing those reordered the list every time.
Comparing the layout instead avoided that but missed a caller replacing their
own comparator, which nothing else would pick up — `Call.participantsStream` is
throttled and silent in a quiet call.

`CallParticipantSort` is a class now, pairing the comparator with an `identity`
that says when it has changed:

    sort: CallParticipantSort(byName, identity: 'by-name')

Two sorts with equal identities are the same sort, whatever functions they
hold, so one built inline on every build reorders nothing. Without an identity
a sort is only equal to itself, which is the old behaviour and is documented as
such.

The SDK's own sorts move to `CallParticipantSorts`, held once and identified,
so `layoutMode.sorting` hands back the same instance for an unchanged layout.
`PictureInPictureConfiguration.sort` and
`StreamPictureInPictureUiKitView.participantSort` take one too, rather than a
bare `Comparator`.

Breaking: a closure no longer assigns to `sort`. There is no `dart fix` for it
— fix_data cannot wrap an expression — so call sites are migrated by hand.

All three behaviours now hold together: a layout change reorders, a caller's
new sort reorders, and neither an inline closure nor a rebuild does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@renefloor
renefloor force-pushed the fix/participant-sort-drop-update-check branch from e0e44d3 to 63d652c Compare September 22, 2026 10:43
@renefloor renefloor changed the title fix(ui): re-sort participants on a layout change, not a comparator change feat(ui)!: give CallParticipantSort an identity Sep 22, 2026
@renefloor renefloor changed the title feat(ui)!: give CallParticipantSort an identity feat(ui): give CallParticipantSort an identity Sep 22, 2026
@renefloor
renefloor marked this pull request as ready for review September 22, 2026 10:47
@renefloor
renefloor requested a review from a team as a code owner September 22, 2026 10:47
@renefloor
renefloor merged commit b2672b5 into v2 Sep 22, 2026
10 of 12 checks passed
@renefloor
renefloor deleted the fix/participant-sort-drop-update-check branch September 22, 2026 11:45
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.

2 participants