Login Form: Adding TextFields and ElevatedButton

Now, let's try to build a login form for our application users. For now, we will work on styling of the form, diving deeper into different widgets and their properties. In later lessons, we will make that form actually work with Laravel API.

By the end of this lesson, you will have a screen like this:

Let's Start Small: Title Change

First, let's change the title on top. So change:

appBar: AppBar(
  title: Text('Welcome to Flutter'),
),

to...

appBar: AppBar(
  title: Text('Login'),
),

If you use Android Studio, it has a so-called "hot reload" function, so that when you click Save, it immediately re-compiles the app, but only the part that has been changed. So you see the changed visual results in your emulator almost instantly:

Adding Email Text Field

Next, let's replace the Scaffold -> body property, remove the Center Text widget with a text field, for email. There are a few different text field widgets, for now we will use TextField widget.

// Scaffold...
body: TextField(
  keyboardType: TextInputType.emailAddress,
  decoration: InputDecoration(
    labelText: 'Email',
  ),
),

Here's the visual result:

As you can see, the text field doesn't have any borders, padding, or other styles, we will take care of it a bit later. When you click on the field itself in the emulator, you also have a keyboard and can type the value:

As you can see, to add this simple text field, we have another "tree" of settings and values: like TextInputType.emailAddress value or InputDecoration class for the label. In this course, I can't really tell you about all the possible settings, there are thousands of them, so you need to get used to look at widget's documentation pages, and rely on your IDE to auto-complete some stuff.

Password Field and Column Widget

Let's practice on another field - let's add one for the password.

But wait, we can't assign two text widgets to the same Scaffold:body property, cause it expects only ONE widget as a result. So we need some kind of a "container" for this, like <div> in HTML. Again, there are a few different widgets that can help, but we will choose a Column widget, which is described as "A widget that displays its children in a vertical array."

So, we will have one Column, with two Children - one text field inside of each. Here's the code:

body: Column(
  children: <Widget>[
    TextField(
      keyboardType: TextInputType.emailAddress,
      decoration: InputDecoration(
        labelText: 'Email',
      ),
    ),
    TextField(
      keyboardType: TextInputType.visiblePassword,
      decoration: InputDecoration(
        labelText: 'Password'
      ),
    ),
  ],
),

So, we have two objects of TextField class, with different properties - now you see the different keyboard type of visiblePassword, and different labelText. Again, look at the official documentation of TextInputType for other possible values: datetime, number, phone, etc.

Visual result:

Now, let's discuss this structure:

// Column 
children: <Widget>[
  TextField(...),
  TextField(...),
  ...
]

This is an example of array of variables in Dart language, which is the base underneath Flutter framework. In Dart, those are called Lists. So here, we define the list with [...] structure, and also specify the field of the list elements: <Widget>[...]. You can read more about lists in Dart on this page.

Next, when we need to add a third widget underneath, we just add it as another element of the list. In fact, let's do exactly that - add a button.

Elevated Button with Properties

For buttons, in older versions of Flutter there were different kinds of buttons, so if you find any older tutorials, keep in mind those breaking changes. In modern Flutter, you can use a widget called TextButton, or we will use the one called ElevatedButton.

Here's the code:

body: Column(
  children: <Widget>[
    TextField(...),
    TextField(...),
    ElevatedButton(
      onPressed: null,
      child: Text('Login'),
      style: ElevatedButton.styleFrom(
          minimumSize: Size(double.infinity, 36)),
    ),
  ],
),

Here's how it all looks now:

So, we added that third element to the list. Let's discuss what are the properties.

The onPressed property defines the method of what should happen if someone presses the button. It is a required property, if you don't specify it, the app won't compile, so even if you don't have the method defined yes, as in our case, you still need to specify it, just as null value.

The child: Text('Login') is another example of a widget within another widget. So yes, if you want to add a button label, you need to provide it as a Child for the button, using the Text widget. It sounds complicated, but it allows to then separately add styles to the button and the label.

Finally, this part:

style: ElevatedButton.styleFrom(
          minimumSize: Size(double.infinity, 36)),

Again, looks like quite complicated code to just specify that the button should be full-width, right? But this is how Flutter works, you need to get used to it: to define some properties, you need to use classes with their properties, and constants like double.infinity. Again, it's all in the documentation, so feel free to play around with different values. For example, ElevatedButton.styleFrom method has this big list of possible properties:

ButtonStyle styleFrom({
  Color? primary,
  Color? onPrimary,
  Color? onSurface,
  Color? shadowColor,
  double? elevation,
  TextStyle? textStyle,
  EdgeInsetsGeometry? padding,
  Size? minimumSize,
  Size? fixedSize,
  BorderSide? side,
  OutlinedBorder? shape,
  MouseCursor? enabledMouseCursor,
  MouseCursor? disabledMouseCursor,
  VisualDensity? visualDensity,
  MaterialTapTargetSize? tapTargetSize,
  Duration? animationDuration,
  bool? enableFeedback,
  AlignmentGeometry? alignment,
  InteractiveInkFeatureFactory? splashFactory,
})

Make the Button Work

Ok, so we styled the button, but it still looks like it's disabled, right? This is what happens if you specify onPressed: null. And it's logical: we don't have any method of what should happen when the button is pressed, so it's automatically disabled.

To change that, all we need to change is to define a method. Even if this method is empty and does absolutely nothing, its existence will automatically "color" the button.

So, change to this:

onPressed: () {},

That's it, nothing else. Now, your app looks like this:

The structure of () {} is another shortcode for so-called anonymous functions in Dart language. It's just a function that doesn't need to have a name, because it's just used here once, for the exact event on the button. So, () defines an anonymous function, and its body is {}, which happens to be empty. But it's still a working function.

To prove that the onPressed event is actually working, let's change it to this:

onPressed: () { print('Login attempt'); },

And then, after you click the button, take a look at Android Studio console, at the bottom, and you'll see something like this:

I clicked the button four times, in this example.

So, that's it for this lesson, we have styled our fields of email+password+button. In the next lesson, we will continue styling it with padding and some more properties.

Discussion

0 comments