Make ParentDataWidget usable with different ancestor RenderObjectWidget types#48541
Make ParentDataWidget usable with different ancestor RenderObjectWidget types#48541fluttergithubbot merged 8 commits intoflutter:masterfrom
Conversation
| /// * [StatefulWidget] and [State], for widgets that can build differently | ||
| /// several times over their lifetime. | ||
| abstract class ParentDataWidget<T extends RenderObjectWidget> extends ProxyWidget { | ||
| abstract class ParentDataWidget<T extends ParentData> extends ProxyWidget { |
There was a problem hiding this comment.
Is this not a breaking change?
There was a problem hiding this comment.
Yes and No. :)
As far as I can tell this will not break any tests. However, I will be writing a migration guide for people that have custom ParentDataWidgets (see PR description :P )
| if (ancestor is ParentDataElement<RenderObjectWidget>) | ||
| return ancestor; | ||
| if (ancestor is ParentDataElement<ParentData>) { | ||
| result = ancestor as ParentDataElement<ParentData>; |
There was a problem hiding this comment.
I believe that either the as isn't necessary here anymore, or that if it is you should just be doing:
result = ancestor as ParentDataElement<ParentData>;
if (result != null) {
break;There was a problem hiding this comment.
Without the "as" I am getting:
error: A value of type 'Element' can't be assigned to a variable of type 'ParentDataElement'. (invalid_assignment at [flutter] lib/src/widgets/framework.dart:5096)
And in your code, the "as" would throw if ancestor is not a ParentDataElement, which I don't want.
There was a problem hiding this comment.
Ahh I was thinking of C#. Nevermind
There was a problem hiding this comment.
i'm surprised you need the as, i expected this case to be handled. cc @leafpetersen
| try { | ||
| throw FlutterError.fromParts(<DiagnosticsNode>[ | ||
| ErrorSummary('Incorrect use of ParentDataWidget.'), | ||
| ErrorDescription('The following ParentDataWidgets are providing parent data to the same RenderObject:'), | ||
| for (final ParentDataElement<ParentData> ancestor in badAncestors) | ||
| ErrorDescription('- ${ancestor.widget} (typically placed directly inside a ${ancestor.widget.debugTypicalAncestorWidget} widget)'), | ||
| ErrorDescription('However, a RenderObject can only receive parent data from at most one ParentDataWidget.'), | ||
| ErrorHint('Usually, this indicates that at least one of the offending ParentDataWidgets listed above is not placed directly inside a compatible ancestor widget.'), | ||
| ErrorDescription('The ownership chain for the RenderObject that received the parent data was:\n ${debugGetCreatorChain(10)}'), | ||
| ]); | ||
| } on FlutterError catch (e) { | ||
| _debugReportException(ErrorSummary('while looking for parent data.'), e, e.stackTrace); | ||
| } |
There was a problem hiding this comment.
Why isn't this just
_debugReportException(ErrorSummary('while looking for parent data.'), FlutterError.fromParts(...), StackTrace.current);?
| } | ||
| return null; | ||
| assert(() { | ||
| if (result == null || ancestor == null) { |
There was a problem hiding this comment.
Is it ever possible for the result to have been the root? Or is that condition acceptable?
e.g. if you somehow get to the top most ancestor, and it is the wrong type, we'd have result != null && ancestor == null here.
There was a problem hiding this comment.
I don't fully understand what you mean, if we never find the correct type, result would be null as well?
Also, it is fine to not find any ancestor ParentDataElement.
There was a problem hiding this comment.
I mean if you find it and it's the root render object
There was a problem hiding this comment.
Maybe just q comment then about why we are throwing and catching this way would help
There was a problem hiding this comment.
I mean if you find it and it's the root render object
You mean, the root of the element tree? That's fine for the purpose of this check. This check only wants to check how many ParentDataElements there are between this RenderObjectElement and the next RenderObjectElement (or the root).
| void _updateParentData(ParentDataWidget<ParentData> parentDataWidget) { | ||
| bool applyParentData = true; | ||
| assert(() { | ||
| try { |
There was a problem hiding this comment.
Ditto here. We should avoid using exceptions for flow control - it causes debuggers to fire here when users are debugging their own code.
Or is that whta we want? If so, we should add a comment to that effect.
There was a problem hiding this comment.
This is not an exception for control flow. This throws when there's actually something wrong with your app, so if you are debugging your app and you have it set up to fire on exception, you want it to fire here.
I am only catching the exception here directly, because I don't want the exception to bubble up and cause the error widget to swoop in because the tree is in a state where the error widget would cause more exceptions...
There was a problem hiding this comment.
Maybe just q comment then about why we are throwing and catching this way would help
Will do.
| Iterable<DiagnosticsNode> debugDescribeInvalidAncestorChain({ String description, DiagnosticsNode ownershipChain, bool foundValidAncestor, Iterable<Widget> badAncestors }) sync* { | ||
| /// The [RenderObjectWidget] that is typically used to setup the [ParentData] | ||
| /// that [applyParentData] will write to. | ||
| Type get debugTypicalAncestorWidget; |
There was a problem hiding this comment.
It would be helpful if this docstring explained that this is only used for error message purposes - i.e. it is not actually used for comparison in asserts or anything.
| } | ||
|
|
||
| @override | ||
| Type get debugTypicalAncestorWidget => _CupertinoAlertActionsRenderWidget; |
There was a problem hiding this comment.
Here and elsewhere - does it make any difference to wrap these in asserts so that they're null outside of debug mode?
There was a problem hiding this comment.
Since they are only called from on assert, these methods should already be tree-shaken out in release mode.
| bool debugIsValidAncestor(RenderObjectWidget ancestor) { | ||
| /// Checks if this widget can apply its parent data to the provided | ||
| /// `renderObject`. | ||
| bool debugIsValidRenderObject(RenderObject renderObject) { |
There was a problem hiding this comment.
docs should probably mention debugTypicalAncestorWidget and also mention when this is called in the widget lifecycle
| /// | ||
| /// This is only used in error messages to tell users what widget typically | ||
| /// wraps this ParentDataWidget. | ||
| Type get debugTypicalAncestorWidget; |
There was a problem hiding this comment.
debugTypicalAncestorWidgetClass? or is that too verbose
| yield ErrorDescription( | ||
| '$runtimeType widgets must be placed inside $T widgets.\n' | ||
| '$description has no $T ancestor at all.' | ||
| '$description, which has not been setup to receive any ParentData.' |
| yield ErrorDescription( | ||
| '$runtimeType widgets must be placed directly inside $T widgets.\n' | ||
| '$description has a $T ancestor, but there are other widgets between them:' | ||
| '$description, which has been setup to only accept ParentData of incompatible type ${parentData.runtimeType}.' |
There was a problem hiding this comment.
i'd probably remove "only" here
Includes fix for Flutter v1.14.0+. Breaking change described here: flutter/flutter#48541
Description
Prior to this change, a ParentDataWidget was bound to a particular ancestor RenderObjectWidget type, whose RenderObject was responsible for setting up the parent data that the ParentDataWidget would write to. A given ParentDataWidget could not be re-used with another RenderObjectWidget type as ancestor. For example, a new implementation of Stack (e.g.
SuperStackwidget) was not able to re-use the Positioned widget, because it was bound to the existing Stack widget.This change loosens the contract between ParentDataWidget and ancestor RenderObjectWidget types. Any RenderObjectWidget type can be used as an ancestor for a ParentDataWidget as long as the RenderObject of the RenderObjectWidget sets up the ParentData type expected by the ParentDataWidget.
Related Issues
Required for fixing #45797.
Tests
I added the following tests:
Checklist
Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes (
[x]). This will ensure a smooth and quick review process.///).flutter analyze --flutter-repo) does not report any problems on my PR.Breaking Change
Did any tests fail when you ran them? Please read Handling breaking changes.
AFIAK, this is not a breaking change as defined in the breaking change policy, but since this may effect some users negatively, I am providing a migration guide: flutter/website#3525.