-
Notifications
You must be signed in to change notification settings - Fork 29.7k
Fix SnackBar clipping when it is floating due to FloatingActionButton positioning #47616
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
ffc2035 to
12a076a
Compare
shihaohong
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the submission! The implementation and the tests look great functionally. I think the most work that'll need to be done following up is that the code can be better formatted such that future readers of the code can easily understand what the intent behind the tests are. I made some higher level comments, but take a look at the Flutter style guide when you're unsure on formatting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not want to sound too particular, but we use American English in flutter/flutter. There are some mistakes/accidents that happen elsewhere in the codebase, but they weren't intentional :)
| String getEnumStringValue(SnackBarBehavior behaviour) => behaviour.toString().split('.').last; | |
| String getEnumStringValue(SnackBarBehavior behavior) => behavior.toString().split('.').last; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i removed this function :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe that we will be adding a lint soon that requires all usages of for (foo in bar) to have the final keyword in it.
| for (SnackBarBehavior behavior in SnackBarBehavior.values) { | |
| for (final SnackBarBehavior behavior in SnackBarBehavior.values) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just tested printing ${SnackBarBehavior.fixed.toString()} and it seems to turn out just fine. Also, describe the expected result for the test in the title. Maybe something like the following:
| testWidgets('behavior is ${getEnumStringValue(behavior)} and scaffold has no other elements', | |
| testWidgets('$behavior should align with the bottom of the Scaffold when the Scaffold has no other elements', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is one difference in the result string:
for $behavior the result is "SnackBarBehavior.fixed";
for getEnumStringValue(behavior) the result is "fixed".
But now i think this difference is not important. Therefore i will remove the function getEnumStringValue and will use $behavior instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added expected results in titles. Is it normal that new test titles have two lines?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that it's ok -- I find that it is difficult to come up with a title that fits in one line that is short enough to be within the 80-character guideline. Plus, I would not have to give up clarity in explaining my test this way
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please fix the indentations on these lines. Every new line generally has two spaces. ie.
testWidgets(
'behavior is ${getEnumStringValue(behavior)} and scaffold has no other elements',
(WidgetTester tester) async {
//...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't hurt to test that the bottom left corners of the SnackBar and Scaffold align
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added a conditions for the left and right corner of snackBar in all new tests except tests with FAB and SnackBarBehavior.floating.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| floatingActionButton: FloatingActionButton(onPressed: () {},), | |
| floatingActionButton: FloatingActionButton(onPressed: () {}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| scaffoldState.showSnackBar(const SnackBar(content: Text('Snackbar text'), behavior: SnackBarBehavior.floating,)); | |
| scaffoldState.showSnackBar( | |
| const SnackBar( | |
| content: Text('Snackbar text'), | |
| behavior: SnackBarBehavior.floating, | |
| ), | |
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comments as above regarding the title of the test and code indentation/style.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comments as above regarding the title of the test and code indentation/style.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| scaffoldState.showSnackBar(const SnackBar(content: Text('Snackbar text'), behavior: SnackBarBehavior.floating,)); | |
| scaffoldState.showSnackBar( | |
| const SnackBar( | |
| content: Text('Snackbar text'), | |
| behavior: SnackBarBehavior.floating, | |
| ), | |
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
|
I hope you don't mind, but I also edited your first comment to use GitHub markdown and clarify the title of the pull request if that's okay with you! |
15e5326 to
64ac431
Compare
shihaohong
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just noticed that there is a test/material/snack_bar_test.dart file, and that it does include some positioning tests. You might want to take a look at the existing tests to ensure that you're not duplicating some tests and maybe look to see why the existing tests are not catching the clipping issue this PR fixes.
Also, the tests you've written might actually belong in snack_bar_test.dart as well :)
Sorry for not noticing this sooner! Let me know if you need any suggestions or help with this PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit
| 'when scaffold has no other elements', | |
| 'when Scaffold has no other elements', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit
| }); | |
| }, | |
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in all new tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: trailing comma
| body: Container() | |
| body: Container(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| }); | |
| }, | |
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| '$behavior should align snackBar with the bottom of Scaffold ' | |
| '$behavior should align SnackBar with the bottom of Scaffold ' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| '$behavior should align snackBar with the top of bottom navigation bar ' | |
| '$behavior should align SnackBar with the top of bottom navigation bar ' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| '${SnackBarBehavior.fixed} should align snackBar with the bottom of Scaffold ' | |
| '${SnackBarBehavior.fixed} should align SnackBar with the bottom of Scaffold ' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| '${SnackBarBehavior.floating} should align snackBar with the top of FloatingActionButton' | |
| '${SnackBarBehavior.floating} should align SnackBar with the top of FloatingActionButton' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| '${SnackBarBehavior.fixed} should align snackBar with the top of BottomNavigationBar ' | |
| '${SnackBarBehavior.fixed} should align SnackBar with the top of BottomNavigationBar ' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| '${SnackBarBehavior.floating} should align snackBar with the top of FloatingActionButton ' | |
| '${SnackBarBehavior.floating} should align SnackBar with the top of FloatingActionButton ' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
Thank you for this notice. Now i analyzed snackbar_test.dart: I found 4 tests with SnackBar position which look like my tests:
Test 1: In this case, the SnackBar position is calculated using the In total:
|
That's okay, I had the same thoughts :)
Hmm, this seems to more so test the margins/padding between the edges of
Same comment as with test 1, let's keep the test for now and just rename it such that it's clear that it checks for margin/padding correctness.
Seems like a great idea!
I think for test 1, 3 and 4, what we could do is add your tests, and then take out the margin/padding sections of the preexisting tests and make them into new tests. That way, we don't remove tests for expected padding, while incorporating your positioning tests. Any idea why the original tests never caught the error case? Is it simply because that specific case was never tested? |
64ac431 to
f20fb34
Compare
yes, that's right.
Good idea, i agree!
I found a test with paddings of SnackBar. In my commit i just improved it by adding universal behavior and checking the leftBottom and rightBottom points of SnackBar. |
shihaohong
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks much better than it used to before, thanks again for doing all the refactor!
The only bigger thing is tweaking some tests to fully animate the SnackBar instead of checking the positions of the SnackBar and the FAB only at the very beginning of the animation. Other than that, it's mostly code clarity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Better to use the same word to describe "the spacing in between widgets" since it might get confusing for future readers when padding/margin/indent is used interchangeably.
| 'Indent between border and content of SnackBar when behavior is fixed', | |
| 'Custom padding between SnackBar and its contents when set to SnackBarBehavior.fixed', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| const int fabMargin = 16; | |
| // {{Describe wherever this value was derived from}} | |
| const int defaultFabPadding = 16; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I confused origin and original here, since origin can also be interpreted as the reference point of some coordinate system :) Maybe we can make this clearer this way:
// Get the Rect of the FAB to compare after the SnackBar appears.
final Offset originalFabRect = tester.getRect(find.byType(FloatingActionButton));
// Code that makes SnackBar appear.
// FAB should shift upwards after SnackBar appears.
expect(originalFabRect.getBottomRight.dy > fabBottomRight.dy, true);There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| expect(snackBarTopRight.dy - fabMargin, equals(fabBottomRight.dy)); | |
| // FAB should be positioned above the SnackBar by the default padding. | |
| expect(snackBarTopRight.dy - fabMargin, equals(fabBottomRight.dy)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| 'Indent between border and content of SnackBar when behavior is floating', | |
| 'Custom padding between SnackBar and its contents when set to SnackBarBehavior.floating', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| find.byType(FloatingActionButton)); | |
| find.byType(FloatingActionButton), | |
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is snackBarBox? If it's referring to the RenderBox of the SnackBar, maybe generalizing the term might be better:
| // Since padding and margin is handled inside snackBarBox, | |
| // Since padding between the SnackBar and the FAB is created by the SnackBar, | |
| // the bottom offset of the SnackBar should be equal to the top offset of the FAB. |
Edit: I realized later that this piece was copy-pasted, but this is a good opportunity to clean this up :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could just do:
| expect(snackBarBottomCenter.dy == floatingActionButtonTopCenter.dy, true); | |
| expect(snackBarBottomCenter.dy, floatingActionButtonTopCenter.dy); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| await tester.pump(); | |
| await tester.pumpAndSettle(); // Have the SnackBar fully animate out. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| await tester.pump(); | |
| await tester.pumpAndSettle(); // Have the SnackBar fully animate out. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
|
Bonus points if you could get a screenshot with the fix and put it in the first comment of the issue. That helps 1) verify that the fix is as expected and 2) helps future maintainers of the code understand what the intent of this change was |
f20fb34 to
32266db
Compare
shihaohong
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for your patience, I was on vacation and am just getting back into the groove of things :) Pretty much good except for a few variable names in a test (they test the correct behavior though)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use lessThan matcher here:
| expect(originalFabRect.center.dy > fabRect.center.dy, true); | |
| expect(fabRect.center.dy, lessThan(originalFabRect.center.dy)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| // value was derived from floating_action_button_location.dart | |
| // FAB's surrounding padding is set to [kFloatingActionButtonMargin] in floating_action_button_location.dart byb default. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the order is expect(actual, expected), so since fabRect is the actual Rect, it should appear first
| expect(snackBarTopRight.dy - defaultFabPadding, fabRect.bottomRight.dy); | |
| expect(fabRect.bottomRight.dy, snackBarTopRight.dy - defaultFabPadding); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit
| await tester.pumpAndSettle(); //Have the SnackBar fully animate out. | |
| await tester.pumpAndSettle(); // Have the SnackBar fully animate out. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
| } | |
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| final Offset snackBarBottomCenter = tester.getBottomLeft(find.byType(SnackBar)); | |
| final Offset snackBarBottomLeft = tester.getBottomLeft(find.byType(SnackBar)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| final Offset floatingActionButtonTopCenter = tester.getTopLeft( | |
| final Offset floatingActionButtonTopLeft = tester.getTopLeft( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| expect(snackBarBottomCenter.dy, floatingActionButtonTopCenter.dy); | |
| expect(snackBarBottomLeft.dy, floatingActionButtonTopLeft.dy); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
Accidentally approved despite wanting a few more changes
acc8ce7 to
f96641d
Compare
shihaohong
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| // FAB's surrounding padding is set to [kFloatingActionButtonMargin] in floating_action_button_location.dart byb default. | |
| // FAB's surrounding padding is set to [kFloatingActionButtonMargin] in floating_action_button_location.dart by default. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit
| } | |
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
|
All presubmit tests seem to be passing. Once the tree is green, this will be merged. Thank you so much for this contribution and for taking the time to address all feedback! |
f96641d to
28915f4
Compare
Thank you for review, it was a pleasure to work with you! |
Now the test checks certain position Y of FAB
added tests for testing height of SnackBar.
…n' to group 'SnackBar position' improved test by removing padding details, and renaming.
… group "SnackBar position" and improved test for testing with universal behavior
28915f4 to
66c0d19
Compare
|
This pull request is not suitable for automatic merging in its current state.
|
|
The presubmit tests seem to have rerun for some reason and |
Description
Screenshots
Before fix
After fix
Bug cause
Scaffoldalways wraps aFloatingActionButton(FAB) in a private widget_FloatingActionButtonTransition, even if theFABis null (to animate the appearance of theFAB).Thus,
Scaffoldalways has aFABand calculates an area for it. Before calculating the position of theSnackBar,Scaffoldverifies the area ofFABonnull. If the area is notnull, thenSnackBarwill be positioned above it. SinceFABand its area is nevernull,SnackBaris always on top (This bug works only forSnackBarwith floating behavior)Solution
If the
FABis null, thenFloatingActionButtonAreahas a size equal toSize.zero. Consequently, if the size ofFloatingActionButtonAreais zero, thenFABwon't show andSnackBarwill be laid out on top of the other content.Tests
This PR also adds tests to ensure that the
SnackBaris positioned in the right places given the many configurations ofScaffoldRelated Issues
Fixes #47202
Fixes #43716
Tests
This PR include parameterized test (to avoid two identical tests differing in one detail). But if it is not ok, i'll split them.
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].