# Designing the UI with Widgets

  • As you know by now, in Flutter (almost) everything is a widget!
  • Before writing your first real app, there are some basic widgets you should know
  • We'll introduce most of them in this section: the Container, Text, Row and Column, Image, and ElevatedButton widgets
  • Together with Scaffold and AppBar, these widgets give you the basic tools to start working with Flutter

WIDGET OF THE WEEK

There are many more widgets than we can cover in this course. On the Flutter YouTube channel, a widget is placed in the spotlight every week. It is highly recommended taking a look there, also for getting ideas of what widgets to use in your project.

WIDGET OF THE WEEK

# Building a Profile app

  • To illustrate the use of the widgets, we will create a very simple, but beautiful app with one page: a profile page
  • To make it a little bit more interesting, we will pretend it is the profile page of the very famous Swedish YouTuber PewDiePie

# Create the app

  • Open VS Code, choose View | Command Palette. Search for flutter and choose Flutter: New Project

new project

  • Choose the Application template
  • Save the app in your Flutter source folder
  • Enter the name of the project profile_app

profile_app

  • When the app is created, delete the testfile widget_test.dart in the test folder

# Material Design

  • As in the first example, we will prettify our user interface by using Material Design
  • Replace the content of main.dart with the following code:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

import 'package:flutter/material.dart';
import 'package:profile_app/pages/home.dart';

void main() {
  runApp(const ProfileApp());
}

class ProfileApp extends StatelessWidget {
  const ProfileApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Profile',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const HomePage(),
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
code description
import 'package:flutter/material.dart'; this package contains all Flutter widgets implementing Material Design
import 'package:profile_app/pages/home.dart'; import the file that contains the implementation of our page (we will create this file in a moment)
main entry point of a Flutter application
runApp will run our Profile app
class ProfileApp ... our app is a StatelessWidget
MaterialApp() defines that the app will use material design
debugShowCheckedModeBanner remove that ugly debug banner
theme we will use a blue theme (for the banner, buttons, ...)
home: HomePage() property to define the widget for the default route of the app, this will be the first (and only) page to show when the App starts

TIP

Always use meaningful names for your apps and use the suffix App.

# Create a very simple page

  • To organize your code a little bit, create a folder pages in the lib folder. The pages folder will contain the different pages of our app. In this case, it will be just one page.
  • In the pages folder, create a new file home.dart
  • Paste the following code
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

import 'package:flutter/material.dart';

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Profile"),
      ),
      body: Container(
        alignment: Alignment.center,
        color: Colors.blue[400],
        child: const Text("PewDiePie"),
      ),
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
code description
Scaffold() class that implements the basic material design visual layout structure
appBar property from Scaffold class to define an application bar
AppBar() class to implement the application bar
title property from AppBar class for the title of the application bar
body property from Scaffold class to to define the primary content of this scaffold

TIP

Always use meaningful names for your pages and use the suffix Page.

# Using Containers

  • Containers contain other widgets, you could think of a container as a div element.
code description
alignment alignment of the widgets of content of the container
color color of the container
child the container contains one child, a text widget with the text PewDiePie
  • Try to change the alignment to Alignment.bottomRight, Alignment.centerLeft, ... and see what happens. At the end choose Alignment.center.
  • We can also specify a width and height for our container and place it in the center of the page like this









 
 

 
 



 




class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Profile"),
      ),
      body: Center(
        child: Container(
          alignment: Alignment.center,
          width: 190.0,
          height: 250.0,
          color: Colors.blue[400],
          child: const Text("PewDiePie"),
        ),
      ),
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  • But the width and height doesn't improve our UI, so comment them out.
 
 

          //width: 190.0,
          //height: 250.0,
1
2

# Margin and padding

  • Margin is the distance between a widget and the other widgets on the screen.
  • Padding is the distance between the content of a widget and its borders.
Margin and padding
  • You can use the EdgeInsets.all to create a margin or padding on all four sides of a box, top, right, bottom, and left.
  • Use the EdgeInsets.only if you want to specify the side you want to create the margin or padding, i.e. EdgeInsets.only(left: 15.0)
  • Try the code below and see what happens





 





      body: Center(
        child: Container(
          alignment: Alignment.center,
          // width: 190.0,
          // height: 250.0,
          margin: const EdgeInsets.all(50.0),
          color: Colors.blue[400],
          child: const Text("PewDiePie"),
        ),
      ),
