Skip to content

Conversation

@lukemmtt
Copy link
Contributor

@lukemmtt lukemmtt commented Jul 25, 2025

Description

This PR adds an onReorderUpdate callback to ReorderableListView that fires continuously during drag operations, enabling developers to provide immediate feedback as items move between positions. This addresses a long-standing limitation where developers only had access to start/end callbacks, making it impossible to provide immediate user feedback during reordering.

demo2

This video demonstrates the new onReorderUpdate callback, showing real-time feedback during drag operations.

Motivation

Developers have long requested the ability to provide immediate feedback during reorder operations (see #139187). Currently, ReorderableListView only provides callbacks at the start and end of a drag, limiting the ability to create responsive, modern UIs with haptic feedback, visual indicators, or sound effects during dragging.

This PR is part of a series of ReorderableList enhancements I've developed for TimeFinder:

Usage Example

ReorderableListView(
  onReorder: (oldIndex, newIndex) {
    // Final reorder logic
  },
  onReorderUpdate: (fromIndex, toIndex) {
    // Fires continuously as the item moves
    // fromIndex: original position
    // toIndex: where the item would end up if dropped now
    HapticFeedback.lightImpact();
  },
  children: [...],
)

Implementation Details

API Changes

  1. Added ReorderUpdateCallback typedef: void Function(int fromIndex, int toIndex)
  2. Added optional onReorderUpdate parameter to ReorderableListView, ReorderableList, and SliverReorderableList
  3. The callback fires from _dragUpdateItems() whenever the potential drop position changes

Index Handling

Flutter's reordering API uses a specific convention that requires careful handling:

The Challenge: The onReorder callback receives newIndex as an insertion point before the dragged item is removed from the list. For example:

  • List: [A, B, C, D]
  • Dragging B (index 1) to after C
  • onReorder receives: oldIndex=1, newIndex=3 (not 2)
  • Final position after move: index 2

This convention (documented in ReorderCallback) works fine for the final onReorder callback but presents challenges for continuous updates where developers expect intuitive "final position" values.

The Solution: This PR includes a helper method that converts insertion points to final positions:

static int _insertionPointToFinalIndex({
  required int insertionPoint,
  required int originalIndex,
}) {
  return insertionPoint > originalIndex ? insertionPoint - 1 : insertionPoint;
}

This ensures onReorderUpdate provides intuitive values that match what developers expect and what they would implement in their onReorder handler.

Preventing Redundant Callbacks

The implementation tracks the previous final index to ensure callbacks only fire when the actual destination position changes, preventing unnecessary updates when the internal _insertIndex changes without affecting the final position.

Related Issues

Resolves #139187

This addresses needs expressed in third-party packages that have attempted to work around this limitation:

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

Breaking Change

Does your PR require Flutter developers to manually update their apps to accommodate your change?

  • Yes, this is a breaking change
  • No, this is not a breaking change

@flutter-dashboard
Copy link

It looks like this pull request may not have tests. Please make sure to add tests or get an explicit test exemption before merging.

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.If you believe this PR qualifies for a test exemption, contact "@test-exemption-reviewer" in the #hackers channel in Discord (don't just cc them here, they won't see it!). The test exemption team is a small volunteer group, so all reviewers should feel empowered to ask for tests, without delegating that responsibility entirely to the test exemption group.

@github-actions github-actions bot added framework flutter/packages/flutter repository. See also f: labels. f: material design flutter/packages/flutter/material repository. labels Jul 25, 2025
@lukemmtt lukemmtt marked this pull request as draft July 25, 2025 01:45
@lukemmtt lukemmtt force-pushed the timefinder/real-time-reorder-feedback branch 2 times, most recently from 1c1966c to a7e06c2 Compare July 25, 2025 02:15
@lukemmtt lukemmtt force-pushed the timefinder/real-time-reorder-feedback branch 9 times, most recently from 6ede3a1 to b1e969d Compare July 27, 2025 13:31
@lukemmtt lukemmtt changed the title Add real-time reorder feedback with onReorderUpdate callback Add onReorderUpdate callback to ReorderableList Jul 27, 2025
@lukemmtt lukemmtt changed the title Add onReorderUpdate callback to ReorderableList Add onReorderUpdate callback to ReorderableListView Jul 27, 2025
@lukemmtt lukemmtt force-pushed the timefinder/real-time-reorder-feedback branch 2 times, most recently from 99dbb74 to 6aba260 Compare July 29, 2025 00:24
@lukemmtt lukemmtt force-pushed the timefinder/real-time-reorder-feedback branch 4 times, most recently from 0b6af15 to e8c013f Compare August 7, 2025 05:00
Adds onReorderUpdate callback to ReorderableListView that fires continuously during drag operations when the potential insertion position changes. The callback provides final indices accounting for Flutter's quirk where the index represents the pending insertion point before removal, rather than the would-be destination index after the reorder.
@lukemmtt lukemmtt force-pushed the timefinder/real-time-reorder-feedback branch from e8c013f to 76b6a57 Compare August 7, 2025 05:04
@lukemmtt lukemmtt marked this pull request as ready for review August 7, 2025 05:28
@lukemmtt lukemmtt marked this pull request as draft August 7, 2025 15:32
@lukemmtt
Copy link
Contributor Author

lukemmtt commented Aug 7, 2025

To do:

  • Clean up implementation / comments; minimize new code
  • Add tests

@Piinks
Copy link
Contributor

Piinks commented Oct 20, 2025

This one as well - I run through older PRs every week to make sure we aren't missing any in our other triage meetings, so no pressure at all. Just checking in. :)

@lukemmtt
Copy link
Contributor Author

@Piinks Thanks for the messages, I totally understand and appreciate the check-ins!

For now I'm going to close these drafts; I think these are valuable enhancements—to the extent that I've vendored the framework files and implemented them in my own app—but I think they're too niche to justify the effort needed to make their PRs merge-worthy. Happy to revisit someday if it makes sense though ☀️

Thanks for all you do Kate!

@lukemmtt lukemmtt closed this Oct 20, 2025
github-merge-queue bot pushed a commit that referenced this pull request Nov 5, 2025
…animation (#173241)

When rapidly dragging items in a ReorderableList before animations
complete, items would jump to their expected positions rather than
smoothly transitioning.

## Problem

<p align="center">
<img
src="https://github.com/user-attachments/assets/0efb250c-2960-4942-959f-59eccc20cefb"
alt="demo2" width="250">
</p>

The issue occurs when a reorder animation is interrupted by starting a
new drag operation. The interrupted animation would reset to the
starting position of the original animation rather than capturing the
current animated position, causing a visual jump.

## Solution

This PR fixes the issue by:
1. Storing the previous target offset before updating to a new target
2. When an animation is interrupted, calculating the actual current
position based on the animation's progress
3. Using this calculated position as the new starting point for the next
animation

<p align="center">
<img
src="https://github.com/user-attachments/assets/c24e4834-1c6b-41c1-8f44-17b4c79e0993"
alt="demo2" width="250">
</p>

This PR is part of a series of `ReorderableList` enhancements I've
developed for [TimeFinder](https://timefinder.app):
- #172740
- #172380
- #172739
- #172738

Fixes #173243

## Code Changes

```dart
// Before
_startOffset = offset;

// After  
final double currentAnimValue = Curves.easeInOut.transform(_offsetAnimation\!.value);
final Offset currentPosition = Offset.lerp(_startOffset, previousTarget, currentAnimValue)\!;
_startOffset = currentPosition;
```

This ensures smooth transitions without visual jumping, making rapid
reordering gestures feel more responsive and natural.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [migration
guide] as needed.
- [x] All existing and new tests are passing.
- [x] The analyzer (`flutter analyze --flutter-repo`) does not report
any problems on my PR.
- [x] I am willing to follow-up on review comments in a timely manner.

[Contributor Guide]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[test-exempt]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[migration guide]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#create-a-migration-guide
IvoneDjaja pushed a commit to IvoneDjaja/flutter that referenced this pull request Nov 22, 2025
…animation (flutter#173241)

When rapidly dragging items in a ReorderableList before animations
complete, items would jump to their expected positions rather than
smoothly transitioning.

## Problem

<p align="center">
<img
src="https://github.com/user-attachments/assets/0efb250c-2960-4942-959f-59eccc20cefb"
alt="demo2" width="250">
</p>

The issue occurs when a reorder animation is interrupted by starting a
new drag operation. The interrupted animation would reset to the
starting position of the original animation rather than capturing the
current animated position, causing a visual jump.

## Solution

This PR fixes the issue by:
1. Storing the previous target offset before updating to a new target
2. When an animation is interrupted, calculating the actual current
position based on the animation's progress
3. Using this calculated position as the new starting point for the next
animation

<p align="center">
<img
src="https://github.com/user-attachments/assets/c24e4834-1c6b-41c1-8f44-17b4c79e0993"
alt="demo2" width="250">
</p>

This PR is part of a series of `ReorderableList` enhancements I've
developed for [TimeFinder](https://timefinder.app):
- flutter#172740
- flutter#172380
- flutter#172739
- flutter#172738

Fixes flutter#173243

## Code Changes

```dart
// Before
_startOffset = offset;

// After  
final double currentAnimValue = Curves.easeInOut.transform(_offsetAnimation\!.value);
final Offset currentPosition = Offset.lerp(_startOffset, previousTarget, currentAnimValue)\!;
_startOffset = currentPosition;
```

This ensures smooth transitions without visual jumping, making rapid
reordering gestures feel more responsive and natural.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [migration
guide] as needed.
- [x] All existing and new tests are passing.
- [x] The analyzer (`flutter analyze --flutter-repo`) does not report
any problems on my PR.
- [x] I am willing to follow-up on review comments in a timely manner.

[Contributor Guide]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[test-exempt]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[migration guide]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#create-a-migration-guide
reidbaker pushed a commit to AbdeMohlbi/flutter that referenced this pull request Dec 10, 2025
…animation (flutter#173241)

When rapidly dragging items in a ReorderableList before animations
complete, items would jump to their expected positions rather than
smoothly transitioning.

## Problem

<p align="center">
<img
src="https://github.com/user-attachments/assets/0efb250c-2960-4942-959f-59eccc20cefb"
alt="demo2" width="250">
</p>

The issue occurs when a reorder animation is interrupted by starting a
new drag operation. The interrupted animation would reset to the
starting position of the original animation rather than capturing the
current animated position, causing a visual jump.

## Solution

This PR fixes the issue by:
1. Storing the previous target offset before updating to a new target
2. When an animation is interrupted, calculating the actual current
position based on the animation's progress
3. Using this calculated position as the new starting point for the next
animation

<p align="center">
<img
src="https://github.com/user-attachments/assets/c24e4834-1c6b-41c1-8f44-17b4c79e0993"
alt="demo2" width="250">
</p>

This PR is part of a series of `ReorderableList` enhancements I've
developed for [TimeFinder](https://timefinder.app):
- flutter#172740
- flutter#172380
- flutter#172739
- flutter#172738

Fixes flutter#173243

## Code Changes

```dart
// Before
_startOffset = offset;

// After  
final double currentAnimValue = Curves.easeInOut.transform(_offsetAnimation\!.value);
final Offset currentPosition = Offset.lerp(_startOffset, previousTarget, currentAnimValue)\!;
_startOffset = currentPosition;
```

This ensures smooth transitions without visual jumping, making rapid
reordering gestures feel more responsive and natural.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [migration
guide] as needed.
- [x] All existing and new tests are passing.
- [x] The analyzer (`flutter analyze --flutter-repo`) does not report
any problems on my PR.
- [x] I am willing to follow-up on review comments in a timely manner.

[Contributor Guide]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[test-exempt]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[migration guide]:
https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#create-a-migration-guide
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add dragUpdate that receives the current drag position in ReorderableListView

2 participants