Skip to content

Toggleable Refactor - #76745

Merged
fluttergithubbot merged 14 commits into
flutter:masterfrom
goderbauer:toggable
Mar 2, 2021
Merged

fluttergithubbot merged 14 commits into
flutter:masterfrom
goderbauer:toggable

Conversation

@goderbauer

@goderbauer goderbauer commented Feb 25, 2021 •

Copy link
Copy Markdown
Member

This started out as a simple fix for #76411, which was caused by RenderToggleable not disposing its GestureRecognizer. Unfortunately, the RenderObject doesn't have a point where it is appropriate to dispose the recognizer.

In this refactoring, I am removing the RenderToggleable class altogether. Instead, I am introducing a ToggleableStateMixin that toggleable widgets (like Switch, Checkbox, Radio) can mix into their state. The ToggleableStateMixin implements logic to trigger state transitions of the toggleable (e.g. when it is tapped). For this, it also manages a bunch of AnimationControllers that drive the transition.

ToggleableStateMixin has no opinion on the visual appearance of the toggleable widget. Users of the ToggleableStateMixin have to provide a CustomPainter, that draws the current visual appearance of the toggleable based on the Animations provided by the mixin.

The idea is that managing the lifecycle of various elements a toggleable needs (e.g. a GestureRecognizer) is easier in the widget layer (i.e. in the ToggleableStateMixin) than it is in the render layer (as it was implemented before in the RenderToggleable).

Prior to this change, concrete Toggleable implementations would subclass RenderToggleable and implement a LeafRenderObjectWidget as well as a StatefulWidget. With this change, they mixin the ToggleableStateMixin into their StatefulWidget and provide a CustomPainter that draws the visual appearance.

The ToggleableStateMixin also implements some common logic (e.g. the FocusableActionDetector) that was previously repeated in the various toggleable widgets.

@flutter-dashboard flutter-dashboard Bot added p: material_ui material_ui package in flutter/packages framework flutter/packages/flutter repository. See also f: labels. labels Feb 25, 2021
@google-cla google-cla Bot added the cla: yes label Feb 25, 2021
@goderbauer goderbauer changed the title Toggable Toggable Refactor Feb 25, 2021

@HansMuller HansMuller left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is a nice refactoring. LGTM

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think it would be fair to qualify this class as material-specific. The animation controllers and their curves work for the material toggle components (but not ToggleButtons I assume) and I don't think developers should expect that we'd lift every opinion in this class into a parameter. In other words, I think the API doc should make it clear that this is something we've factored out of several widgets, not the one and only basis for all toggle buttons.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe explain that it's the first pointer-down or the most recent one?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should this be null if isInteractive is false?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Added a note that it is null if isInteractive is false.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is there a redundant setState() here if the user double-taps? Not sure if this is necessary:

if (isInteractive && downPosition != details.localPosition) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

If the users double taps, the first tap would null out _downPosition in _handleTapEnd - so we have to set it again here for the second tap.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Only call setState if _downPosition is non-null?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe: "Typically wraps a painter"

@goderbauer
goderbauer marked this pull request as ready for review March 1, 2021 22:33
@fluttergithubbot
fluttergithubbot merged commit 14552a9 into flutter:master Mar 2, 2021
@goderbauer
goderbauer deleted the toggable branch March 2, 2021 16:09
goderbauer added a commit that referenced this pull request Mar 2, 2021
goderbauer added a commit that referenced this pull request Mar 2, 2021
goderbauer added a commit to goderbauer/flutter that referenced this pull request Mar 4, 2021
@HansMuller HansMuller changed the title Toggable Refactor Toggleable Refactor Mar 5, 2021
@iulianraduat

Copy link
Copy Markdown

Removing RenderToggleable breaks packages like circular_check_box.
Maybe it can be added back and made to use ToggleableStateMixin 🤔

@tombowers

Copy link
Copy Markdown

I've upgraded from 2.0.4 to 2.2.0. This was a breaking change for me as I was using RenderToggleable for a custom checkbox.

I have no easy migration path for this, other than picking apart the PR and figuring out how to use ToggleableStateMixin, or a complete rewrite of my widget. Just a question, are breaking changes like this normal within a stable major version?

@goderbauer

Copy link
Copy Markdown
Member Author

Flutter's breaking change policy (including when we consider a change to be breaking) is documented here: https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes

@tombowers

Copy link
Copy Markdown

@goderbauer Thanks very much for the link. We normally encourage our developers to perform regular minor version upgrades for dependencies, as such upgrades rarely break public APIs. So I need to make sure I understand that policy in detail.

When developing with Flutter, is it considered bad practice to extend a public class such as RenderToggleable; i.e. could any such class be deleted as long as it's not instantiated in those tests? For instance, could ToggleablePainter be deleted in a future minor version under the same policy? Is there a reference somewhere I can refer to for what is considered to be the "safe" public API for Flutter?

Again, thanks! We love Flutter, we just want to ensure we're using it as intended.

@omidraha

omidraha commented Jun 4, 2021

Copy link
Copy Markdown

I've upgraded from 2.0.4 to 2.2.0. This was a breaking change for me as I was using RenderToggleable for a custom checkbox.

I have no easy migration path for this, other than picking apart the PR and figuring out how to use ToggleableStateMixin, or a complete rewrite of my widget. Just a question, are breaking changes like this normal within a stable major version?

I have the same issue,
After upgrading, The RenderToggleable is not resolvable now.
It used in circular_check_box here.,

Do you have any solution for this?

@tombowers

Copy link
Copy Markdown

Your circular check box is similar to my custom checkbox, so it should be the same process for you.

Your checkbox state class currently returns a RenderToggleable wrapped in a LeafRenderObjectWidget wrapped in a FocusableActionDetector. This is no longer required.

Instead, add the ToggleableStateMixin to your checkbox state class. You then need to implement a ToggleablePainter which will handle the visual appearance of the widget and render any ink splashes etc. You can pretty much take this code from your existing RenderToggleable with a few minor changes. This painter should be instantiated and disposed within your state class, and where you were previously returning the FocusableActionDetector, you now return a call to buildToggleable, passing the painter.

You'll also need to clean up all your state handling to defer to the new mixin. This was a really lengthy process, so in the end it might have been easier to just copy the latest Material Checkbox and manually apply any custom rendering code to its painter.

thuette added a commit to thuette/circular_check_box that referenced this pull request Jun 18, 2021
@nt4f04uNd

Copy link
Copy Markdown
Member

@goderbauer I'm wondering why wasn't it deprecated before being removed? This is a public API and per the breaking change policy it should have been deprecated first, no?

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

Labels

framework flutter/packages/flutter repository. See also f: labels. p: material_ui material_ui package in flutter/packages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants