Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Add AnimatedList with separators #144899

Merged

Conversation

Peetee06
Copy link
Contributor

@Peetee06 Peetee06 commented Mar 10, 2024

This PR adds AnimatedList.separated. A widget like an AnimatedList with animated separators. animated_list_separated.0.dart extends animated_list.0.dart to work with AnimatedList.separated

Related issue: #48226

Pre-launch Checklist

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

@github-actions github-actions bot added framework flutter/packages/flutter repository. See also f: labels. f: scrolling Viewports, list views, slivers, etc. d: api docs Issues with https://api.flutter.dev/ d: examples Sample code and demos labels Mar 10, 2024
@Peetee06
Copy link
Contributor Author

I'm unsure about how to best handle removeItem und removeAllItems. If we want to reuse _AnimatedScrollViewState, we need to prevent users from using those, because they don't come with a parameter for building removed separators. Throwing an exception was the best I can could come up with.

Is there a better way to do this? Would it be preferred to re-implement _AnimatedScrollViewState with separators?

@Piinks Piinks self-requested a review March 15, 2024 19:20
@Peetee06 Peetee06 marked this pull request as ready for review March 24, 2024 12:58
// Build a separator for items that have been removed from the list.
/// The widget will be used by the [AnimatedListSeparatedState.removeSeparatedItem] method's
/// `separatorBuilder` parameter.
Widget _buildRemovedSeparator(int item , BuildContext context, Animation<double> animation) => SizeTransition(
Copy link
Contributor

Choose a reason for hiding this comment

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

trivial nit: remove the extra space before the comma

// Insert the "next item" into the list model.
void _insert() {
final int index = _selectedItem == null ? _list.length : _list.indexOf(_selectedItem!);
_list.insert(index, _nextItem++);
Copy link
Contributor

Choose a reason for hiding this comment

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

to make this code clearer to new programmers, i recommend increasing _nextItem separate from inserting it into the list, so it's 100% clear whether the inserted value is the pre-increment or post-increment value.

Comment on lines 128 to 138
),
),
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: indentation

ErrorSummary('AnimatedListSeparated.of() called with a context that does not contain an AnimatedListSeparated.'),
ErrorDescription(
'No AnimatedListSeparated ancestor could be found starting'
' from the context that was passed to AnimatedListSeparated.of().',
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: we prefer having the space on the end of the line before the cut

(if this is copy/pasted from some other code, bonus points for fixing that code too)

Comment on lines 466 to 476
/// This method is not supported by [AnimatedListSeparatedState]. Use [removeSeparatedItem] instead.
@override
void removeItem(int index, AnimatedRemovedItemBuilder builder, { Duration duration = _kDuration }) {
throw Exception('Separated list items can not be removed with this method. Use removeSeparatedItem instead.');
}

/// This method is not supported by [AnimatedListSeparatedState]. Use [removeAllSeparatedItems] instead.
@override
void removeAllItems(AnimatedRemovedItemBuilder builder, { Duration duration = _kDuration }) {
throw Exception('Separated list items can not be removed with this method. Use removeAllSeparatedItems instead.');
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this would lead to confusion. Can we instead find some way to let removeItem be how you remove items, for consistency across the various APIs?

Copy link
Contributor

Choose a reason for hiding this comment

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

e.g. have the superclass have an @protected internalRemoveItem or some such, and then have the subclasses other than this one introduce a removeItem that just forwards everything to internalRemoveItem?

int _computeItemIndex(int index) {
if (index == 0) {
return index;
} else {
Copy link
Contributor

Choose a reason for hiding this comment

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

avoid else after return

///
/// [AnimatedListSeparated] item input handlers can also refer to their [AnimatedListSeparatedState]
/// with the static [AnimatedListSeparated.of] method.
class AnimatedListSeparatedState extends _AnimatedScrollViewState<AnimatedListSeparated> {
Copy link
Contributor

Choose a reason for hiding this comment

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

@goderbauer / @Piinks
We probably shouldn't do it in this PR but the fact that we couldn't implement AnimatedListSeparatedState outside of the framework because _AnimatedScrollViewState is private probably means we have some magic here and should consider making _AnimatedScrollViewState public.

Copy link
Contributor

@Piinks Piinks left a comment

Choose a reason for hiding this comment

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

Hi @Peetee06 welcome! Thanks very much for contributing.

Rather than a new class, we would typically approach this with a special constructor in the existing AnimatedList class. The ListView widget is a good example of this, with its ListView.separated constructor. There is a fair bit of duplicated code between the AnimatedListSeparated here and the AnimatedList that already exists in the framework, which would mean we need to maintain both, ensuring when we fix a bug in one class, we remember to go fix it as well in the other class, and so on.

Would you be interested in implementing this feature in the existing class?

@Peetee06
Copy link
Contributor Author

Hi @Piinks,

that was actually the first thing I tried, but I wasn't able to find a solution for the removeItem method. It requires a single removal builder for the AnimatedList but two removal builders for the AnimatedList with separators, because we need to remove the separators as well. I could implement a second method like I did already but I agree with @Hixie here, that having two removal methods, where one is unused for each AnimatedList might lead to confusion. Do you have an idea on how to implement it as AnimatedList.separated while not having methods that are only used in either AnimatedList xor AnimatedList.separated?

I like @Hixie's suggestion about replacing the superclass's removeItem with a protected member to be able to implement both a removeItem with a single removal builder for the default AnimatedList and one with two removal builders for the AnimatedList.separated if we would choose to stay with a separated AnimatedListSeparated class. What do you think about this refactoring?

I would be happy to improve the code so that it has a clean, developer-friendly API.

@Piinks
Copy link
Contributor

Piinks commented Apr 3, 2024

that was actually the first thing I tried, but I wasn't able to find a solution for the removeItem method. It requires a single removal builder for the AnimatedList but two removal builders for the AnimatedList with separators, because we need to remove the separators as well. I could implement a second method like I did already but I agree with @Hixie #144899 (comment), that having two removal methods, where one is unused for each AnimatedList might lead to confusion. Do you have an idea on how to implement it as AnimatedList.separated while not having methods that are only used in either AnimatedList xor AnimatedList.separated?

Would the removal of the separators just be expected by the user without having to call it themselves? I feel like that's what I would expect. In that case, we could just internally remove the leftover separators when items are removed. Maybe the constructor can have the user provide the add and remove builders just for the separators then?

@Piinks
Copy link
Contributor

Piinks commented Apr 3, 2024

What do you think about this refactoring?

I think these are great avenues to explore, thanks for working on this! :)

@Piinks
Copy link
Contributor

Piinks commented May 1, 2024

@Peetee06 is this ready for another review?

@Peetee06
Copy link
Contributor Author

Peetee06 commented May 2, 2024

@Piinks yes! I wrote a few infos about it and two different branches with other possible solutions in the "hackers-new" channel on discord. Here is a summary:

All in all, we now have 3 possible solutions:

Use @Protected internal methods for _AnimatedScrollViewState and pass a removedSeparatorBuilder to removeItem and removeAllItems: #144899
Pass removedSeparatorBuilder to the constructor of AnimatedListSeparated. No need to make _AnimatedScrollViewState's methods protected, because they can just be overriden now: https://github.com/Peetee06/flutter/tree/feat/add_AnimatedListSeparated_with_separator_removal_builder
Same as 2, but adding the removedSeparatorBuilder to _AnimatedScrollView and adding conditional logic to _AnimatedScrollViewState's methods to handle the case of having separators or not: https://github.com/Peetee06/flutter/tree/feature/add_AnimatedList_separated

From a user's perspective, I like solution 3 the most, because the API seems the cleanest and is familiar in regards to ListView.separated.

Looking forward to hearing your opinion, or ideas on how to improve on these approaches.

Copy link
Contributor

@Piinks Piinks left a comment

Choose a reason for hiding this comment

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

Wow thank you so much for digging into this so thoroughly! Sorry it took me a while to review all of the options you presented, with sample code too, so grateful you included that. :)

I really like option 3 too, consistency across APIs is really wonderful when we can achieve it. Shall we go with 3 then?

This is a really fantastic contribution for a first PR, thank you again.

@Peetee06
Copy link
Contributor Author

Peetee06 commented May 8, 2024

Sounds good! I will update this PR to use option 3 tomorrow. :)

@Peetee06
Copy link
Contributor Author

Peetee06 commented May 9, 2024

@Hixie @Piinks
The PR is now ready for review.

There is a limitation regarding removedSeparatorBuilder that is observable in the example in animated_list_separated.0.dart. As far as I can tell, there is no way to use the removed item for building the removed separator. The ListModel.removeAt method removes the item from the list, while storing it in a variable. It then uses that item in the removedItemBuilder. Because we are passing the removedSeparatorBuilder to the AnimatedList.separated constructor instead of removeItem, we don't have access to the item when the removedItemBuilder is called.

Let me know if we should consider more options, or should just go with this solution for now.

Looking forward to your reviews! :)

@goderbauer goderbauer requested a review from Piinks May 14, 2024 22:04
Copy link
Contributor

@Piinks Piinks left a comment

Choose a reason for hiding this comment

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

Thank you for the updates, and for your patience!

Comment on lines +105 to +106
/// [MediaQuery]'s padding. To avoid this behavior, override with a
/// zero [padding] property.
Copy link
Contributor

Choose a reason for hiding this comment

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

Should 'zero' here be [EdgeInsets.zero]?

Copy link
Contributor Author

@Peetee06 Peetee06 May 25, 2024

Choose a reason for hiding this comment

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

Not sure, I copy pasted this from [AnimatedList]'s docstring :)
Maybe @HansMuller or @TahaTesser can give more insights?

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm. We should probably know what these docs mean before we commit this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Depending on the use case one might want to remove only the padding for a specific side. So IMO "override with a zero [padding] property." is more fitting than "override with EdgeInsets.zero [padding] property."

What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

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

SGTM! Thanks for verifying!

///
/// {@tool snippet}
/// The following example demonstrates how to override the default top and
/// bottom padding using [MediaQuery.removePadding].
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this another way, other than zero padding?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

see answer regarding 'zero'

packages/flutter/lib/src/widgets/animated_scroll_view.dart Outdated Show resolved Hide resolved
packages/flutter/lib/src/widgets/animated_scroll_view.dart Outdated Show resolved Hide resolved
packages/flutter/lib/src/widgets/animated_scroll_view.dart Outdated Show resolved Hide resolved
@Peetee06 Peetee06 force-pushed the feature/add_AnimatedListSeparated branch 2 times, most recently from ae398f4 to be85aee Compare May 26, 2024 04:37
Copy link
Contributor

@Piinks Piinks left a comment

Choose a reason for hiding this comment

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

Looks like it's just a few docs nits remaining here, very nearly ready to land! :)

Comment on lines 85 to 91
return SizeTransition(
sizeFactor: animation,
child: ItemSeparator(
animation: animation,
item: null,
)
);
Copy link
Contributor

Choose a reason for hiding this comment

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

The indentation here is off, can you fix it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed :)

