Skip to content

fix a table widget render bug#76375

Closed
xu-baolin wants to merge 7 commits intoflutter:masterfrom
xu-baolin:0219table
Closed

fix a table widget render bug#76375
xu-baolin wants to merge 7 commits intoflutter:masterfrom
xu-baolin:0219table

Conversation

@xu-baolin
Copy link
Member

@xu-baolin xu-baolin commented Feb 19, 2021

Re-work of #69670

See #69670 for more process details.

@Hixie Hi,
I admit that the solution looks a bit unconventional, but I think this is due to the peculiarities of the table widget and has to do it.
In fact, this is a bug with a very clear root cause, but we cannot solve it easily, the IndexedSlot is designed to solve the scene of this issue, but we cannot use this.
1, In order to be compatible with the old API, we cannot introduce IndexedSlot,
2, should not match keys at different rows also causes us to be unable to introduce the IndexedSlot

Looking forward to discussing a perfect solution.

@flutter-dashboard flutter-dashboard bot added the framework flutter/packages/flutter repository. See also f: labels. label Feb 19, 2021
@google-cla google-cla bot added the cla: yes label Feb 19, 2021
@xu-baolin xu-baolin requested a review from Hixie February 19, 2021 10:30
Copy link
Member Author

Choose a reason for hiding this comment

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

I have analyzed all the scenarios of inserting RO, it is safe to do so and can fix this bug

Copy link
Member Author

@xu-baolin xu-baolin Feb 23, 2021

Choose a reason for hiding this comment

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

There are three situations where this function will be called,

  1. In the _TableElement mount phase, _lastRemovedParentDataInThisFrame is null at this time;
  2. The _TableElement update phase. For one row update, the void update(Table newWidget) will first insert the new RO, then remove the old RO, and then update the next Row. The first RO inserted coordinations must be the last deletion of the previous row(if any). The Updata will also call _updateRenderObjectChildren at the end, so the operation is safe.
  3. In the scene of the issue problem, _lastRemovedParentDataInThisFrame will record the last delete the coordinates of RO. If there is an insertion in this frame, we can find the position to be inserted.

@xu-baolin xu-baolin requested a review from goderbauer February 19, 2021 10:37
@xu-baolin
Copy link
Member Author

@Hixie Hey, please take a look at this in case you have time, I think it will take your short time this time!
: )

Copy link
Contributor

Choose a reason for hiding this comment

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

let's put this before the first method that uses it, to aid readability

Copy link
Contributor

Choose a reason for hiding this comment

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

this wouldn't work for cases where this is done on a PipelineOwner that isn't being run by the binding...

Copy link
Member Author

@xu-baolin xu-baolin Feb 25, 2021

Choose a reason for hiding this comment

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

Could you let me know in what scenarios the situation you mentioned will be used(or some documentations)?
I am not familiar with this and want to learn it.

@Hixie
Copy link
Contributor

Hixie commented Feb 25, 2021

I admit this is a short solution. I'm not convinced it would work reliably (solutions involving passing information out-of-band like this and scheduling post-frame cleanup are always a bit suspicious), but it might be an improvement over the current code regardless.

Did you try using the slot at all? That still seems like the most likely reliable solution.

cc @goderbauer. I'm leaning towards us just landing this since it does fix the problem, though I'd probably want to make sure we filed a bug to revisit this and consider making Table work with a real slot in due course.

@xu-baolin
Copy link
Member Author

I admit this is a short solution. I'm not convinced it would work reliably (solutions involving passing information out-of-band like this and scheduling post-frame cleanup are always a bit suspicious), but it might be an improvement over the current code regardless.

Did you try using the slot at all? That still seems like the most likely reliable solution.

cc @goderbauer. I'm leaning towards us just landing this since it does fix the problem, though I'd probably want to make sure we filed a bug to revisit this and consider making Table work with a real slot in due course.

I totally agree, nor do I think it’s a perfect solution.

In fact, the last PR #69670 tried to use the slot solution completely, but it might inevitably break something.
maybe we should give everyone a chance to think about how best to solve (and explain) a general solution.

@Hixie
Copy link
Contributor

Hixie commented Feb 25, 2021

In fact, the last PR #69670 tried to use the slot solution completely

I meant using the X,Y version of slot (rather than the before/after version of slot like most multi-child widgets).

@Hixie
Copy link
Contributor

Hixie commented Feb 25, 2021

If you are running into a problem with this bug in your app, it's probably fine to land this as-is (assuming @goderbauer agrees). It's better to have a fix than no fix and this fix is not particularly more complicated than what we have now.

@xu-baolin
Copy link
Member Author

In fact, the last PR #69670 tried to use the slot solution completely

I meant using the X,Y version of slot (rather than the before/after version of slot like most multi-child widgets).

I will try it!

@xu-baolin
Copy link
Member Author

yes, the x,y version slot seems to work! I will update the new solution ASAP

@xu-baolin
Copy link
Member Author

@Hixie Hey,
I believe we could completely solve this bug this time : )

