Skip to content

Fix(AnimatedScrollView): exclude outgoing items in removeAllItems - #176452

Merged
auto-submit[bot] merged 10 commits into
flutter:masterfrom
kazbeksultanov:animation_scroll_view_remove_all_fix
Oct 21, 2025
Merged

auto-submit[bot] merged 10 commits into
flutter:masterfrom
kazbeksultanov:animation_scroll_view_remove_all_fix

Conversation

@kazbeksultanov

@kazbeksultanov kazbeksultanov commented Oct 3, 2025 •

Copy link
Copy Markdown
Contributor

Fix: #176362

Issue provided reproducible code related to exception being thrown when removing all items in an AnimatedList when some of the item were already being removed but had a long animation duration.

In this PR: The removeAllItems method now correctly calculates the range of items to remove, excluding those already undergoing a removal animation. This prevents an assert from triggering when removeAllItems is called while other items are still being removed.

Video example after fix
Screen.Recording.2025-10-03.at.12.45.34.PM.mov
Updated reproducible code
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(debugShowCheckedModeBanner: false, home: AnimatedListExample());
  }
}

class AnimatedListExample extends StatefulWidget {
  const AnimatedListExample({super.key});

  @override
  State<AnimatedListExample> createState() => _AnimatedListExampleState();
}

class _AnimatedListExampleState extends State<AnimatedListExample> {
  final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
  final List<int> _items = List<int>.generate(10, (int index) => index);

  void _removeItem(int index) {
    final int removedItem = _items[index];
    _listKey.currentState?.removeItem(
      index,
      (BuildContext context, Animation<double> animation) => SizeTransition(
        sizeFactor: animation,
        child: _buildItem(removedItem, animation, isRemoved: true),
      ),
      // 👇 Long delay so you can press "Remove All" while it’s still animating
      duration: const Duration(seconds: 10),
    );
    _items.removeAt(index);
  }

  void _addItem() {
    final int addingItem = _items.length;
    _listKey.currentState?.insertItem(addingItem, duration: const Duration(seconds: 3));
    _items.add(addingItem);
  }

  void _removeAll() {
    _listKey.currentState?.removeAllItems((BuildContext context, Animation<double> animation) {
      return SizeTransition(
        sizeFactor: animation,
        child: Container(
          color: Colors.red[100],
          child: const ListTile(title: Text('Removing...')),
        ),
      );
    }, duration: const Duration(seconds: 2));
    _items.clear();
  }

