Skip to content

Document why Widget.operator== is marked as non-virtual #49490

Closed

Description

@rrousselGit

Why is Widget.operator== marked as non-virtual?

Trying to override == on a widget triggers a warning, but there's no explanation on why (neither in the warning nor in the sources), and is not immediately obvious as to why we shouldn't do so.

Activity

  1. added
    d: api docsIssues with https://api.flutter.dev/
    frameworkflutter/packages/flutter repository. See also f: labels.
    on Jan 26, 2020
  2. dnfield commented on Jan 26, 2020

    @dnfield
    Contributor

    We should probably document this.

    I'm not sure why you'd want to override it though. Widgets are expected to be immutable. Even though Dart can't really guarantee immutability of e.g. a child list, the framework breaks if you mutate a property of the widget.

    The default implementations of equals (i.e. identical) and hashCode (i.e. identityHashCode) should be good enough for that, and implementing it otherwise could slow things down when the framework goes to compare widgets in the tree.

    /cc @Hixie @goderbauer who probably have more insight into this.

  3. rrousselGit commented on Jan 26, 2020

    @rrousselGit
    ContributorAuthor

    Flutter optimize rebuilds around the == operator.

    Overriding == allows filtering rebuilds in situations that would otherwise rebuild for no reason.

    It's mostly for leaf nodes though.

  4. dnfield commented on Jan 26, 2020

    @dnfield
    Contributor

    Can you give an example?

  5. rrousselGit commented on Jan 26, 2020

    @rrousselGit
    ContributorAuthor
    class Leaf extends StatelessWidget {
      const Leaf({Key key, this.label}) : super(key: key);
    
      final String label;
    
      @override
      bool operator ==(Object other) {
        return other is Leaf && other.key == key && other.label == label;
      }
    
      @override
      int get hashCode => hashValues(key, label);
    
      @override
      Widget build(BuildContext context) {
        return Text(label);
      }
    }

    This makes the widget behaves like a const instance (don't rebuild if didn't change), but works with dynamic values.

  6. goderbauer commented on Jan 27, 2020

    @goderbauer
    Member

    Some context for why this was done is in the linked from the PR that added the @nonVirtual annotation: #46900

  7. dnfield commented on Jan 27, 2020

    @dnfield
    Contributor

    @goderbauer - I did look through that and saw a few places where @Hixie said not to do it, but never why.

  8. rrousselGit commented on Jan 27, 2020

    @rrousselGit
    ContributorAuthor

    It was likely about non-leaf widgets.

    If a widget has a child, and that child too overrides ==, then we could end up effectively comparing the entire widget tree.

    But I don't see any issue with leaf widgets though.

  9. Hixie commented on Jan 27, 2020

    @Hixie
    Contributor

    Do we have any benchmarks for the leaf case? I wouldn't be surprised if the mere existence of that operator== made everything more expensive (since it turns a single-dispatch into a dynamic dispatch for every single widget comparison ever).

  10. jonahwilliams commented on Jan 27, 2020

    @jonahwilliams
    Contributor

    I think if we don't want developers to implement Widget.==, we should just use identical instead

  11. dnfield commented on Jan 27, 2020

    @dnfield
    Contributor

    That could be confusing - someone expects the framework to be using their overridden ==, but it's actually just using identical and returning an unexpected result...

  12. jonahwilliams commented on Jan 27, 2020

    @jonahwilliams
    Contributor

    Regardless of what choice we make here (keeping nonVirtual, using identity) we won't be able to get away with documenting it extensively. equality as a concept is too heavily overloaded

  13. jonahwilliams commented on Jan 27, 2020

    @jonahwilliams
    Contributor

    I meant "without" documenting it extensively

  14. t-artikov commented on Feb 9, 2020

    @t-artikov

    I am pretty sure there are cases when Widget.operator== overriding is useful.
    Consider an example: https://dartpad.dev/c20abd67d2cde74263ba1b49b368e062
    There is a dynamic list of Items displayed in ListView. Overriding the operator== allows to avoid rebuilding all ItemViews in the viewport on Item addition, only a new one is built (or none if it happens outside the viewport).
    This reduces worst frame build time from 25ms to less than 16ms on my device in profiling mode.

  15. 41 remaining items

  16. github-actions commented on Dec 24, 2021

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  17. locked as resolved and limited conversation to collaborators on Dec 24, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

d: api docsIssues with https://api.flutter.dev/frameworkflutter/packages/flutter repository. See also f: labels.waiting for PR to land (fixed)A fix is in flight

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions