DEV Community

rubensdemelo
rubensdemelo

Posted on • Edited on

9 2

Flutter Forms: Improving UX with SingleChildScrollView

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à:

Login Screen
It’s done! Really beautiful, then we started our tests and…
Somethin gets wrong
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
view raw overflow.log hosted with ❤ by GitHub

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,
),
),
],
),
),
),
),
);
}
}

Perfect

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

:)

Image of Docusign

Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more

Top comments (2)

Collapse
 
cdsaenz profile image
Charly S.

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!

Collapse
 
thinkdigitalsoftware profile image
ThinkDigitalSoftware

Alright! Waiting for the next article. Came here because I wanted to make a widget that is scrollable after a certain height

Image of Timescale

Timescale – the developer's data platform for modern apps, built on PostgreSQL

Timescale Cloud is PostgreSQL optimized for speed, scale, and performance. Over 3 million IoT, AI, crypto, and dev tool apps are powered by Timescale. Try it free today! No credit card required.

Try free