Closed dannyvalentesonos closed 4 years ago
Flutter doctor on 1.19.0-4.1.pre
985aeb8ceee6:all danny.valente$ flutter doctor -v [✓] Flutter (Channel beta, 1.19.0-4.1.pre, on Mac OS X 10.15.5 19F101, locale en-US) • Flutter version 1.19.0-4.1.pre at /Users/danny.valente/Library/flutter/1.19.0-4.1.pre-beta • Framework revision f994b76974 (6 weeks ago), 2020-06-09 15:53:13 -0700 • Engine revision 9a28c3bcf4 • Dart version 2.9.0 (build 2.9.0-14.1.beta)
[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.2) • Android SDK at /Users/danny.valente/Library/Android/sdk • Platform android-29, build-tools 29.0.2 • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6222593) • All Android licenses accepted.
[✓] Xcode - develop for iOS and macOS (Xcode 11.4.1) • Xcode at /Applications/Xcode.app/Contents/Developer • Xcode 11.4.1, Build version 11E503a • CocoaPods version 1.9.3
[✓] Android Studio (version 4.0) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin version 47.1.2 • Dart plugin version 193.7361 • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6222593)
Hi @dannyvalentesonos I see there's an open issue addressing the case you described. https://github.com/flutter/flutter/issues/58959 If you have any details, please add them there Please follow up on that issue, I'm closing the current one as a duplicate. If you disagree, please write in the comments and I will reopen it. Thank you
@TahaTesser I'd prefer we reopen this issue. I have code (that is attached in the description) that worked perfectly to work around the Feature Request which #58959 is about. That code still works perfectly on iOS, however something changed between Flutter 1.19.0-4.0.pre and 1.19.0-4.1.pre that broke this specifically on Android, and this is a rather very big problem for us, and we're about to ship our app.
If you run that code, you'll see that it works on iOS and doesn't work on Android. However, is you use 1.19.0-4.0.pre, you'll see it works.
Thanks.
Hi @dannyvalentesonos Just reopened the issue, it's not clear based on description on how to reproduce the issue Can you please exact provide steps and actual results vs expected results? Thank you
1st issue:
The expected result is to remain on page 100 after tapping Add.
Second issue (which might just be a consequence of the same problem):
The expected result is to still be able to scroll once you've removed all pages except for page 100, and add a new page. Again, this works on iOS, and on Android when running with Flutter 1.19.0-4.0.pre.
This is another side effect of the correctForNewDimensions
change introduced in https://github.com/flutter/flutter/commit/98bc1766273fcdcf02f22f0bb193266b8c50145d
See https://github.com/flutter/flutter/issues/60288 and https://github.com/flutter/flutter/issues/61156
Seems like @Hixie is assigned on the other issue. I will reassign this to @Hixie as well. Feel free to reassign back if you don't have time.
i can reproduce the issue based on https://github.com/flutter/flutter/issues/62209#issuecomment-664390879 steps on beta
and master
, works on stable
I'll try to take a look this week.
I don't really understand why this ever worked, or why it would still be working today on any platform. Fundamentally, the code is requesting that we jump to a position that isn't yet in range (it does the jump just before the frame where we add the new page to the PageView). The scroll physics scroll to the new position and immediately try to go ballistic, and that means pulling back from the new position back to the nearest in-range position, which is what happens.
Oh, interesting. On stable, when the internal dimensions change, we go ballistic again, which essentially terminates the original ballistic scroll...
I don't really understand why this ever worked, or why it would still be working today on any platform. Fundamentally, the code is requesting that we jump to a position that isn't yet in range (it does the jump just before the frame where we add the new page to the PageView). The scroll physics scroll to the new position and immediately try to go ballistic, and that means pulling back from the new position back to the nearest in-range position, which is what happens.
I'm a little concerned about this comment, as this is a really important feature for us, and it seems like comments in #58959 suggest that developers should make this possible in their code, which I have. It really should be a natural use case to support to be honest. If a page is added before the current page, we should be able to "stay" on that page without disrupting the user's experience.
Oh, interesting. On stable, when the internal dimensions change, we go ballistic again, which essentially terminates the original ballistic scroll...
Does this comment mean there's hope we can get a fix for this?
Thanks very much!
Ah, I see. This is hitting the new logic for position correction. The test code sends the page position out of range (since the new page doesn't exist yet). Then we build with the new page. When we discover that the range has changed, we try to maintain the previous relationship between the position and the range, so we move ourselves out of range again, but then when we get the new set of dimensions, they aren't different that the previous call, so we never call applyNewDimensions and never go ballistic.
(Or rather, we only go ballistic once.)
But if we do go ballistic the second time, it'll be from the newly out-of-range position...
It really should be a natural use case to support to be honest.
This is absolutely something we should support.
Does this comment mean there's hope we can get a fix for this?
Working on it. :-)
So the ideal solution here would be that when one adds something to the list of contents in a list that is before the first rendered widget, either something that gets built or something that affects the known offset of the first rendered widget, the scroll position would be adjusted accordingly. Not sure how to do that...
Another option would be to provide a separate way to scroll that is immune to the logic mentioned above, something like "scroll to this position but hold it until after the next build then go ballistic" or something. should be possible with a new ScrollActivity...
I had to make a couple of framework changes but I created a ScrollActivity that the code can use to make this happen. Need to figure out if I think it should be in the base framework or if it's ok for someone to have to create their own.
https://github.com/flutter/flutter/pull/62757 makes this possible in a more reliable way. It's not built into the framework. Not sure if it's good enough or if we should do more...
I will apply your patch and give this a try. I think it's ok to create the scroll activity, but would be nice to have this built in, but if this works, this will be fine for me.
2 quick questions:
+ math.max(0, _pageController.position.viewportDimension * (_pageController.viewportFraction - 1) / 2)
? What exactly does this do? Seeing this is being used in the call to jumToPage, shouldn't the pixels fall on a perfect page boundary?Thanks!
@Hixie So far, this is working and looking good. Thanks again for the quick fix!
re 1, I wasn't able to reproduce that this worked for iOS. It failed for both for me. re 2, I just inlined all the code that I couldn't get to because those members are private. I haven't examined the maths to see if it's correct or not. :-)
An argument to not add the activity itself to the framework is that it's a really narrow use case and what we should really do is find a way to make this whole thing moot (by having a way to add content to a list without moving the perceived scroll position). An argument for adding it is that I have no idea how to do that...
If the goal is to keep the user on the "same" page when the children change, one could also use the old center
work-around with a CustomScrollView:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: WrapHomePage(),
);
}
}
class WrapHomePage extends StatefulWidget {
const WrapHomePage();
@override
_WrapHomePageState createState() => _WrapHomePageState();
}
class _WrapHomePageState extends State<WrapHomePage> {
List<Widget> _pages;
List<Widget> _newPages = <Widget>[];
@override
void initState() {
super.initState();
_pages = List.generate(2, (index) {
final Key key = ValueKey(index);
return Container(
key: key,
height: 50,
color: index % 2 == 0 ? Colors.green : Colors.yellow,
child: Text('Tile $index'),
);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: SizedBox(
height: 100,
child: CustomScrollView(
center: ValueKey(0), /// <-- Here...
physics: PageScrollPhysics(),
slivers: [
SliverFillViewport(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return _newPages[index];
},
childCount: _newPages.length,
),
),
SliverFillViewport(
key: ValueKey(0), /// <-- and here...
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return _pages[index];
},
childCount: _pages.length,
),
),
],
),
),
),
persistentFooterButtons: [
FloatingActionButton(
child: Text('B'),
onPressed: () {
setState(() {
_newPages.add(
Container(
height: 50,
color: _newPages.length % 2 == 0 ? Colors.green : Colors.yellow,
child: Text('New Tile ${_newPages.length}'),
),
);
});
},
),
FloatingActionButton(
child: Text('E'),
onPressed: () {
setState(() {
_pages.add(
Container(
height: 50,
color: _pages.length % 2 == 0 ? Colors.green : Colors.yellow,
child: Text('New Tile ${_pages.length}'),
),
);
});
},
),
],
);
}
}
Thanks @goderbauer . This seems like a very big change for us to change our use of PageView to instead use a CustomScrollView and have to use value keys to keep track of what is showing. I prefer @Hixie 's change which allows our changes to be very minimal, and I've already confirmed that they work very well.
and have to use value keys to keep track of what is showing.
You don't have to keep track of what's showing. The key is statically assigned to one of the SliverFillViewport
that act as one of two page views.
I wonder if the right fix here is really just to drop the RangeMaintainingScrollPhysics from PageViews...
I wonder if the right fix here is really just to drop the RangeMaintainingScrollPhysics from PageViews...
@Hixie I'm not sure what that would entail, but one thing I really like is it takes care of animating should the page the user is on gets removed. At that point, it automatically scrolls and takes care of animating for us, so that the user is aware that something has changed.
Would your suggestion remove that? Basically, I call jumpToPage(-1)
and it automatically scrolls to the last page nicely.
that should still work. RangeMaintainingScrollPhysics is essentially just doing the same thing you're doing, you don't need it in your case. (It does it in a way that has a different effect than what you want, hence this bug.)
I'm looking at https://github.com/flutter/flutter/issues/60288 which the PR above didn't fix, but which is fundamentally the same problem, some code tries to fix the position of a PageView before the PageView metrics change (in their case, the viewport metrics rather than the position, bu the effect is the same), and the RangeMaintainingScrollPhysics logic keeps the scroll position in "the same position" except that it doesn't know the position was already "fixed".
After some discussion on Discord I think we have a fix now that will let your old code work unmodified.
Proposed fix: https://github.com/flutter/flutter/pull/63146
Can we open this? as the PR is reverted
For closure, the original fix was re-applied in https://github.com/flutter/flutter/pull/65135 and is in 1.22.0 and newer.
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v
and a minimal reproduction of the issue.
When using a PageView, if the developer wants to keep the user on the "same" page when the children change, so that the user's experience doesn't get affected, it will not work on Android as of 1.19.0-4.1.pre, if the user is on the last page of the PageView.
So imagine there are 4 pages, and the user is on Page 4. If a new page is added somewhere before Page 4, jumping to Page 5 to keep the user's experience unaffected no longer works on Android only as of 1.19.0-4.1.pre (from the beta channel). This does still work on iOS (using 1.19.0-4.1.pre and even the current 1.20 builds), and it also worked properly on Android on 1.19.0-4.0.pre (from the dev channel).
The trick I use is to call jumpToPage on the pageController right from the didUpdateWidget method, so that the next frame that is built correctly shows the same page as if nothing changed.
The following code reproduces this issue on Android, when building with Flutter 1.19.0-4.1.pre or later, and still works well on iOS. If you run this with flutter 1.19.0-4.0.pre on Android, it works as well.
Simply tap "Add" and you'll see the same page remains, but you can see that if you scroll, new pages can be found. If you go all the way to the last page (100), and press Add, you'll see that the PageView scrolls.
Another issue also in build 1.19.0-4.1.pre and later (only on Android as well) is that if you stay on the last page and Tap remove until all pages have been removed except for page 100, then trying to scroll does nothing (which is expected). Now add a new page, and notice you can no longer scroll, until you add yet another page again. This also does not happen on iOS.
Expand to see code
``` import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'dart:math' as Math; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: PageViewHomePage(), ); } } class PageViewHomePage extends StatefulWidget { const PageViewHomePage(); @override _PageViewHomePageState createState() => _PageViewHomePageState(); } class _PageViewHomePageState extends State