Comment on lines +105 to +106
/// [MediaQuery]'s padding. To avoid this behavior, override with a
/// zero [padding] property.
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm. We should probably know what these docs mean before we commit this.

packages/flutter/lib/src/widgets/animated_scroll_view.dart Outdated Show resolved Hide resolved
@Peetee06 Peetee06 force-pushed the feature/add_AnimatedListSeparated branch from 62d9f7a to a040676 Compare May 31, 2024 17:10
Copy link
Contributor

@Piinks Piinks left a comment

Choose a reason for hiding this comment

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

Flutter_LGTM

Thanks for all of your hard work on this! 🎉

@Peetee06 Peetee06 force-pushed the feature/add_AnimatedListSeparated branch from 41226da to 03d0fcc Compare June 5, 2024 04:55
@nate-thegrate nate-thegrate added the autosubmit Merge PR when tree becomes green via auto submit App label Jun 5, 2024
@auto-submit auto-submit bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Jun 5, 2024
Copy link
Contributor

auto-submit bot commented Jun 5, 2024

auto label is removed for flutter/flutter/144899, due to - The status or check suite Linux build_tests_1_3 has failed. Please fix the issues identified (or deflake) before re-applying this label.

@nate-thegrate nate-thegrate added the autosubmit Merge PR when tree becomes green via auto submit App label Jun 5, 2024
@Peetee06
Copy link
Contributor Author

