Prevent exception being thrown on hasScrolledBody#31485
Prevent exception being thrown on hasScrolledBody#31485dnfield merged 22 commits intoflutter:masterfrom
Conversation
|
Thanks for this! I'm wondering if this is really the right fix. There are a lot of places in the framework that assume I suspect the intent of marking |
dnfield
left a comment
There was a problem hiding this comment.
As mentioned in the comment, I suspect we just shouldn't let this be null rather than allowing it to be null in this one special case.
|
@dnfield Both Given that the permitted values can be also -/+ infinity, it might be actually good to change the getters of these fields to return -/+ infinity if they are not being set?* I'll check what effect this will have, but after looking at the code, I believe this will retain the current behaviour of null where nulls are accepted, and otherwise won't change the behaviour. WDYT? * or perhaps initialize them with infinities -- I'm not sure what's the code style around these cases |
|
I've updated the PR with the changes I suggested above, PTAL. Looking through the code I've also noticed that FixedScrollMetrics should also probably have an assertion on the min-/maxScrollExtent params. However, this can be breaking for some clients (though the usage with nulls might not necessarily be useful). |
|
They get set in the consturctor for _NestedScrollView no? |
I'm afraid that I don't know which they you are referring to. EDIT: If you're thinking of _min/_maxScrollExtent, they are private to scroll_position.dart, and not initialized there. |
|
Sorry, not being clear. I'll take a look when not on mobile :) |
|
I was mixing up the constructor for I suspect the fix here is to default the min and max scroll extents to something, but I suspect 0.0 is a safer default than double.infinity (or double.negativeInfinity). It also may be the case that we don't need to default it, we just need to figure out why someone is failing to properly initialize it. Either way, it seems allowing it to ever be null could potentialy cause problems all over the place. @goderbauer might know better but he's out right now. I'll poke at it a little more in the mean time. |
|
Two nits/thoughts:
|
|
Had a quick chat with @Hixie about this - he's suggesting we shouldn't default it, as that could cause some bad behavior in other places. Basically, we need to figure out who is initializing or trying to use NestedScrollController before it's actually ready for use. Then we need to either make sure it's properly initialized at that point, or we need to make sure the lifecycle is making sense and not trying to use something it's not supposed to. |
…ion_toDouble_exception_31195
|
I've added a test for the bug in |
|
OK, After investigating the repro I think I know what's happening:
Now, the questions is: what shouldn't happen? :) |
|
Note: I realized that the test that I wrote doesn't follow the same path that production code does -- it might be related to the placeholder being present in tests, or some binding overrides. Hence, I reverted it. |
|
Thanks for the detailed explanation! This part seems strange to me:
Are you saying that the build method call here skips a layout? |
I assumed it |
The initial build (caused by EDIT: This also explains why the widgetTest is unable to reproduce this: the test placeholder's existence causes any subsequent |
|
@dnfield Any idea on how to proceed with that? |
|
If there's any part of the framework or test framework that causes us to go through a build cycle before the corresponding layout cycle, that's the bug we should track down and fix. The only times that should happen is when you've explicitly told the test framework to only go as far as build, and that's quite rare. |
|
Ok. Here's the sequence that seems to cause this:
So our two options are:
|
|
Discussed this elsewhere, but one possibility for this is to update the RendererBinding to override the |
…Position_toDouble_exception_31195
| super.handleMetricsChanged(); | ||
| for (WidgetsBindingObserver observer in _observers) | ||
| observer.didChangeMetrics(); | ||
| afterWarmUpFrame(() { |
There was a problem hiding this comment.
I think this won't be robust enough - we'll eventually add some new handle call, or another implementer of the binding would potentially dirty the tree in some unexpected way. Can we somehow push this up to the RendererBinding?
That seems like the bug. Why are these ending up on the microtask queue instead of the task queue? |
|
Hm. It may not actually be the microtask queue at all - the problem is they mark the widgets as needing a build again, but they're getting marked as needing a build while a build is already going on which leads to the confusion. |
Actually, they are not. I've been a bit misled by the comment in The engine calls that end up dirtying the state before layout are actually regular Tasks, rather than microtasks. So I tried a different approach now, in which instead of using |
|
This seems reasonable to me but I'm afraid I may be lacking in my understanding of what's going on here. I suspect @jonahwilliams @goderbauer, @yjbanov or @Hixie would be better suited to review this. Could one of you take a look? |
This seems reasonable but I'd defer to someone with a better understanding of the inner workings here.
dnfield
left a comment
There was a problem hiding this comment.
This LGTM. I've talked with a couple people and no one has been able to give me a strong reason not to do this, and it seems to work fine. We'll keep an eye out for it breaking anything.
|
Thanks for sticking with this @kpsroka ! It definitely was a tricky one. |
|
The PR as it stands now seems correct, but I'm still confused as to why it didn't work before when calling It would be good to have a test that specifically tests the original bug that led to this, though I admit I don't really know how one would write that test. |
Description
Fixes #31195, by introducing a null check in
hasScrolledBodyof_NestedScrollCoordinator.The root cause of the issue is that the
position.minScrollExtentreturns null, and the comparison operator trying to cast the value totoDoubleon it causes exception to be thrown.This occurs as a result of
scheduleWarmUpFramecausing a cascade ofupdateChildcalls. Eventually this reaches aNestedScrollViewState'sbuildmethod.The bug is likely more due to the
scheduleWarmUpFramebeing run with some assumptions that do not hold. The_minScrollExtentof_NestedScrollPositionthat back NestedScrollView is initialized with null, and updated to a double value inapplyContentDimensions. That, in turn, is executed by cascadedlayoutcalls, which happens only after thescheduleWarmUpFramefinishes.It is likely that the bug has a better solution, but I'd need some guidance on resolving this in another way.
Related Issues
Fixes #31195
Fixes #28351
Tests
I added the following tests:
I'd love to know how to reproduce this in a test environment (including fake 'warmUp' frame).
Checklist
///).flutter analyze --flutter-repo) does not report any problems on my PR.Breaking Change
Does your PR require Flutter developers to manually update their apps to accommodate your change?