Skip to content

Conversation

@gspencergoog
Copy link
Contributor

@gspencergoog gspencergoog commented Apr 4, 2018

This fixes a rendering problem in the AnimatedChildSwitcher where it would add a new "previous" child each time it rebuilt, and if you did it fast enough, all of them would disappear from the page.

It also expands the API for AnimatedChildSwitcher to allow you to specify your own transition and/or layout builder for the transition.

Fixes #16226

@gspencergoog gspencergoog requested a review from Hixie April 4, 2018 22:40
@gspencergoog gspencergoog force-pushed the switcher_fix branch 2 times, most recently from 4468ff6 to ba727f3 Compare April 4, 2018 22:42
Copy link
Contributor

Choose a reason for hiding this comment

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

You could make the _AnimatedChildSwitcherChildEntry implement AnimatedChildSwitcherLayoutData and then just pass the actual _AnimatedChildSwitcherChildEntry in instead of copying the data over.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oooh! Nice!

Copy link
Contributor

Choose a reason for hiding this comment

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

presumably this should be final?

Copy link
Contributor

Choose a reason for hiding this comment

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

or at least, just a getter?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nope, should be final.

Copy link
Contributor

Choose a reason for hiding this comment

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

If you do the thing above with the abstract class bla bla, you'll probably want it to be a getter in this interface, and a mutable field in the concrete subclass.

Copy link
Contributor

@Hixie Hixie Apr 4, 2018

Choose a reason for hiding this comment

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

Signature for builders 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.

Done

Copy link
Contributor

Choose a reason for hiding this comment

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

ditto

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

Copy link
Contributor

Choose a reason for hiding this comment

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

strike "essentially"

Copy link
Contributor

Choose a reason for hiding this comment

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

though then this reads a bit weird, since you are sometimes saying they're the same and sometimes saying they're different.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, essentially. :-)

Copy link
Contributor

Choose a reason for hiding this comment

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

probably not a UniqueKey. That would likely trigger the same bug you saw earlier.

Copy link
Contributor

Choose a reason for hiding this comment

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

maybe only mention valuekey.

Copy link
Contributor

Choose a reason for hiding this comment

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

constructor docs are out of date

also constructor docs should say what it does

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

Copy link
Contributor

Choose a reason for hiding this comment

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

talk about the default, and maybe something about the contract here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Copy link
Contributor

Choose a reason for hiding this comment

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

ditto

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Copy link
Contributor

Choose a reason for hiding this comment

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

looks like _currentChild can never be false, since we set it in initState, so you can remove the ?

Copy link
Contributor

Choose a reason for hiding this comment

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

unless you change the semantics as suggested above

Copy link
Contributor

Choose a reason for hiding this comment

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

in which case you could just make this _currentChild != null

then you could use it directly in addEntry

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I updated it to the new semantics.

Copy link
Contributor

Choose a reason for hiding this comment

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

missing docs

Copy link
Contributor

Choose a reason for hiding this comment

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

this class should have an opinion about whether widget can be null. if it can, this should be documented with emphasis, since that is likely to cause null derefs.

May be better to not call the callbacks when there's a null child?

Copy link
Contributor

Choose a reason for hiding this comment

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

addEntry's logic is a bit wrong for the case of _currentChild.widget == null. We should probably consider moving to a world where if widget.child is null, we set _currentChild to null rather than setting its widget to null and still creating a (useless) animation controller.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

Copy link
Contributor

Choose a reason for hiding this comment

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

this should be in didUpdateWidget

Copy link
Contributor

Choose a reason for hiding this comment

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

why isn't this just a list of Widgets?

Copy link
Contributor

Choose a reason for hiding this comment

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

something else we need to do here is make sure we put keys on each of these children, otherwise the animations will get all messed up -- e.g. if you have animations A, B, and C running, then A ends and you take it out, without keys, B will be synced with A, C will be synced with B, and all of them will lose the state of any descendants.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK, but how can I do that if the transition builder is the one creating the children? I can add it in the default, but not enforce it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should I use "KeyedSubtree" to do this?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, KeyedSubtree. (Or pass the key to the child and require the contract to be that they must use the key, which is what AnimatedCrossFade does iirc, though that makes the API less nice.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK, I did it with KeyedSubtree to make it cleaner.

Copy link
Contributor

Choose a reason for hiding this comment

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

you need to update _currentChild.widget to the new widget.child if you don't add an entry.

Copy link
Contributor Author

@gspencergoog gspencergoog Apr 5, 2018

Choose a reason for hiding this comment

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


OK, I think I understand: it is definitely updated, or it wouldn't have called didUpdateWidget, and since the _currentChild.widget and widget.child might be compatible for an update, we have to update the one we kept to make sure its in sync with the widget.child. I did that, and also regenerated the transition with the new child, since that needs to be updated too.

@gspencergoog gspencergoog force-pushed the switcher_fix branch 5 times, most recently from a5ea885 to 9da3a41 Compare April 5, 2018 15:44
Copy link
Contributor

@Hixie Hixie Apr 5, 2018

Choose a reason for hiding this comment

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

you either need an extra comma after "previously" or to remove the comma after "is", otherwise the sentence becomes "{Represents a child that is} or {was previously set on the [AnimatedChildSwitcher.child] field that needs to be laid out using a [AnimatedChildSwitcherLayoutBuilder]}.", which makes no sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

Copy link
Contributor

Choose a reason for hiding this comment

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

wait, i'm confused. I thought this was the interface you had to pass to the transition builder, but actually you only use it for the layout builder. Why not just pass the layout builder a List<Widget>?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because the layout builder might need to see all of the animations too in order to do its layout. The default one doesn't, but that doesn't mean that another layout won't want to animate them.

Copy link
Contributor

Choose a reason for hiding this comment

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

Either add the magic null factory constructor here (see the chip pr i submitted) if you want this to be an interface, or add a const constructor with the const constructor boilerplate if you want this to be a superclass (then use "extends" instead of "implements" below).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

@gspencergoog gspencergoog force-pushed the switcher_fix branch 5 times, most recently from 89b7204 to 7a92a48 Compare April 5, 2018 23:09
Copy link
Contributor

Choose a reason for hiding this comment

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

please keep the arguments, asserts, and fields in the same order.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, got a little messed up there... Done.

Copy link
Contributor

Choose a reason for hiding this comment

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

trailing comma

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

Copy link
Contributor

Choose a reason for hiding this comment

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

this paragraph is now moot

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed.

Copy link
Contributor

Choose a reason for hiding this comment

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

unlikely that a UniqueKey is what one wants

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh? Why? Isn't it going to generate a unique key each time, which is what the user might sometimes want here? I can see cases where it might be onerous to collect all of the "values" and decide if the widget is different visually, and instead they just put a UniqueKey on it each time they swap it out.

Edit: I see from below why (fading when rotating, etc.).

Copy link
Contributor

Choose a reason for hiding this comment

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

This should be a ValueKey<int>(count). Otherwise, rotating the phone (for instance) would cause the text to fade.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK.

Copy link
Contributor

Choose a reason for hiding this comment

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

+= 1

Copy link
Contributor

Choose a reason for hiding this comment

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

You need a blank line before the list.
Also, only indent the bullet by one, so that the text is indented by 3.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed, and below.

Copy link
Contributor

@HansMuller HansMuller left a comment

Choose a reason for hiding this comment

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

The changes LGTM (looks like a very useful widget!).

I have to admit that I couldn't follow all of the logic around _currentChild. A // comment that explained how it was used, what invariants it satisfied, would make it easier to read the code.

Copy link
Contributor

Choose a reason for hiding this comment

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

Convention for State vars is to make them _private.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok. I didn't think that mattered as much in a private class. Done.

Copy link
Contributor

Choose a reason for hiding this comment

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

The duration of the transition from the old [child] value to the new one.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Copy link
Contributor

Choose a reason for hiding this comment

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

The animation curve to use when transitioning in [child].

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, thanks for catching these.

Copy link
Contributor

Choose a reason for hiding this comment

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

The animation curve to use when transitioning the previous [child] out.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Copy link
Contributor

Choose a reason for hiding this comment

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

It would be more helpful to say what this builder function is for: A function that wraps the new [child] with an animation that transitions the [child] in when the animation runs in the forward direction and out when the animation runs in the reverse direction.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Copy link
Contributor

Choose a reason for hiding this comment

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

It would be more helpful to say what this builder function is for: A function that wraps all of the children that are in the midst of transitioning out and the [child] that's transitioning in, with a widget that lays all of them out.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Copy link
Contributor

Choose a reason for hiding this comment

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

If _newEntry is private, shouldn't addEntry be _addEntry?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be worth special casing children.isEmpty and children.length == 1? Maybe Stack already handles those cases efficiently?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It appears to be fairly efficient, but it does do some trivial work in the empty case that it could probably avoid. I added a special case inside of Stack for the empty case.

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 you could flatten this into one if statement

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe assert that _children doesn't already contain _currentChild here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, and below.

@flutter flutter deleted a comment from googlebot Apr 12, 2018
@flutter flutter deleted a comment from googlebot Apr 12, 2018
@gspencergoog gspencergoog merged commit c73b8a7 into flutter:master Apr 12, 2018
if (childCount == 0) {
size = constraints.biggest;
assert(size.isFinite);
return;
Copy link
Contributor

Choose a reason for hiding this comment

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

did you mean to add this block? it seems to be redundant with the code below, but if you meant it as an optimisation it seems to be in a weird place (since it makes earlier statements redundant in the short-circuited case).

Copy link
Contributor

Choose a reason for hiding this comment

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

oh, i see the comment later about this.

We shouldn't add this kind of thing unless we have benchmarks showing it's actually a benefit.
If we do want to add it we should really do it seriously, which would mean putting it before the hasNonPositionedChildren, at least, and maybe before _resolve (and moving _hasVisualOverflow earlier), assuming _resolve doesn't do anything useful in the case of there being no children.

Also, the assert is redundant with the same assert in box.dart.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK, makes sense. Want me to revert this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Or, I can follow up with the yak shave and do a performance analysis of Stack.

Copy link
Contributor

Choose a reason for hiding this comment

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

Unless we have numbers showing that we need this, we should probably revert it, and if there's a performance concern, we can then do the yak shave.

@gspencergoog gspencergoog deleted the switcher_fix branch April 17, 2018 03:26
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 11, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enhance AnimatedChildSwitcher to animate using a builder

4 participants