My Simple Flutter App: Part 4 — Styling Form Widgets

Aysha Williams
7 min readJan 9, 2020

Let’s fix the UI.

In Part 3, the user interface of our app did not reflect the look and feel of the components in our mock-up. We should have text fields with rounded corners, the content should have some padding around its edges, and the greeting should be centered on the page.

TDD

All of our tests are passing, but our automated tests don’t tell us how our app looks, but it does tell us that what’s displayed is what we want to be displayed, and it tells us if the app is functioning. We will be using manual testing, our eyes, to determine if the app matches the look and feel we want to achieve. And, we will use the automated testing to determine if we’ve broken existing functionality while making changes.

First Up: Add Padding to the app’s content

Our current app is in need of some padding. If we take a look at our code, we’ll notice that the GreetingForm widget is the body of our app. The GreetingForm contains all of the widgets we want to add padding to (the label, text field, and the greeting message). So, let’s try to add padding around the entire widget.

The Padding Widget
The Padding widget will do exactly what we want. By setting the Padding widget’s child property to the GreetingForm, the Padding widget will add padding around the content of our app. We will also need to set a value for the padding attribute, using the EdgeInsets class.

Our main.drt should look like so:

import 'package:flutter/material.dart';

import 'GreetingForm.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
theme: ThemeData(
brightness: Brightness.dark,
primaryColor: Colors.lightBlue[800],
accentColor: Colors.cyan[600]
),
home: Scaffold(
appBar: AppBar(
title: Text('Greetings From Outer Space'),
),
body: Center(
child: Padding(
padding: EdgeInsets.all(40.0),
child: GreetingForm(),
)
),

bottomNavigationBar: BottomAppBar(
child: Container(height: 50.0,),
),
),
);
}
}

Note: I played around with the value that’s passed to EdgeInsets.all() before settling on the number 40. Choose a number that you feel fits best.

TDD — Run your tests to ensure your changes haven’t broken functionality.

Note: We can check for the existence of a Padding widget in our test, but we should also be cautious of tightly coupling our tests to our implementation.

Next: Rounding the Text Field

Rounding the text field will be quite easy. We just need the proper widget! So, we’ll be using the OutlineBorderInput widget.

OutlineBorderInput Widget
To round the TextFormField we’ll need to go to our GreetingForm widget and set the border property for our TextFormField's InputDecorator. The border will be set to an OutlineBorderInput widget.

Go to the GreetingForm and update the build function to the following:

@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextFormField(
decoration: const InputDecoration(
labelText: 'What\'s your name?',
hintText: 'Your Name',
border: const OutlineInputBorder()
),
controller: textEditingController,
),
Text(_greetingText)
]);
}

TDD — Run your tests to ensure your changes haven’t broken functionality.

We’ve got rounded corners!

Next Up: Move the Label for “What’s your name?”

The label “What’s your name?,” should appear above the text field rather than inline with its border. So, We’re going to use a Text widget to display “What’s your name?” above the text field. Then, we’ll add some padding around the Text widget, using the Padding widget, to better display it on the form.

We will also remove the labelText property from the TextFormField since we’re going to be using a separate Text widget to display the same information.

Go to the GreetingForm and update the build function to the following:

@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(0, 0, 0, 10.0),
child: Text('What\'s your name?'),
),

TextFormField(
decoration: const InputDecoration(
hintText: 'Your Name',
border: const OutlineInputBorder()
),
controller: textEditingController,
),
Text(_greetingText)
]);
}

TDD — Run your tests! They should pass.

Note: The beauty of not coupling our tests so tightly to our implementation is that we can make styling changes without having to constantly change our tests. If we needed to constantly change our tests, for the purposes of the UI, it could be an indication that our tests are fickle.

Increase the font size

Also, let’s update the font size for “What’s your name?,” so that it’s larger. We’ll use the style property of the Text widget, and we’re going to set it to a DefaultTextStyle widget.

Go to the GreetingForm and update the build function to the following:

@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(0, 0, 0, 10.0),
child: Text(
'What\'s your name?',
style:
DefaultTextStyle.of(context).style.apply(fontSizeFactor: 1.5)),
),
TextFormField(
decoration: const InputDecoration(
hintText: 'Your Name',
border: const OutlineInputBorder()
),
controller: textEditingController,
),
Text(_greetingText)
]);
}
}
That’s much easier to read!

Center and Vertically align the Greeting

The last thing to do will be to center the Text widget that displays our greeting.

Add a Center and Padding Widget
There are a couple of ways to achieve this goal. But, we’re going to wrap our Text widget in a Center widget so that it is centered. Then, we’ll wrap the Center widget in a Padding widget and set the top padding so that it pushes the greeting text down making it appear to be more centered.

TDD — Run your tests! They should pass. But, there’s an issue they did not catch!

The issue
I played around with the top padding and managed to make the greeting text disappear when the keyboard was activated (by clicking inside the text field) 😅. The emulator notified me of the issue, but the tests were still green. This is an issue that I believe a test could catch.

I added a top padding of 200. But now, when the keyboard pops up, the greeting is no longer visible. The warning reads “BOTTOM OVERFLOWED BY 69 PIXELS”

Write a test which causes the Overflow error

The reason our unit test did not catch the overflow error is because the test environment doesn’t take the keyboard into account. When we interact with the app and tap into the text field, the keyboard pops up which causes the overflow error.

Now, let’s update our tests to account for a screen that has a keyboard displayed:

testWidgets('greeting text is always visible', (WidgetTester tester) async {
await tester.pumpWidget(MyApp());

const double PORTRAIT_WIDTH = 400.0;
const double PORTRAIT_HEIGHT = 800.0;
const double PORTRAIT_HEIGHT_WITH_KEYBOARD = PORTRAIT_HEIGHT/2;

final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();

await binding.setSurfaceSize(Size(PORTRAIT_WIDTH, PORTRAIT_HEIGHT_WITH_KEYBOARD));
await tester.pumpAndSettle();

final Finder textfield = find.widgetWithText(TextFormField, 'Your Name');

await tester.tap(textfield);
await tester.enterText(textfield, 'Aysha');
await tester.pump();

final Finder greetingText = find.text('Hello, Aysha!');
expect(greetingText, findsOneWidget);
});

Note: In the test above, we’ve defined a value that represents our screen size when the keyboard is displayed, and then we’ve assigned that value to the total screen available for the app to be displayed.

Run the test. It fails with the same error A RenderFlex overflowed by 58 pixels on the bottom. Perfect! Let’s fix it!

Note: While attempting to write an integration test, I observed the same issue as the unit test. When the integration test runs, I can see that the test “types” into the text field, but when it does, the keyboard continues to be hidden, and thus, the overflow error does not occur.

Get the test to green
We are going to update the value set for the Padding widget used for the greetingText. Then, we’ll update the value so that the test passes. Note that the error message tells us how much we need to adjust our value by.

Center(
child: Padding(
padding: EdgeInsets.fromLTRB(0, 90.0, 0, 0),
child: Text(
_greetingText,
key: Key('greetingText')
)
)
)

Changing the value from 150 to 90 did the trick! Now let’s see what it looks like when we run the app:

Final Step — Increase the greeting text’s font size

And last but not least, we’ll increase the font size of the greeting text.

Update the greeting Text widget to the following:

child: Text(
_greetingText,
key: Key('greetingText'),
style: DefaultTextStyle.of(context).style.apply(fontSizeFactor: 1.5),
)

TDD-If we run our tests again, we’ll notice that they are failing, even though our greeting is clearly visible. YIKES! The good news is that the testing framework gives us a really great error message telling us which widget we should use.

Expanded Widget
We can fix our tests by doing a number of things, such as decreasing the padding on the greeting text, reducing the font size, or changing the portrait parameters we set for our testing. But, after reviewing the documentation for the Expanded widget, it appears to be just what we need.

Making this change is going to be super easy! We just need to wrap our Center widget (which contains the Text widget displaying our greeting) with the Expanded widget.

Update the build function in GreetingForm to be:

Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(0, 0, 0, 10.0),
child: Text(
'What\'s your name?',
style: DefaultTextStyle.of(context).style.apply(fontSizeFactor: 1.5)),
),
TextFormField(
key: Key('textField'),
decoration: const InputDecoration(
hintText: 'Your Name',
border: const OutlineInputBorder()
),
controller: textEditingController,
),
Expanded(
child: Center(

child: Padding(
padding: EdgeInsets.fromLTRB(0, 90.0, 0, 0),
child: Text(
_greetingText,
key: Key('greetingText'),
style: DefaultTextStyle.of(context).style.apply(fontSizeFactor: 1.5),
)
)
)
)

]);
}

TDD — Run your tests! La Fin 🎉

--

--