@override
void insertRenderObjectChild(RenderObject child, dynamic slot) {
renderObject.setupParentData(child);
if (slot is _TableSlot)
Copy link
Member Author

Choose a reason for hiding this comment

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

1, mount phase: slot is null
2, _TableElement.update phase: slot is IndexedSlot
3, This bug scenario: slot is _TableSlot

the #1 and #2 scenario are finally update to _TableSlot type.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think the problem here is that we are using updateChildren and that uses _IndexedSlot. We probably need to create our own version of updateChildren that doesn't mess with the slot and does things by honoring the x,y coordinate, and then we can reliably use _TableSlot everywhere, including in mount.

Copy link
Member Author

Choose a reason for hiding this comment

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

Truly, I considered this matter. There is an intermediate state of indexedSlot that is not so perfect, but if we overrideupdateChildren, there may be a lot of duplicate code. Do you have any suggestions for this problem?

Copy link
Member Author

Choose a reason for hiding this comment

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

Another question, if we do this, there will be no way to distinguish the three scenarios here, because we cannot add RO through this method in the first two scenarios.

Copy link
Member Author

Choose a reason for hiding this comment

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

The current implementation uses void setFlatChildren to change the dimensions of RO._children, which is different from other multiChildRO.

Copy link
Member Author

Choose a reason for hiding this comment

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

Now I ignore the slot during updating, any thoughts? :)

Copy link
Member Author

@xu-baolin xu-baolin Mar 11, 2021

Choose a reason for hiding this comment

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

@goderbauer Hi,
There are three scenarios that will call 'insertRenderObjectChild',

  1. mount
  2. update
  3. The related issue scenario
    If we initialize the slots in the mount and update phases to tableslot, we cannot distinguish between these three scenarios, because the current implementation does not allow us to insert RO through this function, and RO needs to be inserted only in the scenario of the issue. Otherwise, we need to refactor the 'RenderTable' again!

Copy link
Member

Choose a reason for hiding this comment

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

I was playing around with this a little bit, what do you think about the approach in https://github.com/flutter/flutter/pull/78081/files?

Copy link
Member Author

Choose a reason for hiding this comment

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

So appreciate that your being able to continue this work. I have left some comments there.

@xu-baolin
Copy link
Member Author

Ping @Hixie : )

int get hashCode => hashValues(x, y);

@override
String toString() => '${super.toString()}; "x" : "$x"; "y" : "$y"; }';
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Will fix this.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

/// This function is a convenience wrapper around [updateChild], which updates
/// each individual child. When calling [updateChild], this function uses an
/// [IndexedSlot<Element>] as the value for the `newSlot` argument.
/// [IndexedSlot<Element>] as the value for the `newSlot` argument if [ignoreSlot]
Copy link
Member

Choose a reason for hiding this comment

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

nit: document what it does if ignoreSlot is true.

Comment on lines 5511 to 5512
/// is `false`.
/// [IndexedSlot.index] is set to the index that the currently processed
Copy link
Member

Choose a reason for hiding this comment

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

nit: reflow this paragraph?

@protected
List<Element> updateChildren(List<Element> oldChildren, List<Widget> newWidgets, { Set<Element>? forgottenChildren }) {
List<Element> updateChildren(
List<Element> oldChildren, List<Widget> newWidgets, {Set<Element>? forgottenChildren, bool ignoreSlot = false}
Copy link
Member

Choose a reason for hiding this comment

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

The ignoreSlot argument is a little awkward. Could RenderObjectElement have a createSlotForChild method that by default returns an IndexSlot and the Table could override that to return its own slot version? Or something like that?

Copy link
Member Author

Choose a reason for hiding this comment

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

Make sense.
Fixed as your suggestion!

// This class ignores the child's slot during [_TableElement.mount] and [_TableElement.update]
// phase, and then updates the slot to [_TableSlot] after completing the update of the child list.
@override
Object? createSlotForChild(int index, Element? previousChild) => null;
Copy link
Member

Choose a reason for hiding this comment

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

I was actually hoping that this could create the TableSlot?

@@ -370,7 +403,10 @@ class _TableElement extends RenderObjectElement {

Copy link
Member

Choose a reason for hiding this comment

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

In the mount above, we should be able to calculate the right TableSlot and just pass it into inflateWidget, no?

Copy link
Member

Choose a reason for hiding this comment

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

So, the only remaining problem is the use of updateChildren in update then? It looks like we could figure out the appropriate slot value for each child we pass to updateChildren right there. Maybe updateChildren could take an optional list of slots (with the same length as the newWidgets list) or a function that generates a slot based on the index of the widget - and when provided the slot from that list or function is used instead of the default IndexSlot?

Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Please let me know if did not explain it clearly.

Copy link
Member

Choose a reason for hiding this comment

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

I am not sure I understand what you mean...

@xu-baolin
Copy link
Member Author

Continue working by #78081

@xu-baolin xu-baolin closed this Mar 17, 2021
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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants