Skip to content

Conversation

@nate-thegrate
Copy link
Contributor

@nate-thegrate nate-thegrate commented Dec 3, 2024

This PR reworks the AlignTransition widget to enable skipping the "build" stage of the pipeline entirely.

Additionally, the alignment parameter's type has been changed from Animation to ValueListenable, to allow using a ValueNotifier if desired.



I ran a benchmark with some colored boxes to see the performance difference between rebuilding each frame vs. updating the render object directly.

debug (raster) debug (UI) profile (raster) profile (UI)
listenable builder 3.5 ms 24.0 ms 3.4 ms 4.6 ms
custom render object 3.4 ms 6.5 ms 3.4 ms 3.4 ms
improvement 3% 269% 0% 35%

Avoiding rebuilds gives a decent boost in profile mode and a significant improvement in debug mode.


I remember there being some concerns about adding a bunch of new RenderObject subclasses, but this one is short enough that I can copy-paste here:

_RenderAnimatedAlign extends RenderPositionedBox
    with _RenderTransition<ValueListenable<AlignmentGeometry>> {
  _RenderAnimatedAlign({
    required ValueListenable<AlignmentGeometry> listenable,
    super.widthFactor,
    super.heightFactor,
    super.textDirection,
  }) : _listenable = listenable,
       super(alignment: listenable.value);

  @override
  ValueListenable<AlignmentGeometry> _listenable;

  @override
  void _listener() {
    alignment = _listenable.value;
  }

Obviously, a real-world app is going to have a bit more going on than a few "transition" widgets, but still—30 lines of code for a 30% performance boost is a pretty sweet deal!

@github-actions github-actions bot added the framework flutter/packages/flutter repository. See also f: labels. label Dec 3, 2024
@nate-thegrate

This comment was marked as resolved.

@davidhicks980
Copy link
Contributor

davidhicks980 commented Dec 10, 2024

Is there any benefit to using a final animation rather than allowing users to swap out animations? I feel that could lead to some confusion.

@nate-thegrate
Copy link
Contributor Author

@davidhicks980 good question! Since I personally have never wanted to swap out a transition widget's animation, I made it final here to simplify the implementation.

But I agree that there's a potential for confusion, and with that in mind it might be best to keep it flexible.

@davidhicks980
Copy link
Contributor

@nate-thegrate that makes sense! I was trying to figure out if there was a performance benefit, but code simplicity is not insignificant.

@nate-thegrate
Copy link
Contributor Author

Ooh, speaking of code simplicity—since a bunch of transitions are being defined in the same library, a private mixin could work pretty well.

class _RenderAnimatedAlign extends RenderPositionedBox with _RenderTransition {
  // ...
}

Might end up being even more concise than the code with a final animation!

@davidhicks980
Copy link
Contributor

davidhicks980 commented Dec 10, 2024

Good idea. I almost wonder whether a generic is even needed since the mixin isn't using the value and the _listenable is abstract. In code:

mixin _RenderTransition on RenderBox {
  Listenable get listenable => _listenable;
  abstract Listenable _listenable;
  set listenable(Listenable value) {
    if (value == _listenable) {
      return;
    }
    _listenable.removeListener(onListenableUpdate);
    _listenable = value;
    if (attached) {
      value.addListener(onListenableUpdate..call());
    }
  }

  // I changed the name of this method to be a little more specific -- not sure if that's a good idea.
  void onListenableUpdate();

  @override
  void attach(PipelineOwner owner) {
    super.attach(owner);
    _listenable.addListener(onListenableUpdate);
  }

  @override
  void detach() {
    _listenable.removeListener(onListenableUpdate);
    super.detach();
  }
}

Though there could be some benefits to typing the mixin. Either way, good idea!

@nate-thegrate
Copy link
Contributor Author

Thanks!

Yeah, maybe the method should be renamed (though probably to handle___ instead of on___, per the style guide). I don't have a strong preference either way.

I tried removing the type argument, and it almost worked:

setter error


…and then just for kicks, I decided to see if covariant would fix it:

class _RenderAnimatedAlign extends RenderPositionedBox with _RenderTransition {
  // ...

  @override
  covariant ValueListenable<AlignmentGeometry> _listenable;
}

and it worked!


I'm slightly leaning toward keeping the type argument: if at some point we make RenderAnimatedAlign a public class, it'd be nice to have that type information on the getter & setter.

But I've never used that keyword on a member variable before; learned something new today!

Widget? child,
);

mixin _RenderTransition<L extends Listenable> on RenderObject {
Copy link
Member

Choose a reason for hiding this comment

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

Since this private mixin has only one use: Why not inline this logic into the only place where it is used?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I like the way you think 😏
Perhaps you'd be interested in sharing this opinion over at #158255 (comment), since there's a private class that's used in 1 place, unnecessarily increasing the size of the element tree.

My intent was for this mixin to be used for each "transition" widget that simply passes the params to a RO widget.

I can go ahead and use this mixin in a few other places, or I'm also happy to save that for a future PR.

Copy link
Member

Choose a reason for hiding this comment

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

Maybe do at least one more in this PR to justify the existence of this mixin and also to proof that the abstraction works for other cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good! This PR is now set up for both AlignTransition and DecoratedBoxTransition.

@nate-thegrate nate-thegrate force-pushed the align-transition branch 3 times, most recently from 0144d95 to bfa2834 Compare January 9, 2025 21:39
@nate-thegrate nate-thegrate changed the title AlignTransition without rebuilds AlignTransition / DecoratedBoxTransition without rebuilds Jan 9, 2025
@github-actions github-actions bot added the f: cupertino flutter/packages/flutter/cupertino repository label Jan 9, 2025
@Piinks Piinks removed the f: cupertino flutter/packages/flutter/cupertino repository label Jan 15, 2025
Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

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

LGTM

@github-actions github-actions bot added the f: cupertino flutter/packages/flutter/cupertino repository label Jan 22, 2025
@nate-thegrate
Copy link
Contributor Author

I guess the good news is: we won't be needing to go through those Google Testing failures!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

f: cupertino flutter/packages/flutter/cupertino repository framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants