MDC-101 Flutter: Material Components Basics

1. Introduction

What are Material Design and the Material Flutter library?

Material Design is a system for building bold and beautiful digital products. By uniting style, branding, interaction, and motion under a consistent set of principles and components, product teams can realize their greatest design potential.

The Material Flutter library includes Flutter widgets which implement the designs of Material Design components (MDC for short) to create a consistent user experience across apps and platforms. As the Material Design system evolves, these components are updated to ensure consistent pixel-perfect implementation, adhering to Google's front-end development standards.

In this codelab, you'll build a login page using several of Material Flutter's components.

What you'll build

This codelab is the first of four codelabs that will guide you through building an app called Shrine, an e-commerce app that sells clothing and home goods. It will demonstrate how you can customize components to reflect any brand or style using Material Flutter.

In this codelab, you'll build a login page for Shrine that contains:

  • An image of Shrine's logo
  • The name of the app (Shrine)
  • Two text fields, one for entering a username and the other for a password
  • Two buttons

Android

iOS

Shrine login page on Android

Shrine login page on iOS

Material Flutter components and subsystems in this codelab

  • Text field
  • Button
  • Ink ripple (a visual form of feedback for touch events)

How would you rate your level of experience with Flutter development?

Novice Intermediate Proficient

2. Set up your Flutter development environment

You need two pieces of software to complete this lab—the Flutter SDK and an editor.

You can run the codelab using any of these devices:

  • A physical Android or iOS device connected to your computer and set to Developer mode.
  • The iOS simulator (requires installing Xcode tools).
  • The Android Emulator (requires setup in Android Studio).
  • A browser (Chrome is required for debugging).
  • As a Windows, Linux, or macOS desktop application. You must develop on the platform where you plan to deploy. So, if you want to develop a Windows desktop app, you must develop on Windows to access the appropriate build chain. There are operating system-specific requirements that are covered in detail on docs.flutter.dev/desktop.

3. Download the codelab starter app

The starter project is located in the material-components-flutter-codelabs-101-starter/mdc_100_series directory.

...or clone it from GitHub

To clone this codelab from GitHub, run the following commands:

git clone https://github.com/material-components/material-components-flutter-codelabs.git
cd material-components-flutter-codelabs/mdc_100_series
git checkout 101-starter

Open the project and run the app

  1. Open the project in your editor of choice.
  2. Follow the instructions to "Run the app" in Get Started: Test drive for your chosen editor.

Success! The starter code for Shrine's login page should be running on your device. You should see the Shrine logo and the name "Shrine" just below it.

Android

iOS

Shrine logo

Shrine logo

Let's look at the code.

Widgets in login.dart

Open up login.dart. It should contain this:

import 'package:flutter/material.dart';

class LoginPage extends StatefulWidget {
  const LoginPage({Key? key}) : super(key: key);

  @override
  _LoginPageState createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  // TODO: Add text editing controllers (101)
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: ListView(
          padding: const EdgeInsets.symmetric(horizontal: 24.0),
          children: <Widget>[
            const SizedBox(height: 80.0),
            Column(
              children: <Widget>[
                Image.asset('assets/diamond.png'),
                const SizedBox(height: 16.0),
                const Text('SHRINE'),
              ],
            ),
            const SizedBox(height: 120.0),
            // TODO: Remove filled: true values (103)
            // TODO: Add TextField widgets (101)
            // TODO: Add button bar (101)
          ],
        ),
      ),
    );
  }
}

Notice that it contains an import statement and two new classes:

  • The import statement allows using the Material library in this file.
  • The LoginPage class represents the entire page displayed in the simulator.
  • The _LoginPageState class's build() function controls how all the widgets in our UI are created.

4. Add TextField widgets

To begin, we'll add two text fields to our login page, where users enter their username and password. We'll use the TextField widget, which displays a floating label and activates a touch ripple.

This page is structured primarily with a ListView, which arranges its children in a scrollable column. Let's put text fields below.

Add the TextField widgets

Add two new text fields and a spacer after const SizedBox(height: 120.0).

// TODO: Add TextField widgets (101)
// [Name]
TextField(
  decoration: const InputDecoration(
    filled: true,
    labelText: 'Username',
  ),
),
// spacer
const SizedBox(height: 120.0),
// [Password]
TextField(
  decoration: const InputDecoration(
    filled: true,
    labelText: 'Password',
  ),
  obscureText: true,
),

The text fields each have a decoration: field that takes an InputDecoration widget. The filled: field means the background of the text field is lightly filled in to help people recognize the tap or touch target area of the text field. The second text field's obscureText: true value automatically replaces the input that the user types with bullets, which is appropriate for passwords.

Save your project (with the keystroke: command + s) which performs a Hot Reload.

You should now see a page with two text fields for Username and Password! Check out the floating label animation:

Android

iOS

Shrine logo with username and password fields

5. Add buttons

Next, we'll add two buttons to our login page: "Cancel" and "Next." We'll use two kinds of button widgets: the TextButton and the ElevatedButton.

Add the OverflowBar

After the text fields, add the OverflowBar to the ListView's children:

// TODO: Add button bar (101)
OverflowBar(
  alignment: MainAxisAlignment.end,
  // TODO: Add a beveled rectangular border to CANCEL (103)
  children: <Widget>[
    // TODO: Add buttons (101)
  ],
),

The OverflowBar arranges its children in a row.

Add the buttons

Then add two buttons to the OverflowBar's list of children:

    // TODO: Add buttons (101)
    TextButton(
      child: const Text('CANCEL'),
      onPressed: () {
        // TODO: Clear the text fields (101)
      },
    ),
    // TODO: Add an elevation to NEXT (103)
    // TODO: Add a beveled rectangular border to NEXT (103)
    ElevatedButton(
      child: const Text('NEXT'),
      onPressed: () {
    // TODO: Show the next page (101) 
      },
    ),

Save your project. Under the last text field, you should see two buttons appear:

Android

iOS

Shrine logo with username and password fields, cancel and next buttons

Shrine logo with username and password fields, cancel and next buttons

The OverflowBar handles the layout work for you. It positions the buttons horizontally, so they appear side by side.

Touching a button initiates an ink ripple animation, without causing anything else to happen. Let's add functionality into the anonymous onPressed: functions, so that the cancel button clears the text fields, and the next button dismisses the screen:

Add TextEditingControllers

To make it possible to clear the text fields' values, we'll add TextEditingControllers to control their text.

Right under the _LoginPageState class's declaration, add the controllers as final variables.

  // TODO: Add text editing controllers (101)
  final _usernameController = TextEditingController();
  final _passwordController = TextEditingController();

On the first text field's controller: field, set the _usernameController:

// TODO: Add TextField widgets (101)
// [Name]
TextField(
  controller: _usernameController,

On the second text field's controller: field, now set the _passwordController:

// TODO: Add TextField widgets (101)
// [Password]
TextField(
  controller: _passwordController,

Edit onPressed

Add a command to clear each controller in the TextButton's onPressed: function:

    // TODO: Clear the text fields (101)
    _usernameController.clear();
    _passwordController.clear();

Save your project. Now when you type something into the text fields, hitting cancel clears each field again.

This login form is in good shape! Let's advance our users to the rest of the Shrine app.

Pop

To dismiss this view, we want to pop (or remove) this page (which Flutter calls a route) off the navigation stack.

In the ElevatedButton's onPressed: function, pop the most recent route from the Navigator:

        // TODO: Show the next page (101) 
        Navigator.pop(context);

Lastly, open up home.dart and set resizeToAvoidBottomInset to false in the Scaffold:

    return Scaffold(
      // TODO: Add app bar (102)
      // TODO: Add a grid view (102)
      body: Center(
        child: Text('You did it!'),
      ),
      // TODO: Set resizeToAvoidBottomInset (101)
      resizeToAvoidBottomInset: false,
    );

Doing this ensures that the keyboard's appearance does not alter the size of the home page or its widgets.

That's it! Save your project. Go ahead and click "Next."

You did it!

Android

iOS

screen that says 'you did it'

screen that says 'you did it'

This screen is the starting point for our next codelab, which you'll work on in MDC-102.

6. Congratulations!

We added text fields and buttons and hardly had to consider layout code. Material Components for Flutter come with a lot of style and can be placed on screen almost effortlessly.

Next steps

Text fields and buttons are two core components in the Material System, but there are many more! You can also explore the rest in the Material Components widgets catalog.

Alternatively, go to MDC-102: Material Design Structure and Layout to learn about the components covered in MDC-102 for Flutter.

I was able to complete this codelab with a reasonable amount of time and effort

Strongly agree Agree Neutral Disagree Strongly disagree

I would like to continue using Material Components in the future

Strongly agree Agree Neutral Disagree Strongly disagree