Skip to content

Conversation

@kazbeksultanov
Copy link
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.

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

@gemini-code-assist gemini-code-assist bot left a comment

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.

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
Contributor Author

@chunhtai @chinmaygarde can I ask for review?

@justinmc justinmc requested a review from Piinks October 7, 2025 22:06
@kazbeksultanov
Copy link
Contributor Author

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

@kazbeksultanov
Copy link
Contributor Author

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

Copy link
Contributor

@justinmc justinmc left a comment

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
Contributor Author

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

Copy link
Contributor

@justinmc justinmc left a comment

Choose a reason for hiding this comment

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

LGTM with nits 👍

…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.
Copy link
Contributor

@chunhtai chunhtai left a comment

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
Copy link
Contributor

auto-submit bot commented Oct 20, 2025

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
Contributor

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

Copy link
Contributor

@justinmc justinmc left a comment

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 2d3416713fe8 to 75004a639ae4 (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]>
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