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

Do not rebuild when TickerMode changes #93166

Merged
merged 2 commits into from
Nov 9, 2021

Conversation

goderbauer
Copy link
Member

@goderbauer goderbauer commented Nov 5, 2021

Fixes #93032.

Prior to this change, all widgets using the TickerProviderStateMixin would rebuild unnecessarily when the TickerMode changed. That's because the provider mixin created a dependency on TickerMode. The rebuild is unnecessary, because a changing ticker mode doesn't have any visual effects. All it needs to do is disable/enable the vended Tickers. When the Tickers start ticking again, their listeners may cause rebuilds independently e.g. to advance an animation.

Also fixes an issue where in certain cases EditableTextState would instantiate AnimationControllers in dispose only to dispose them.

@flutter-dashboard flutter-dashboard bot added a: text input Entering text in a text field or keyboard related problems framework flutter/packages/flutter repository. See also f: labels. labels Nov 5, 2021
@google-cla google-cla bot added the cla: yes label Nov 5, 2021
@@ -1808,12 +1807,12 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
_internalScrollController?.dispose();
_currentAutofillScope?.unregister(autofillId);
widget.controller.removeListener(_didChangeTextEditingValue);
_floatingCursorResetController.dispose();
_floatingCursorResetController?.dispose();
Copy link
Contributor

Choose a reason for hiding this comment

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

How do we get a dispose without an init state?

Copy link
Contributor

Choose a reason for hiding this comment

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

also nit: null the variable after disposing it?

Copy link
Member Author

Choose a reason for hiding this comment

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

How do we get a dispose without an init state?

These weren't initialized in initState. Instead, the variable was marked as late, which initializes them on first access. If the floating cursor is never shown throughout the lifetime of this object (which is possible), then the first access was in dispose here.

Copy link
Member Author

Choose a reason for hiding this comment

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

nulling out: Done.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we add a test to make sure we don't regress this? Specifically a test that verifies we don't do whatever work these controllers would drive if we just immediately dispose the widget?

Copy link
Member Author

Choose a reason for hiding this comment

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

Hm, I don't see a good way to test this. The test would have to verify that dispose doesn't instantiate any AnimationControllers.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ahh agreed, I misunderstood what's going on here. Filed dart-lang/linter#3058 which could help with this.

Comment on lines 74 to 76
/// [TickerMode] ancestor, it is up to the [Widget] to obtain a new
/// [ValueNotifier] from the new ancestor [TickerMode] by calling this method
/// again. [StatefulWidget]s can, for example, do this in [State.activate].
Copy link
Contributor

Choose a reason for hiding this comment

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

Express this more prescriptively: if the widget is moved, it must unsubscribe from the old notifier and subscribe to any new one that might be available, the correct place to do this is state.deactivate/activate.

Copy link
Member Author

Choose a reason for hiding this comment

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

Re-wrote this section to incorporate that information.

Copy link
Contributor

@dnfield dnfield left a comment

Choose a reason for hiding this comment

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

Seems fine overall

///
/// While the [ValueNotifier] is stable for the lifetime of the surrounding
/// [TickerMode], calling this method does not establish a dependency between
/// the `context` and the [TickerMode]. When the [Widget] owning the `context`
Copy link
Contributor

Choose a reason for hiding this comment

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

Explain the difference between establishing a dependency or not and why you'd want to avoid it (unnecessarily rebuilding to stop an animation controller, building anyway once the animation controller is started)

Copy link
Member Author

Choose a reason for hiding this comment

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

Re-wrote this section to incorporate that information.

Copy link
Member Author

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

PTAL

@@ -1808,12 +1807,12 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
_internalScrollController?.dispose();
_currentAutofillScope?.unregister(autofillId);
widget.controller.removeListener(_didChangeTextEditingValue);
_floatingCursorResetController.dispose();
_floatingCursorResetController?.dispose();
Copy link
Member Author

Choose a reason for hiding this comment

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

nulling out: Done.

Comment on lines 74 to 76
/// [TickerMode] ancestor, it is up to the [Widget] to obtain a new
/// [ValueNotifier] from the new ancestor [TickerMode] by calling this method
/// again. [StatefulWidget]s can, for example, do this in [State.activate].
Copy link
Member Author

Choose a reason for hiding this comment

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

Re-wrote this section to incorporate that information.

///
/// While the [ValueNotifier] is stable for the lifetime of the surrounding
/// [TickerMode], calling this method does not establish a dependency between
/// the `context` and the [TickerMode]. When the [Widget] owning the `context`
Copy link
Member Author

Choose a reason for hiding this comment

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

Re-wrote this section to incorporate that information.

@dnfield
Copy link
Contributor

dnfield commented Nov 8, 2021

Also, it seems like we should have a test that routes do not rebuild when their ticker mode changes to muted and they are covered.

@skia-gold
Copy link

Gold has detected about 18 new digest(s) on patchset 2.
View them at https://flutter-gold.skia.org/cl/github/93166

@goderbauer
Copy link
Member Author

goderbauer commented Nov 8, 2021

Also, it seems like we should have a test that routes do not rebuild when their ticker mode changes to muted and they are covered.

That's what the test "Ticking widgets in old route do not rebuild when new route is pushed" in this PR is covering.

Copy link
Contributor

@dnfield dnfield left a comment

Choose a reason for hiding this comment

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

LGTM

@fluttergithubbot
Copy link
Contributor

This pull request is not suitable for automatic merging in its current state.

  • The status or check suite Linux web_canvaskit_tests_5 has failed. Please fix the issues identified (or deflake) before re-applying this label.

@goderbauer
Copy link
Member Author

Curiously, the test that's failing on the Linux web_canvaskit_tests_5 shard...

05:03 +616 ~16 -1: test/cupertino/context_menu_action_test.dart: turns grey when pressed and held [E]                                                                                                  
  Test failed. See exception logs above.
  The test description was: turns grey when pressed and held

... is supposed to be skipped:

'test/cupertino/context_menu_action_test.dart',

@goderbauer
Copy link
Member Author

Looks like the test has since been excluded from running: #93268

Rebasing and trying again...

@zanderso
Copy link
Member

There were some nice improvements to flutter gallery frame build times on this change: https://flutter-flutter-perf.skia.org/e/?begin=1635352385&end=1637009732&keys=X868dfa49d7994e8de2106df6966dcc25&requestType=0&xbaroffset=26521

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a: text input Entering text in a text field or keyboard related problems framework flutter/packages/flutter repository. See also f: labels.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Option to not rebuild when inherited widget notifies dependents
5 participants