1
2
3
4
5
6
7
8
9
10
  • Mmm, a little bit disappointing. Let's remove the margin, so comment it out and we'll try using padding instead


 



 





      body: Center(
        child: Container(
          padding: const EdgeInsets.all(15.0),
          alignment: Alignment.center,
          // width: 190.0,
          // height: 250.0,
          // margin: const EdgeInsets.all(50.0),
          color: Colors.blue[400],
          child: const Text("PewDiePie"),
        ),
      ),
1
2
3
4
5
6
7
8
9
10
11
  • Your app should look like this now. Nothing to be proud off, but we will add some cool UI widgets right away
PewDiePie

# Add Style to Text

# Downloading a font

  • First let's add a nice font to our project.
  • Go to Google fonts
  • Click on the Sansita Swashed font and download the font by clicking Download family

Download font

  • Create a folder fonts in your project folder and copy the ttf-files (from the staticsubfolder) into it

fonts

  • Now import the font into your project by modifying the file pubspec.yaml
 
 
 
 

  fonts:
    - family: SansitaSwashed
      fonts:
        - asset: fonts/SansitaSwashed-Regular.ttf
1
2
3
4

YAML = Error sensitive

  • As you know an error is easily made in a .yaml file (remember Homestead.yaml)
  • When you add the fonts section, you have to make sure that it is indented by just 2 spaces

TIP

pubspec.yaml is a file that contains information about our project metadata and dependencies. We can use it to declare the fonts we are using, as well as other assets in our projects.

  • Modify your code


 
 
 
 
 
 
 


          child: const Text(
            "PewDiePie",
            style: TextStyle(
              fontSize: 40.0,
              decoration: TextDecoration.none,
              fontFamily: 'SansitaSwashed',
              fontWeight: FontWeight.normal,
              color: Colors.white,
            ),
          ),
1
2
3
4
5
6
7
8
9
10
code description
style to add some style to a Text widget, we'll use the style property that takes a TextStyle object
fontSize fontSize property, which requires a double value
decoration for TextDecoration we choose the none value. We could also choose overline, underline, or lineThrough
fontFamily family name of the imported font in pubspec.yaml
fontWeight we could also choose bold
color textcolor
  • This is already a start
PewDiePie

Restart application

  • It's possible that your font doesn't change (even after restarting the debugger)
  • In this case just completely stop the application and start debugging again in VS Code

# Positioning Widgets with Rows and Columns

  • The Row layout is a list of child widgets placed horizontally. The Column is a list of child widgets placed vertically.
Rows and columns
  • Row contains an array of widgets so it does not have a child property, but the children property that contains the array. Column also has a children property that contains the array with children widgets.
  • We 'll modify the code and, using a Column widget, we'll show two Text Widgets, one above the other
  • The HomePage class now looks like this
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Profile"),
      ),
      body: Container(
        padding: const EdgeInsets.all(15.0),
        alignment: Alignment.center,
        color: Colors.blue[400],
        child: const Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              "PewDiePie",
              style: TextStyle(
                  fontSize: 40.0,
                  decoration: TextDecoration.none,
                  fontFamily: 'SansitaSwashed',
                  fontWeight: FontWeight.normal,
                  color: Colors.white),
            ),
            SizedBox(height: 30),
            Text(
              "Swedish YouTuber, comedian, gamer and philanthropist",
              style: TextStyle(
                  fontSize: 20.0,
                  decoration: TextDecoration.none,
                  fontFamily: 'SansitaSwashed',
                  fontWeight: FontWeight.normal,
                  color: Colors.white),
              textAlign: TextAlign.center,
            ),
            // Row comes here
          ],
        ),
      ),
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