Peetee06 commented Jun 5, 2024

A build check fails with

Execution failed for task ':app:packageDebug'.
> A failure occurred while executing com.android.build.gradle.tasks.PackageAndroidArtifact$IncrementalSplitterRunnable
> java.lang.OutOfMemoryError (no error message)

Is that related to the broken tree? Can I do anything to fix this? OutOfMemoryError to me looks unrelated to the changes.

@auto-submit auto-submit bot merged commit 39472d9 into flutter:master Jun 5, 2024
73 checks passed
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jun 5, 2024
@nate-thegrate
Copy link
Member

A build check fails with

Execution failed for task ':app:packageDebug'.
> A failure occurred while executing com.android.build.gradle.tasks.PackageAndroidArtifact$IncrementalSplitterRunnable
> java.lang.OutOfMemoryError (no error message)

Is that related to the broken tree? Can I do anything to fix this? OutOfMemoryError to me looks unrelated to the changes.

I believe that error was a flake—we just needed to re-run the test :)

When that happens in the future, feel free to click the "Rebase branch" button, or you can wait for somebody with commit access to manually reboot the failing tests.

engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jun 5, 2024
auto-submit bot pushed a commit to flutter/packages that referenced this pull request Jun 5, 2024
flutter/flutter@c246ecd...27e0656

