Some times the first user interaction with our app is a login/register screen. As a great Flutter developer, we would like to provide the best experience. Ever. So, we do your best and make a gorgeous screen.
import 'package:flutter/material.dart'; | |
import 'package:flutter/services.dart'; | |
void main() { | |
SystemChrome.setEnabledSystemUIOverlays([]); | |
runApp( | |
MyApp(), | |
); | |
} | |
class MyApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: 'Flutter Forms', | |
theme: ThemeData( | |
primarySwatch: Colors.blue, | |
), | |
home: MyHomePage(title: 'Flutter Forms'), | |
); | |
} | |
} | |
class MyHomePage extends StatefulWidget { | |
MyHomePage({Key key, this.title}) : super(key: key); | |
final String title; | |
@override | |
_MyHomePageState createState() => _MyHomePageState(); | |
} | |
class _MyHomePageState extends State<MyHomePage> { | |
final TextStyle textstyle = | |
TextStyle(color: Colors.white, fontWeight: FontWeight.bold); | |
final InputDecoration decoration = InputDecoration( | |
border: OutlineInputBorder(), | |
); | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
body: Center( | |
child: Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: Center( | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.center, | |
crossAxisAlignment: CrossAxisAlignment.center, | |
children: <Widget>[ | |
FlutterLogo( | |
size: 190, | |
), | |
SizedBox( | |
height: 15, | |
), | |
TextFormField( | |
decoration: decoration, | |
), | |
SizedBox( | |
height: 15, | |
), | |
TextFormField( | |
decoration: decoration, | |
), | |
SizedBox( | |
height: 15, | |
), | |
MaterialButton( | |
color: Colors.red, | |
minWidth: 160, | |
child: Text( | |
'Google', | |
style: textstyle, | |
), | |
), | |
MaterialButton( | |
color: Colors.blue, | |
minWidth: 160, | |
child: Text( | |
'Facebook', | |
style: textstyle, | |
), | |
), | |
MaterialButton( | |
color: Colors.orange, | |
minWidth: 160, | |
child: Text( | |
'E-mail', | |
style: textstyle, | |
), | |
), | |
], | |
), | |
), | |
), | |
), | |
); | |
} | |
} |
Easy to combine a Colum(), TextFormField(), MaterialButton(), SizedBox(), centered with Center(), a small Padding() and voilà:
It’s done! Really beautiful, then we started our tests and…
Yep, something crash in our screen. If we try oll up, it won’t work.
Alright, let’s check messages on console:
I/flutter (22185): ══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════ | |
I/flutter (22185): The following message was thrown during layout: | |
I/flutter (22185): A RenderFlex overflowed by 28 pixels on the bottom. | |
I/flutter (22185): | |
I/flutter (22185): The overflowing RenderFlex has an orientation of Axis.vertical. | |
I/flutter (22185): The edge of the RenderFlex that is overflowing has been marked in the rendering with a yellow and | |
I/flutter (22185): black striped pattern. This is usually caused by the contents being too big for the RenderFlex. | |
I/flutter (22185): Consider applying a flex factor (e.g. using an Expanded widget) to force the children of the | |
I/flutter (22185): RenderFlex to fit within the available space instead of being sized to their natural size. | |
I/flutter (22185): This is considered an error condition because it indicates that there is content that cannot be | |
I/flutter (22185): seen. If the content is legitimately bigger than the available space, consider clipping it with a | |
I/flutter (22185): ClipRect widget before putting it in the flex, or using a scrollable container rather than a Flex, | |
I/flutter (22185): like a ListView. | |
I/flutter (22185): The specific RenderFlex in question is: | |
I/flutter (22185): RenderFlex#62103 relayoutBoundary=up3 OVERFLOWING | |
I/flutter (22185): creator: Column ← Padding ← Center ← MediaQuery ← LayoutId-[<_ScaffoldSlot.body>] ← | |
I/flutter (22185): CustomMultiChildLayout ← AnimatedBuilder ← DefaultTextStyle ← AnimatedDefaultTextStyle ← | |
I/flutter (22185): _InkFeatures-[GlobalKey#8c740 ink renderer] ← NotificationListener<LayoutChangedNotification> ← | |
I/flutter (22185): PhysicalModel ← ⋯ | |
I/flutter (22185): parentData: offset=Offset(16.0, 16.0) (can use size) | |
I/flutter (22185): constraints: BoxConstraints(0.0<=w<=379.4, 0.0<=h<=369.1) | |
I/flutter (22185): size: Size(379.4, 369.1) | |
I/flutter (22185): direction: vertical | |
I/flutter (22185): mainAxisAlignment: center | |
I/flutter (22185): mainAxisSize: max | |
I/flutter (22185): crossAxisAlignment: center | |
I/flutter (22185): verticalDirection: down |
In technical terms, the size of our viewport were reduced and it caused a overflow on our layout.
But Flutter error messages are awesome! It suggests:
consider clipping it with a ClipRect widget before putting it in the flex
Or:
using a scrollable container rather than a Flex
Searching on docs we found a Scrollable widget: A widget that scrolls. Fine, but on next paragraph: It’s rare to construct a Scrollable directly.
Got it. Once it’s rare, we probably don’t need to build it.
But, what widget solve our problem ?
A built-in widget provided by Flutter and works perfectly in this case is SingleChildScrollView.
This widget is useful when you have a single box that will normally be entirely visible.
This is a superpower widget.
First, let’s try to fix our screen and then dive deep into this widget. Wrapping our Center() as SingleChildScrollView() child’s.
import 'package:flutter/material.dart'; | |
import 'package:flutter/services.dart'; | |
void main() { | |
SystemChrome.setEnabledSystemUIOverlays([]); | |
runApp( | |
MyApp(), | |
); | |
} | |
class MyApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: 'Flutter Forms', | |
theme: ThemeData( | |
primarySwatch: Colors.blue, | |
), | |
home: MyHomePage(title: 'Flutter Forms'), | |
); | |
} | |
} | |
class MyHomePage extends StatefulWidget { | |
MyHomePage({Key key, this.title}) : super(key: key); | |
final String title; | |
@override | |
_MyHomePageState createState() => _MyHomePageState(); | |
} | |
class _MyHomePageState extends State<MyHomePage> { | |
final TextStyle textstyle = | |
TextStyle(color: Colors.white, fontWeight: FontWeight.bold); | |
final InputDecoration decoration = InputDecoration( | |
border: OutlineInputBorder(), | |
); | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
body: Center( | |
child: SingleChildScrollView( | |
padding: const EdgeInsets.all(8.0), | |
child: Center( | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.center, | |
crossAxisAlignment: CrossAxisAlignment.center, | |
children: <Widget>[ | |
FlutterLogo( | |
size: 190, | |
), | |
SizedBox( | |
height: 15, | |
), | |
TextFormField( | |
decoration: decoration, | |
), | |
SizedBox( | |
height: 15, | |
), | |
TextFormField( | |
decoration: decoration, | |
), | |
SizedBox( | |
height: 15, | |
), | |
MaterialButton( | |
color: Colors.red, | |
minWidth: 160, | |
child: Text( | |
'Google', | |
style: textstyle, | |
), | |
), | |
MaterialButton( | |
color: Colors.blue, | |
minWidth: 160, | |
child: Text( | |
'Facebook', | |
style: textstyle, | |
), | |
), | |
MaterialButton( | |
color: Colors.orange, | |
minWidth: 160, | |
child: Text( | |
'E-mail', | |
style: textstyle, | |
), | |
), | |
], | |
), | |
), | |
), | |
), | |
); | |
} | |
} |
Well done! Our screen now works perfectly. We can roll up and down and everything is fine!
SingleChildScrollView supports following parameters:
child: (Widget) — The widget that scrolls.
controller: (ScrollController) — An object that can be used to control the position to which this scroll view is scrolled.
padding: (EdgeInsetsGeometry) — The amount of space by which to inset the child.
physics: (ScrollPhysics) — How the scroll view should respond to user input.
primary: (bool) — Whether this is the primary scroll view associated with the parent.
reverse: (bool) — Whether the scroll view scrolls in the reading direction.
scrollDirection: (Axis) — The axis along which the scroll view scrolls.
And as I said before, this is a superpower widget and we will explore on next article.
Thanks for reading. See you soon
:)
Top comments (2)
Awesome! Shared with link in SO. This was specially great for me (using it under a Form widget that was overflown at the bottom) as I was using a Padding widget and this one has a padding property.. so a perfect replacement. Thanks!
Alright! Waiting for the next article. Came here because I wanted to make a widget that is scrollable after a certain height