REMARK

  • We removed the Center widget, since it does not work with Columns. To center a Column, we used MainAxisAlignment.center instead.
  • We used a SizedBox of height 30.0 to put some extra space between the two Text widgets.
  • We also removed the comments.
  • Of course, Row widgets and Column widgets can be nested.
  • Below the last Text widget (within the children array of Column), we'll put a Row with three Text widgets
  • We have changed the position of some const keywords in the code above.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

            Row(
              children: <Widget>[
                Text(
                  "12035 posts",
                  textAlign: TextAlign.center,
                ),
                Text(
                  "1205021 followers",
                  textAlign: TextAlign.center,
                ),
                Text(
                  "2563 following",
                  textAlign: TextAlign.center,
                ),
              ],
            ),
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# Filling space with Expanded

  • As you can see, the total space of the Row isn't used by the three Text children. We can easily solve that by using an Expanded, a widget that expands a child of a row or a column. The child will expand to fill the available space horizontally for a row or vertically for a column.
Expanded
  • Replace the Row with the code below
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

            Row(
              children: <Widget>[
                Expanded(
                  child: Text(
                    "12035 posts",
                    textAlign: TextAlign.center,
                  ),
                ),
                Expanded(
                  child: Text(
                    "1205021 followers",
                    textAlign: TextAlign.center,
                  ),
                ),
                Expanded(
                  child: Text(
                    "2563 following",
                    textAlign: TextAlign.center,
                  ),
                ),
              ],
            ),
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  • You can see the difference below
Fout     Goed

# Divider

  • Let's put a horizontal line above and below the row. This can be done using a Divider widget.
 

            Divider(height: 50.0, color: Colors.white),
1

# Create a custom widget

  • As you might have guessed by now, it will be an impossible task to design the UI of the whole page in one build method. We have still a lot of work to do and our dart code already looks quite messy. We can solve this by splitting up our UI in small custom widgets.
  • Create a new folder widgets in the lib folder.
  • Inside the widgets folder, create a new file numberlabel.dart. This widget will contain a number and a label.
  • Paste the following code
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

import 'package:flutter/material.dart';

class NumberLabelWidget extends StatelessWidget {
  final String text;
  final int count;