  Widget _buildItem(int item, Animation<double> animation, {bool isRemoved = false}) {
    return SizeTransition(
      sizeFactor: animation,
      child: Card(
        margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
        child: ListTile(
          title: Text('Item $item'),
          trailing: !isRemoved
              ? IconButton(
                  icon: const Icon(Icons.delete, color: Colors.red),
                  onPressed: () {
                    final int index = _items.indexOf(item);
                    if (index != -1) {
                      _removeItem(index);
                    }
                  },
                )
              : null,
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('AnimatedList Example'),
        actions: <Widget>[
          TextButton(onPressed: _addItem, child: const Text('Add')),
          TextButton(
            onPressed: _items.isNotEmpty ? _removeAll : null,
            child: const Text('Clear list'),
          ),
        ],
      ),
      body: AnimatedList(
        key: _listKey,
        initialItemCount: _items.length,
        itemBuilder: (BuildContext context, int index, Animation<double> animation) {
          return _buildItem(_items[index], animation);
        },
      ),
    );
  }
}

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the gemini-code-assist bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.

talabat.com Talabat Flutter PRs

@flutter-dashboard

Copy link
Copy Markdown

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: scrolling Viewports, list views, slivers, etc. labels Oct 3, 2025

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request fixes a bug in AnimatedList where an exception could be thrown if removeAllItems was called while other items were already undergoing a removal animation. The fix correctly calculates the range of items to remove by excluding items that are already animating out. My review includes suggestions to improve the clarity of a variable name and the documentation for the removeAllItems method.

Comment thread packages/flutter/lib/src/widgets/animated_scroll_view.dart Outdated
Comment thread packages/flutter/lib/src/widgets/animated_scroll_view.dart Outdated
The `removeAllItems` method now correctly calculates the range of items to remove, excluding those already undergoing a removal animation. This prevents an assert from triggering when `removeAllItems` is called while other items are still being removed.
@kazbeksultanov
kazbeksultanov force-pushed the animation_scroll_view_remove_all_fix branch from acf6820 to b6582e0 Compare October 3, 2025 12:15
@kazbeksultanov

Copy link
Copy Markdown
Contributor Author

@chunhtai @chinmaygarde can I ask for review?

@justinmc
justinmc requested a review from Piinks October 7, 2025 22:06
@kazbeksultanov

Copy link
Copy Markdown
Contributor Author

@Piinks what should I do to accelerate this process of review?

@kazbeksultanov

Copy link
Copy Markdown
Contributor Author

@justinmc maybe you can suggest somebody, I believe @Piinks is not available right now 🙏

@justinmc justinmc 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.

Can you add a test for this?

Adds a test case to ensure that calling `removeAllItems` on an `AnimatedList` while another item is already in the process of being removed executes safely and correctly clears the list.
@kazbeksultanov

Copy link
Copy Markdown
Contributor Author

@justinmc added test for the exact scenario that I am testing. Waiting for review

@justinmc justinmc 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.

LGTM with nits 👍

Comment thread packages/flutter/lib/src/widgets/animated_scroll_view.dart Outdated
Comment thread packages/flutter/lib/src/widgets/animated_scroll_view.dart Outdated
Comment thread packages/flutter/test/widgets/animated_list_test.dart Outdated
…mentation

The documentation for `AnimatedList.removeAllItems` is clarified to better explain its behavior, specifying that it removes all items, including those being inserted, but excludes those already being removed.

The implementation is updated to use a more descriptive variable name, `visibleItemCount`, for better code readability.

Additionally, periods have been added to comments in the corresponding test file for grammatical correctness.

@chunhtai chunhtai 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.

LGTM

@chunhtai chunhtai added the autosubmit Merge PR when tree becomes green via auto submit App label Oct 20, 2025
@auto-submit auto-submit Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Oct 20, 2025
@auto-submit

auto-submit Bot commented Oct 20, 2025

Copy link
Copy Markdown
Contributor

autosubmit label was removed for flutter/flutter/176452, because - The status or check suite Google testing has failed. Please fix the issues identified (or deflake) before re-applying this label.

@chunhtai

Copy link
Copy Markdown
Contributor

Can't tell whats wrong from google test log, rerun to see if it passes

@justinmc justinmc 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.

LGTM, will autosubmit

@justinmc justinmc added the autosubmit Merge PR when tree becomes green via auto submit App label Oct 21, 2025
@auto-submit
auto-submit Bot added this pull request to the merge queue Oct 21, 2025
Merged via the queue into flutter:master with commit 61fff25 Oct 21, 2025
78 checks passed
@flutter-dashboard flutter-dashboard Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Oct 21, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Oct 22, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Oct 22, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Oct 22, 2025
auto-submit Bot pushed a commit to flutter/packages that referenced this pull request Oct 22, 2025
Roll Flutter from 2d34167 to 75004a6 (39 revisions)

flutter/flutter@2d34167...75004a6

2025-10-22 [email protected] [Impeller] Add the paint color to the key of the text shadow cache (flutter/flutter#177140)
2025-10-22 [email protected] Roll Skia from 96e75ca8e24b to 928b5cf727c1 (2 revisions) (flutter/flutter#177387)
2025-10-22 [email protected] Roll reclient to version 185 (flutter/flutter#177293)
2025-10-22 [email protected] Roll Skia from b157f6b95f95 to 96e75ca8e24b (4 revisions) (flutter/flutter#177366)
2025-10-22 [email protected] Fix InputDatePickerFormField does not inherit local InputDecorationTheme (flutter/flutter#177090)
2025-10-22 [email protected] Roll Skia from 2c6162c977db to b157f6b95f95 (2 revisions) (flutter/flutter#177362)
2025-10-22 [email protected] Roll Skia from cadf8e7e6fca to 2c6162c977db (4 revisions) (flutter/flutter#177359)
2025-10-22 [email protected] Cleanup after -news_toolkit, +google_fonts, and some leftover `team-go_router` (flutter/flutter#176841)
2025-10-21 [email protected] don't break sheet's snap from physics (flutter/flutter#171157)
2025-10-21 [email protected] Roll Dart SDK from 913c2ae1b367 to c23010c4f9e6 (8 revisions) (flutter/flutter#177348)
2025-10-21 [email protected] Fix typo in comment about screen availibility  (flutter/flutter#177168)
2025-10-21 [email protected] Fix(AnimatedScrollView): exclude outgoing items in removeAllItems (flutter/flutter#176452)
2025-10-21 [email protected] Enable deprecated_member_use_from_same_package for all packages containing tests of Dart fixes defined within the package (flutter/flutter#177341)
2025-10-21 [email protected] Roll Skia from 19bff385f7e8 to cadf8e7e6fca (3 revisions) (flutter/flutter#177331)
2025-10-21 [email protected] Revert "[Android 16] Update `android_engine_vulkan_tests` to Test Against SDK 36 Emulator" (flutter/flutter#177292)
2025-10-21 [email protected] Fix SliverMainAxisGroup.cacheOrigin (flutter/flutter#175760)
2025-10-21 [email protected] Roll Skia from 75c756e029c9 to 19bff385f7e8 (3 revisions) (flutter/flutter#177316)
2025-10-21 [email protected] Fix typo in overlay.dart documentation comment (flutter/flutter#176612)
2025-10-21 [email protected] [ Tool ] Output DTD URI for Flutter web applications (flutter/flutter#177310)
2025-10-21 [email protected] Roll Skia from 982988b472a4 to 75c756e029c9 (1 revision) (flutter/flutter#177305)
2025-10-21 [email protected] Roll Skia from 42ff13a91c80 to 982988b472a4 (8 revisions) (flutter/flutter#177300)
2025-10-21 [email protected] Fix DateRangePickerDialog does not inherit local InputDecorationTheme (flutter/flutter#177086)
2025-10-21 [email protected] Remove references to dart:_js_annotations (flutter/flutter#176698)
2025-10-20 [email protected] Make `FlutterSceneLifeCycleProvider.sceneLifeCycleDelegate` readonly (flutter/flutter#177240)
2025-10-20 [email protected] Make sure that a CupertinoDesktopTextSelectionToolbar doesn't crash i… (flutter/flutter#173964)
2025-10-20 [email protected] Make sure that a BottomSheet doesn't crash in 0x0 environment (flutter/flutter#172229)
2025-10-20 [email protected] Move the Fuchsia SDK to //third_party/fuchsia-sdk (flutter/flutter#177118)
2025-10-20 [email protected] Roll Skia from 641994569415 to 42ff13a91c80 (8 revisions) (flutter/flutter#177283)
2025-10-20 [email protected] Make sure that a NavigationDrawer doesn't crash in 0x0 environment (flutter/flutter#176951)
2025-10-20 [email protected] Fix ink features painting in TabBar. (flutter/flutter#177155)
2025-10-20 [email protected] Make sure that SimpleDialog and SimpleDialogOption do not crash in 0x0 environment (flutter/flutter#174229)
2025-10-20 [email protected] Fix ink features painting in YearPicker. (flutter/flutter#177014)
2025-10-20 [email protected] Update `image.error_builder.0.dart` to replace the emoji with some text  (flutter/flutter#176886)
2025-10-20 [email protected] Roll Skia from ed4294faecde to 641994569415 (4 revisions) (flutter/flutter#177264)
2025-10-20 [email protected] Remove redundant name field form `TargetPlatform` and `XCDeviceEventInterface` enums (flutter/flutter#176890)
2025-10-20 [email protected] Added support to pass in texture type while creating textures. (flutter/flutter#175376)
2025-10-20 [email protected] Roll Packages from 3747006 to d113bbc (6 revisions) (flutter/flutter#177270)
2025-10-20 [email protected] Roll Dart SDK from 2cd2106f2cef to 913c2ae1b367 (2 revisions) (flutter/flutter#177258)
2025-10-20 [email protected] Added link to ClipRect from ImageFilter in the docstring (flutter/flutter#177196)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC [email protected],[email protected] on the revert to ensure that a human
is aware of the problem.

...
reidbaker pushed a commit to AbdeMohlbi/flutter that referenced this pull request Dec 10, 2025
…utter#176452)

<!--
Thanks for filing a pull request!
Reviewers are typically assigned within a week of filing a request.
To learn more about code review, see our documentation on Tree Hygiene:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
-->

Fix: flutter#176362

Issue provided reproducible code related to exception being thrown when
removing all items in an `AnimatedList` when some of the item were
already being removed but had a long animation duration.

In this PR: The `removeAllItems` method now correctly calculates the
range of items to remove, excluding those already undergoing a removal
animation. This prevents an assert from triggering when `removeAllItems`
is called while other items are still being removed.

<details open>
<summary> Video example after fix </summary>


https://github.com/user-attachments/assets/c7351702-b829-4e35-9978-b95f9f3ae8cd


</details>


<details open>
<summary> Updated reproducible code </summary>

```
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @OverRide
  Widget build(BuildContext context) {
    return const MaterialApp(debugShowCheckedModeBanner: false, home: AnimatedListExample());
  }
}

class AnimatedListExample extends StatefulWidget {
  const AnimatedListExample({super.key});

  @OverRide
  State<AnimatedListExample> createState() => _AnimatedListExampleState();
}

class _AnimatedListExampleState extends State<AnimatedListExample> {
  final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
  final List<int> _items = List<int>.generate(10, (int index) => index);

  void _removeItem(int index) {
    final int removedItem = _items[index];
    _listKey.currentState?.removeItem(
      index,
      (BuildContext context, Animation<double> animation) => SizeTransition(
        sizeFactor: animation,
        child: _buildItem(removedItem, animation, isRemoved: true),
      ),
      // 👇 Long delay so you can press "Remove All" while it’s still animating
      duration: const Duration(seconds: 10),
    );
    _items.removeAt(index);
  }

  void _addItem() {
    final int addingItem = _items.length;
    _listKey.currentState?.insertItem(addingItem, duration: const Duration(seconds: 3));
    _items.add(addingItem);
  }

  void _removeAll() {
    _listKey.currentState?.removeAllItems((BuildContext context, Animation<double> animation) {
      return SizeTransition(
        sizeFactor: animation,
        child: Container(
          color: Colors.red[100],
          child: const ListTile(title: Text('Removing...')),
        ),
      );
    }, duration: const Duration(seconds: 2));
    _items.clear();
  }

  Widget _buildItem(int item, Animation<double> animation, {bool isRemoved = false}) {
    return SizeTransition(
      sizeFactor: animation,
      child: Card(
        margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
        child: ListTile(
          title: Text('Item $item'),
          trailing: !isRemoved
              ? IconButton(
                  icon: const Icon(Icons.delete, color: Colors.red),
                  onPressed: () {
                    final int index = _items.indexOf(item);
                    if (index != -1) {
                      _removeItem(index);
                    }
                  },
                )
              : null,
        ),
      ),
    );
  }

  @OverRide
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('AnimatedList Example'),
        actions: <Widget>[
          TextButton(onPressed: _addItem, child: const Text('Add')),
          TextButton(
            onPressed: _items.isNotEmpty ? _removeAll : null,
            child: const Text('Clear list'),
          ),
        ],
      ),
      body: AnimatedList(
        key: _listKey,
        initialItemCount: _items.length,
        itemBuilder: (BuildContext context, int index, Animation<double> animation) {
          return _buildItem(_items[index], animation);
        },
      ),
    );
  }
}
```

</details>


## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] 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].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md

---------

Co-authored-by: chunhtai <[email protected]>
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Feb 11, 2026
gabrimatic added a commit to gabrimatic/flutter that referenced this pull request Apr 29, 2026
…Item

The separator-aware index translation in
_AnimatedScrollViewState._computeItemIndex used _itemsCount, which
counts every entry in the underlying sliver including ones still
animating out. The sliver-level insert/remove APIs are documented
to operate as if outgoing items had already been removed (see the
comment on _SliverAnimatedMultiBoxAdaptorState._indexToItemIndex),
so the separator math must also be done against that live count.

When a removeItem call was followed by an insertItem on the same
position before the removal animation had finished, the inflated
count made _computeItemIndex map the user index to a sliver index
past the new live end. _SliverAnimatedMultiBoxAdaptorState then
shifted that index further to skip the still-outgoing entries and
tripped the bounds assertion in insertItem.

Compute the index against _visibleItemsCount (_itemsCount minus the
number of outgoing animations) so user indices match the live state
that the sliver-level APIs expect, matching the precedent set by
removeAllItems in 61fff25 (flutter#176452).

Fixes flutter#179029
gabrimatic added a commit to gabrimatic/flutter that referenced this pull request Apr 29, 2026
…Item

The separator-aware index translation in
_AnimatedScrollViewState._computeItemIndex used _itemsCount, which
counts every entry in the underlying sliver including ones still
animating out. The sliver-level insert/remove APIs are documented
to operate as if outgoing items had already been removed (see the
comment on _SliverAnimatedMultiBoxAdaptorState._indexToItemIndex),
so the separator math must also be done against that live count.

When a removeItem call was followed by an insertItem on the same
position before the removal animation had finished, the inflated
count made _computeItemIndex map the user index to a sliver index
past the new live end. _SliverAnimatedMultiBoxAdaptorState then
shifted that index further to skip the still-outgoing entries and
tripped the bounds assertion in insertItem.

Compute the index against _visibleItemsCount (_itemsCount minus the
number of outgoing animations) so user indices match the live state
that the sliver-level APIs expect, matching the precedent set by
removeAllItems in 61fff25 (flutter#176452).

Fixes flutter#179029
gabrimatic added a commit to gabrimatic/flutter that referenced this pull request May 1, 2026
…Item

The separator-aware index translation in
_AnimatedScrollViewState._computeItemIndex used _itemsCount, which
counts every entry in the underlying sliver including ones still
animating out. The sliver-level insert/remove APIs are documented
to operate as if outgoing items had already been removed (see the
comment on _SliverAnimatedMultiBoxAdaptorState._indexToItemIndex),
so the separator math must also be done against that live count.

When a removeItem call was followed by an insertItem on the same
position before the removal animation had finished, the inflated
count made _computeItemIndex map the user index to a sliver index
past the new live end. _SliverAnimatedMultiBoxAdaptorState then
shifted that index further to skip the still-outgoing entries and
tripped the bounds assertion in insertItem.

Compute the index against _visibleItemsCount (_itemsCount minus the
number of outgoing animations) so user indices match the live state
that the sliver-level APIs expect, matching the precedent set by
removeAllItems in 61fff25 (flutter#176452).

Fixes flutter#179029
gabrimatic added a commit to gabrimatic/flutter that referenced this pull request May 1, 2026
…Item

The separator-aware index translation in
_AnimatedScrollViewState._computeItemIndex used _itemsCount, which
counts every entry in the underlying sliver including ones still
animating out. The sliver-level insert/remove APIs are documented
to operate as if outgoing items had already been removed (see the
comment on _SliverAnimatedMultiBoxAdaptorState._indexToItemIndex),
so the separator math must also be done against that live count.

When a removeItem call was followed by an insertItem on the same
position before the removal animation had finished, the inflated
count made _computeItemIndex map the user index to a sliver index
past the new live end. _SliverAnimatedMultiBoxAdaptorState then
shifted that index further to skip the still-outgoing entries and
tripped the bounds assertion in insertItem.

Compute the index against _visibleItemsCount (_itemsCount minus the
number of outgoing animations) so user indices match the live state
that the sliver-level APIs expect, matching the precedent set by
removeAllItems in 61fff25 (flutter#176452).

Fixes flutter#179029
gabrimatic added a commit to gabrimatic/flutter that referenced this pull request May 1, 2026
…Item

The separator-aware index translation in
_AnimatedScrollViewState._computeItemIndex used _itemsCount, which
counts every entry in the underlying sliver including ones still
animating out. The sliver-level insert/remove APIs are documented
to operate as if outgoing items had already been removed (see the
comment on _SliverAnimatedMultiBoxAdaptorState._indexToItemIndex),
so the separator math must also be done against that live count.

When a removeItem call was followed by an insertItem on the same
position before the removal animation had finished, the inflated
count made _computeItemIndex map the user index to a sliver index
past the new live end. _SliverAnimatedMultiBoxAdaptorState then
shifted that index further to skip the still-outgoing entries and
tripped the bounds assertion in insertItem.

Compute the index against _visibleItemsCount (_itemsCount minus the
number of outgoing animations) so user indices match the live state
that the sliver-level APIs expect, matching the precedent set by
removeAllItems in 61fff25 (flutter#176452).

Fixes flutter#179029
gabrimatic added a commit to gabrimatic/flutter that referenced this pull request May 2, 2026
…Item

The separator-aware index translation in
_AnimatedScrollViewState._computeItemIndex used _itemsCount, which
counts every entry in the underlying sliver including ones still
animating out. The sliver-level insert/remove APIs are documented
to operate as if outgoing items had already been removed (see the
comment on _SliverAnimatedMultiBoxAdaptorState._indexToItemIndex),
so the separator math must also be done against that live count.

When a removeItem call was followed by an insertItem on the same
position before the removal animation had finished, the inflated
count made _computeItemIndex map the user index to a sliver index
past the new live end. _SliverAnimatedMultiBoxAdaptorState then
shifted that index further to skip the still-outgoing entries and
tripped the bounds assertion in insertItem.

Compute the index against _visibleItemsCount (_itemsCount minus the
number of outgoing animations) so user indices match the live state
that the sliver-level APIs expect, matching the precedent set by
removeAllItems in 61fff25 (flutter#176452).

Fixes flutter#179029
gabrimatic added a commit to gabrimatic/flutter that referenced this pull request May 2, 2026
…Item

The separator-aware index translation in
_AnimatedScrollViewState._computeItemIndex used _itemsCount, which
counts every entry in the underlying sliver including ones still
animating out. The sliver-level insert/remove APIs are documented
to operate as if outgoing items had already been removed (see the
comment on _SliverAnimatedMultiBoxAdaptorState._indexToItemIndex),
so the separator math must also be done against that live count.

When a removeItem call was followed by an insertItem on the same
position before the removal animation had finished, the inflated
count made _computeItemIndex map the user index to a sliver index
past the new live end. _SliverAnimatedMultiBoxAdaptorState then
shifted that index further to skip the still-outgoing entries and
tripped the bounds assertion in insertItem.

Compute the index against _visibleItemsCount (_itemsCount minus the
number of outgoing animations) so user indices match the live state
that the sliver-level APIs expect, matching the precedent set by
removeAllItems in 61fff25 (flutter#176452).

Fixes flutter#179029
KercyDing pushed a commit to KercyDing/flutter that referenced this pull request May 20, 2026
flutter#186389)

## Summary

`AnimatedList.separated.removeItem` used the sliver's total child count
(`_itemsCount`) to decide whether the just-removed item was the last
item in the list. That count still includes children animating out from
earlier `removeItem` calls, so while a previous removal was in flight
the new tail of the visible list was misclassified as a middle item. The
separator removal then targeted the wrong index, and once the item being
removed was added to `_outgoingItems` the next `_indexToItemIndex`
lookup ran past `_itemsCount` and tripped the `itemIndex >= 0 &&
itemIndex < _itemsCount` assertion inside
`_SliverAnimatedMultiBoxAdaptorState.removeItem`.

The fix captures `_itemsCount - _outgoingItems.length` before mutating
the sliver and uses that visible count for both the "are there enough
items to have a separator?" and "is this the new last item?" checks.
When no removal is in flight `_outgoingItems` is empty and behavior is
unchanged, so existing `.separated` coverage and the `removeAllItems`
regression test from flutter#176452 continue to pass.

## Related issues

Fixes flutter#186361.

Related prior fix for the non-separated `removeAllItems` variant:
flutter#176452 (issue flutter#176362).

## Test plan

- [x] Added a regression test in
`packages/flutter/test/widgets/animated_list_test.dart` that reproduces
the assertion on master and passes with this change: starts a 10s
removal on a non-last item, pumps part of the animation, then removes
the new last item, and asserts no exception is thrown.
- [x] `flutter test
packages/flutter/test/widgets/animated_list_test.dart
packages/flutter/test/widgets/animated_grid_test.dart` — 34 tests pass,
including the existing comprehensive `AnimatedList.separated` test and
the `removeAllItems` regression test from flutter#176452.
- [x] `dart analyze` on the touched files: no issues.
- [x] Manually verified with the reproducer from the issue: before the
fix the assertion fires in the debug console; after the fix the second
removal animates out cleanly.

## AI disclosure

Written with assistance from Claude Code (Anthropic). I reviewed every
change, ran the tests, and verified the fix manually before submitting.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [AI contribution guidelines] and understand my
responsibilities, or I am not using AI tools.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[AI contribution guidelines]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
gabrimatic added a commit to gabrimatic/flutter that referenced this pull request May 21, 2026
…Item

The separator-aware index translation in
_AnimatedScrollViewState._computeItemIndex used _itemsCount, which
counts every entry in the underlying sliver including ones still
animating out. The sliver-level insert/remove APIs are documented
to operate as if outgoing items had already been removed (see the
comment on _SliverAnimatedMultiBoxAdaptorState._indexToItemIndex),
so the separator math must also be done against that live count.

When a removeItem call was followed by an insertItem on the same
position before the removal animation had finished, the inflated
count made _computeItemIndex map the user index to a sliver index
past the new live end. _SliverAnimatedMultiBoxAdaptorState then
shifted that index further to skip the still-outgoing entries and
tripped the bounds assertion in insertItem.

Compute the index against _visibleItemsCount (_itemsCount minus the
number of outgoing animations) so user indices match the live state
that the sliver-level APIs expect, matching the precedent set by
removeAllItems in 61fff25 (flutter#176452).

Fixes flutter#179029
matthewhendrix pushed a commit to matthewhendrix/flutter that referenced this pull request May 23, 2026
flutter#186389)

## Summary

`AnimatedList.separated.removeItem` used the sliver's total child count
(`_itemsCount`) to decide whether the just-removed item was the last
item in the list. That count still includes children animating out from
earlier `removeItem` calls, so while a previous removal was in flight
the new tail of the visible list was misclassified as a middle item. The
separator removal then targeted the wrong index, and once the item being
removed was added to `_outgoingItems` the next `_indexToItemIndex`
lookup ran past `_itemsCount` and tripped the `itemIndex >= 0 &&
itemIndex < _itemsCount` assertion inside
`_SliverAnimatedMultiBoxAdaptorState.removeItem`.

The fix captures `_itemsCount - _outgoingItems.length` before mutating
the sliver and uses that visible count for both the "are there enough
items to have a separator?" and "is this the new last item?" checks.
When no removal is in flight `_outgoingItems` is empty and behavior is
unchanged, so existing `.separated` coverage and the `removeAllItems`
regression test from flutter#176452 continue to pass.

## Related issues

Fixes flutter#186361.

Related prior fix for the non-separated `removeAllItems` variant:
flutter#176452 (issue flutter#176362).

## Test plan

- [x] Added a regression test in
`packages/flutter/test/widgets/animated_list_test.dart` that reproduces
the assertion on master and passes with this change: starts a 10s
removal on a non-last item, pumps part of the animation, then removes
the new last item, and asserts no exception is thrown.
- [x] `flutter test
packages/flutter/test/widgets/animated_list_test.dart
packages/flutter/test/widgets/animated_grid_test.dart` — 34 tests pass,
including the existing comprehensive `AnimatedList.separated` test and
the `removeAllItems` regression test from flutter#176452.
- [x] `dart analyze` on the touched files: no issues.
- [x] Manually verified with the reproducer from the issue: before the
fix the assertion fires in the debug console; after the fix the second
removal animates out cleanly.

## AI disclosure

Written with assistance from Claude Code (Anthropic). I reviewed every
change, ran the tests, and verified the fix manually before submitting.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [AI contribution guidelines] and understand my
responsibilities, or I am not using AI tools.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[AI contribution guidelines]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
Renzo-Olivares pushed a commit to gabrimatic/flutter that referenced this pull request Jun 9, 2026
…Item

The separator-aware index translation in
_AnimatedScrollViewState._computeItemIndex used _itemsCount, which
counts every entry in the underlying sliver including ones still
animating out. The sliver-level insert/remove APIs are documented
to operate as if outgoing items had already been removed (see the
comment on _SliverAnimatedMultiBoxAdaptorState._indexToItemIndex),
so the separator math must also be done against that live count.

When a removeItem call was followed by an insertItem on the same
position before the removal animation had finished, the inflated
count made _computeItemIndex map the user index to a sliver index
past the new live end. _SliverAnimatedMultiBoxAdaptorState then
shifted that index further to skip the still-outgoing entries and
tripped the bounds assertion in insertItem.

Compute the index against _visibleItemsCount (_itemsCount minus the
number of outgoing animations) so user indices match the live state
that the sliver-level APIs expect, matching the precedent set by
removeAllItems in 61fff25 (flutter#176452).

Fixes flutter#179029
creatorpiyush pushed a commit to creatorpiyush/packages that referenced this pull request Jun 10, 2026
Roll Flutter from 2d34167 to 75004a6 (39 revisions)

flutter/flutter@2d34167...75004a6

2025-10-22 [email protected] [Impeller] Add the paint color to the key of the text shadow cache (flutter/flutter#177140)
2025-10-22 [email protected] Roll Skia from 96e75ca8e24b to 928b5cf727c1 (2 revisions) (flutter/flutter#177387)
2025-10-22 [email protected] Roll reclient to version 185 (flutter/flutter#177293)
2025-10-22 [email protected] Roll Skia from b157f6b95f95 to 96e75ca8e24b (4 revisions) (flutter/flutter#177366)
2025-10-22 [email protected] Fix InputDatePickerFormField does not inherit local InputDecorationTheme (flutter/flutter#177090)
2025-10-22 [email protected] Roll Skia from 2c6162c977db to b157f6b95f95 (2 revisions) (flutter/flutter#177362)
2025-10-22 [email protected] Roll Skia from cadf8e7e6fca to 2c6162c977db (4 revisions) (flutter/flutter#177359)
2025-10-22 [email protected] Cleanup after -news_toolkit, +google_fonts, and some leftover `team-go_router` (flutter/flutter#176841)
2025-10-21 [email protected] don't break sheet's snap from physics (flutter/flutter#171157)
2025-10-21 [email protected] Roll Dart SDK from 913c2ae1b367 to c23010c4f9e6 (8 revisions) (flutter/flutter#177348)
2025-10-21 [email protected] Fix typo in comment about screen availibility  (flutter/flutter#177168)
2025-10-21 [email protected] Fix(AnimatedScrollView): exclude outgoing items in removeAllItems (flutter/flutter#176452)
2025-10-21 [email protected] Enable deprecated_member_use_from_same_package for all packages containing tests of Dart fixes defined within the package (flutter/flutter#177341)
2025-10-21 [email protected] Roll Skia from 19bff385f7e8 to cadf8e7e6fca (3 revisions) (flutter/flutter#177331)
2025-10-21 [email protected] Revert "[Android 16] Update `android_engine_vulkan_tests` to Test Against SDK 36 Emulator" (flutter/flutter#177292)
2025-10-21 [email protected] Fix SliverMainAxisGroup.cacheOrigin (flutter/flutter#175760)
2025-10-21 [email protected] Roll Skia from 75c756e029c9 to 19bff385f7e8 (3 revisions) (flutter/flutter#177316)
2025-10-21 [email protected] Fix typo in overlay.dart documentation comment (flutter/flutter#176612)
2025-10-21 [email protected] [ Tool ] Output DTD URI for Flutter web applications (flutter/flutter#177310)
2025-10-21 [email protected] Roll Skia from 982988b472a4 to 75c756e029c9 (1 revision) (flutter/flutter#177305)
2025-10-21 [email protected] Roll Skia from 42ff13a91c80 to 982988b472a4 (8 revisions) (flutter/flutter#177300)
2025-10-21 [email protected] Fix DateRangePickerDialog does not inherit local InputDecorationTheme (flutter/flutter#177086)
2025-10-21 [email protected] Remove references to dart:_js_annotations (flutter/flutter#176698)
2025-10-20 [email protected] Make `FlutterSceneLifeCycleProvider.sceneLifeCycleDelegate` readonly (flutter/flutter#177240)
2025-10-20 [email protected] Make sure that a CupertinoDesktopTextSelectionToolbar doesn't crash i… (flutter/flutter#173964)
2025-10-20 [email protected] Make sure that a BottomSheet doesn't crash in 0x0 environment (flutter/flutter#172229)
2025-10-20 [email protected] Move the Fuchsia SDK to //third_party/fuchsia-sdk (flutter/flutter#177118)
2025-10-20 [email protected] Roll Skia from 641994569415 to 42ff13a91c80 (8 revisions) (flutter/flutter#177283)
2025-10-20 [email protected] Make sure that a NavigationDrawer doesn't crash in 0x0 environment (flutter/flutter#176951)
2025-10-20 [email protected] Fix ink features painting in TabBar. (flutter/flutter#177155)
2025-10-20 [email protected] Make sure that SimpleDialog and SimpleDialogOption do not crash in 0x0 environment (flutter/flutter#174229)
2025-10-20 [email protected] Fix ink features painting in YearPicker. (flutter/flutter#177014)
2025-10-20 [email protected] Update `image.error_builder.0.dart` to replace the emoji with some text  (flutter/flutter#176886)
2025-10-20 [email protected] Roll Skia from ed4294faecde to 641994569415 (4 revisions) (flutter/flutter#177264)
2025-10-20 [email protected] Remove redundant name field form `TargetPlatform` and `XCDeviceEventInterface` enums (flutter/flutter#176890)
2025-10-20 [email protected] Added support to pass in texture type while creating textures. (flutter/flutter#175376)
2025-10-20 [email protected] Roll Packages from 3747006 to d113bbc (6 revisions) (flutter/flutter#177270)
2025-10-20 [email protected] Roll Dart SDK from 2cd2106f2cef to 913c2ae1b367 (2 revisions) (flutter/flutter#177258)
2025-10-20 [email protected] Added link to ClipRect from ImageFilter in the docstring (flutter/flutter#177196)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC [email protected],[email protected] on the revert to ensure that a human
is aware of the problem.

...
via-guy pushed a commit to via-guy/flutter that referenced this pull request Jun 26, 2026
flutter#186389)

## Summary

`AnimatedList.separated.removeItem` used the sliver's total child count
(`_itemsCount`) to decide whether the just-removed item was the last
item in the list. That count still includes children animating out from
earlier `removeItem` calls, so while a previous removal was in flight
the new tail of the visible list was misclassified as a middle item. The
separator removal then targeted the wrong index, and once the item being
removed was added to `_outgoingItems` the next `_indexToItemIndex`
lookup ran past `_itemsCount` and tripped the `itemIndex >= 0 &&
itemIndex < _itemsCount` assertion inside
`_SliverAnimatedMultiBoxAdaptorState.removeItem`.

The fix captures `_itemsCount - _outgoingItems.length` before mutating
the sliver and uses that visible count for both the "are there enough
items to have a separator?" and "is this the new last item?" checks.
When no removal is in flight `_outgoingItems` is empty and behavior is
unchanged, so existing `.separated` coverage and the `removeAllItems`
regression test from flutter#176452 continue to pass.

## Related issues

Fixes flutter#186361.

Related prior fix for the non-separated `removeAllItems` variant:
flutter#176452 (issue flutter#176362).

## Test plan

- [x] Added a regression test in
`packages/flutter/test/widgets/animated_list_test.dart` that reproduces
the assertion on master and passes with this change: starts a 10s
removal on a non-last item, pumps part of the animation, then removes
the new last item, and asserts no exception is thrown.
- [x] `flutter test
packages/flutter/test/widgets/animated_list_test.dart
packages/flutter/test/widgets/animated_grid_test.dart` — 34 tests pass,
including the existing comprehensive `AnimatedList.separated` test and
the `removeAllItems` regression test from flutter#176452.
- [x] `dart analyze` on the touched files: no issues.
- [x] Manually verified with the reproducer from the issue: before the
fix the assertion fires in the debug console; after the fix the second
removal animates out cleanly.

## AI disclosure

Written with assistance from Claude Code (Anthropic). I reviewed every
change, ran the tests, and verified the fix manually before submitting.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [AI contribution guidelines] and understand my
responsibilities, or I am not using AI tools.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[AI contribution guidelines]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

f: scrolling Viewports, list views, slivers, etc. framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AnimatedList throws 'itemIndex >= 0 && itemIndex < _itemsCount': is not true. when removing all items when an item is being removed

3 participants