2024-06-05 98614782+auto-submit[bot]@users.noreply.github.com Reverts "TreeSliver & associated classes (#147171)" (flutter/flutter#149754)
2024-06-05 p.trost93@gmail.com Feature: Add AnimatedList with separators (flutter/flutter#144899)
2024-06-05 yjbanov@google.com make output of flutter run web tests verbose (flutter/flutter#149694)
2024-06-05 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Request focus if `SemanticsAction.focus` is sent to a focusable widget (#142942)" (flutter/flutter#149741)
2024-06-05 engine-flutter-autoroll@skia.org Roll Flutter Engine from e88469090fed to 11a32d43e3f6 (2 revisions) (flutter/flutter#149699)
2024-06-05 gspencergoog@users.noreply.github.com Request focus if `SemanticsAction.focus` is sent to a focusable widget (flutter/flutter#142942)
2024-06-04 engine-flutter-autoroll@skia.org Roll Flutter Engine from 3dd40156afb6 to e88469090fed (2 revisions) (flutter/flutter#149695)
2024-06-04 katelovett@google.com TreeSliver & associated classes (flutter/flutter#147171)
2024-06-04 engine-flutter-autoroll@skia.org Roll Flutter Engine from fe00f023666c to 3dd40156afb6 (3 revisions) (flutter/flutter#149692)
2024-06-04 49699333+dependabot[bot]@users.noreply.github.com Bump github/codeql-action from 3.25.7 to 3.25.8 (flutter/flutter#149691)
2024-06-04 47866232+chunhtai@users.noreply.github.com Prepares semantics_update_test for upcoming heading level changes (flutter/flutter#149671)
2024-06-04 34871572+gmackall@users.noreply.github.com Identify and re-throw our dependency checking errors in `flutter.groovy` (flutter/flutter#149609)
2024-06-04 hans.muller@gmail.com Scrollbar thumb drag gestures now produce one start and one end scroll notification (flutter/flutter#146654)
2024-06-04 15619084+vashworth@users.noreply.github.com Disable sandboxing for macOS apps and tests in CI (flutter/flutter#149618)
2024-06-04 engine-flutter-autoroll@skia.org Roll Flutter Engine from e211c43f3dc1 to fe00f023666c (3 revisions) (flutter/flutter#149680)
2024-06-04 gspencergoog@users.noreply.github.com Allow `find.byTooltip` to use a RegEx (flutter/flutter#149348)
2024-06-04 engine-flutter-autoroll@skia.org Roll Flutter Engine from a6aa5d826649 to e211c43f3dc1 (8 revisions) (flutter/flutter#149658)

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 dit@google.com,rmistry@google.com,stuartmorgan@google.com on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
arc-yong pushed a commit to Arctuition/packages-arc that referenced this pull request Jun 14, 2024
flutter/flutter@c246ecd...27e0656

2024-06-05 98614782+auto-submit[bot]@users.noreply.github.com Reverts "TreeSliver & associated classes (#147171)" (flutter/flutter#149754)
2024-06-05 p.trost93@gmail.com Feature: Add AnimatedList with separators (flutter/flutter#144899)
2024-06-05 yjbanov@google.com make output of flutter run web tests verbose (flutter/flutter#149694)
2024-06-05 98614782+auto-submit[bot]@users.noreply.github.com Reverts "Request focus if `SemanticsAction.focus` is sent to a focusable widget (#142942)" (flutter/flutter#149741)
2024-06-05 engine-flutter-autoroll@skia.org Roll Flutter Engine from e88469090fed to 11a32d43e3f6 (2 revisions) (flutter/flutter#149699)
2024-06-05 gspencergoog@users.noreply.github.com Request focus if `SemanticsAction.focus` is sent to a focusable widget (flutter/flutter#142942)
2024-06-04 engine-flutter-autoroll@skia.org Roll Flutter Engine from 3dd40156afb6 to e88469090fed (2 revisions) (flutter/flutter#149695)
2024-06-04 katelovett@google.com TreeSliver & associated classes (flutter/flutter#147171)
2024-06-04 engine-flutter-autoroll@skia.org Roll Flutter Engine from fe00f023666c to 3dd40156afb6 (3 revisions) (flutter/flutter#149692)
2024-06-04 49699333+dependabot[bot]@users.noreply.github.com Bump github/codeql-action from 3.25.7 to 3.25.8 (flutter/flutter#149691)
2024-06-04 47866232+chunhtai@users.noreply.github.com Prepares semantics_update_test for upcoming heading level changes (flutter/flutter#149671)
2024-06-04 34871572+gmackall@users.noreply.github.com Identify and re-throw our dependency checking errors in `flutter.groovy` (flutter/flutter#149609)
2024-06-04 hans.muller@gmail.com Scrollbar thumb drag gestures now produce one start and one end scroll notification (flutter/flutter#146654)
2024-06-04 15619084+vashworth@users.noreply.github.com Disable sandboxing for macOS apps and tests in CI (flutter/flutter#149618)
2024-06-04 engine-flutter-autoroll@skia.org Roll Flutter Engine from e211c43f3dc1 to fe00f023666c (3 revisions) (flutter/flutter#149680)
2024-06-04 gspencergoog@users.noreply.github.com Allow `find.byTooltip` to use a RegEx (flutter/flutter#149348)
2024-06-04 engine-flutter-autoroll@skia.org Roll Flutter Engine from a6aa5d826649 to e211c43f3dc1 (8 revisions) (flutter/flutter#149658)

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 dit@google.com,rmistry@google.com,stuartmorgan@google.com on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Aug 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
autosubmit Merge PR when tree becomes green via auto submit App d: api docs Issues with https://api.flutter.dev/ d: examples Sample code and demos 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.

4 participants