  const NumberLabelWidget(
      {Key? key, required this.text, required this.count}) 
      : super(key: key);

  
  Widget build(BuildContext context) {
    return Expanded(
      child: Column(
        children: <Widget>[
          Text(
            count.toString(),
            style: const TextStyle(
              fontSize: 20.0,
              decoration: TextDecoration.none,
              color: Colors.white,
              fontWeight: FontWeight.normal,
            ),
          ),
          Text(
            text.toUpperCase(),
            style: const TextStyle(
              fontSize: 20.0,
              decoration: TextDecoration.none,
              color: Colors.white,
              fontWeight: FontWeight.normal,
            ),
          ),
        ],
      ),
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
code description
class NumberLabelWidget extends StatelessWidget our custom widget is a StatelessWidget
final String text; final int count; the widget has two properties text and count
const NumberLabelWidget({Key? key, required this.text, required this.count}): super(key: key); the constructor of the widget saves the parameters (text and count) in the properties. Dart does this automatically. You don't have to write this code yourself. Both parameters are required by the way. As always we'll use the optional key parameter, when calling the constructor of the super class.
Widget build(BuildContext context) { override the build method to render the widget
Column we use a Column widget to position the count widget above the text widget

TIP

Always use meaningful names for your widgets and use the suffix Widget.

  • Now we can use our custom widget in our HomePage.
  • First import the dart-file containing the widget.
  • Then replace the Row with the Text widgets by the Row with the custom NumberLabelWidget's

 

 
 
 
 
 
 
 
 
 
 
 
 
 
 

import 'package:flutter/material.dart';
import 'package:profile_app/widgets/numberlabel.dart';
...
            Row(children: <Widget>[
              NumberLabelWidget(
                count: 12035,
                text: "posts",
              ),
              NumberLabelWidget(
                count: 1205021,
                text: "followers",
              ),
              NumberLabelWidget(
                count: 2563,
                text: "following",
              ),
            ]),
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  • Now we are getting somewhere
Custom widget

# Adding Images from Assets

  • There are several sources you can choose from to display images in your Flutter app.
  • We will load images from assets. Assets are resources you add to your project.
  • Create a folder assets in the root folder of you app.
  • Download the avatar of PewDiePie and copy it into the assets folder.
  • Open the pubspec.yaml file. Here, you need to create a section for your images.








 
 

flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  # To add assets to your application, add an assets section, like this:
  assets:
    - assets/pewdiepie.png
1
2
3
4
5
6
7
8
9
10
  • As good developers, we like to build our app in a modular way, so we'll create a custom widget once again to show the PewDiePie avatar.
  • Inside the widgets folder, create a new file circleimage.dart. This widget will be a rounded image.
  • Paste the following code
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

import 'package:flutter/material.dart';

class CircleImageWidget extends StatelessWidget {
  final String imageName;

  const CircleImageWidget(
      {Key? key, required this.imageName}) 
      : super(key: key);

  
  Widget build(BuildContext context) {
    AssetImage avatarAsset = AssetImage(imageName);
    return Image(
      image: avatarAsset,
      width: 100.0,
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
code description
class CircleImageWidget extends StatelessWidget our custom widget is a StatelessWidget
final String imageName; the widget has only one property imageName, the name of the image to use as avatar
AssetImage avatarAsset = AssetImage(imageName); this will create an asset from the imageName we specify in the constructor
return Image(image: avatarAsset); create the image widget and in the constructor, we'll specify an image property that will be our avatar asset
  • Now we can use our custom widget in our HomePage.
  • First import the dart-file containing the widget.
  • Then insert the CircleImageWidget as the first widget in the children property of the Column widget. We'll put some extra space between the avatar and PewDiePie's name
 

 
 
 
 

import 'package:profile_app/widgets/circleimage.dart';
...
            CircleImageWidget(
              imageName: "assets/pewdiepie.png",
            ),
            SizedBox(height: 30),
1
2
3
4
5
6
  • Restart your app (since you added an asset file), you should see the PewDiePie avatar

# CircleAvatar

  • Not bad, but normally, avatars are shown with rounded borders. Modify the build method of your custom widget
 
 
 
 
 
 
 
 

  
  Widget build(BuildContext context) {
    AssetImage avatarAsset = AssetImage(imageName);
    return CircleAvatar(
      radius: 100.0,
      backgroundImage: avatarAsset,
    );
  }
1
2
3
4
5
6
7
8
  • That looks more like it!
Avatar

# Giving Feedback

# ElevatedButton

  • We will create a button at the bottom. When our users press the button, we want to show a message to give feedback.
  • We will use an ElevatedButton, a button with an elevation, but there's also a flat TextButton. You can try this one yourself.
  • Let's make our final custom widget.
  • Inside the widgets folder, create a new file iconbutton.dart. Our button will also contain an icon.
  • Paste the following code
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

import 'package:flutter/material.dart';

typedef MyCallback = void Function(String text);

class IconButtonWidget extends StatelessWidget {
  final String text;
  final IconData icon;
  final MyCallback onButtonPressed;

  const IconButtonWidget(
      {Key? key, required this.text, required this.icon, required this.onButtonPressed}) 
      : super(key: key);

  
  Widget build(BuildContext context) {
    return ElevatedButton(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Icon(icon),
          const SizedBox(
            width: 5,
          ),
          Text(text.toUpperCase())
        ],
      ),
      onPressed: () {
        onButtonPressed(text);
      },
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
code description
typedef MyCallback = void Function(String text); when we press the button in our widget, we want to activate a callback function in our HomePage. This callback function will be a void with one parameter, namely the text on the button that has been pressed.
final String text; the widget has a property text, the text that is displayed on the button
final IconData icon; the widget has a property icon, the icon shown on the button
final MyCallback onButtonPressed; we will pass the callback-function in the onButtonPressed parameter
return ElevatedButton( create the ElevatedButton
child: Row( we will use the Rowwidget to show the icon next to the text
  • Now we can use our custom widget in our HomePage.
  • First import the dart-file containing the widget.
  • Then insert the IconButtonWidget as the last widget in the children property of the Column widget
 

 
 
 
 
 

import 'package:profile_app/widgets/iconbutton.dart';
...
            IconButtonWidget(
                icon: Icons.person,
                text: "follow",
                onButtonPressed: (text) {
                }),
1
2
3
4
5
6
7
  • Because the callback function onButtonPressed can't be in a const widget, we have to bring down the const one level (from Column). You can do this yourself.

# AlertDialog

  • To finish our Profile app, we want to give feedback when the user pressed the button.
  • Paste the code below in the HomePage class
 
 
 
 
 
 
 

  void _follow(String text, BuildContext context) {
    var alert = AlertDialog(
      title: Text(text.toUpperCase()),
      content: const Text("Registration successfully completed!"),
    );
    showDialog(context: context, builder: (BuildContext context) => alert);
  }
1
2
3
4
5
6
7
code description
var alert = AlertDialog( create a new AlertDialog window with a title and a content
showDialog(context: context, builder: (BuildContext context) => alert); Call the showDialog method passing the context and the builder that will return our alert that we have just set up.
  • Finally pass the _follow function as the callback function of the custom widget.
  • The complete code of our HomePage is
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

import 'package:flutter/material.dart';
import 'package:profile_app/widgets/numberlabel.dart';
import 'package:profile_app/widgets/circleimage.dart';
import 'package:profile_app/widgets/iconbutton.dart';

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Profile"),
      ),
      body: Container(
        padding: const EdgeInsets.all(15.0),
        alignment: Alignment.center,
        color: Colors.blue[400],
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const CircleImageWidget(
              imageName: "assets/pewdiepie.png",
            ),
            const SizedBox(height: 30),
            const Text(
              "PewDiePie",
              style: TextStyle(
                  fontSize: 40.0,
                  decoration: TextDecoration.none,
                  fontFamily: 'SansitaSwashed',
                  fontWeight: FontWeight.normal,
                  color: Colors.white),
            ),
            const SizedBox(height: 30),
            const Text(
              "Swedish YouTuber, comedian, gamer and philanthropist",
              style: TextStyle(
                  fontSize: 20.0,
                  decoration: TextDecoration.none,
                  fontFamily: 'SansitaSwashed',
                  fontWeight: FontWeight.normal,
                  color: Colors.white),
              textAlign: TextAlign.center,
            ),
            const Divider(height: 50.0, color: Colors.white),
            const Row(children: <Widget>[
              NumberLabelWidget(
                count: 12035,
                text: "posts",
              ),
              NumberLabelWidget(
                count: 1205021,
                text: "followers",
              ),
              NumberLabelWidget(
                count: 2563,
                text: "following",
              ),
            ]),
            const Divider(height: 50.0, color: Colors.white),
            IconButtonWidget(
                icon: Icons.person,
                text: "follow",
                onButtonPressed: (text) {
                  _follow(text, context);
                }),
          ],
        ),
      ),
    );
  }

  void _follow(String text, BuildContext context) {
    var alert = AlertDialog(
      title: Text(text.toUpperCase()),
      content: const Text("Registration successfully completed!"),
    );
    showDialog(context: context, builder: (BuildContext context) => alert);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
  • And our final Profile page looks like this
Completed     AlertDialog

IMPORTANT

Maybe the most important lesson, you should have learned in this section, is - for modularity, better readability, simplicity and ease of maintenance - to always split your page into seperate custom widgets.

# Exercise - One two three (part 1)

  • In this exercise you will develop an app with which primary school children can practice their arithmetic skills. In the first part, you will design the UI. In the second part you will program the code. The app looks like this
Flags

# Create the app

  • Create a new flutter application with name one_two_three
  • Create the following directories in the lib folder to organize your code: pages and widgets
  • Delete widget_test.dart in the test folder
  • In main.dart open a MaterialApp with the right colors. The name of the app is OneTwoThreeApp. Remove that ugly debug banner. In the home property call the page widget for the default route of the app: HomePage

# Design the UI of the HomePage

  • Create a new file home.dart in the pages folder
  • Create HomePage, a stateless widget (for now at least). Design the UI of the page. Use a Scaffold widget with AppBar.
  • Also make use of the Column and Row widgets.
  • Create a custom widget for the buttons, namely NumberButton. This custom widget has a number property and a onButtonPressed callback function with one parameter, the number of the button being pressed.

# Download a font

Last Updated: 9/12/2023, 11:42:34 AM