Skip to content

Designing UIs with Dart

Adarsh Kumar Maurya edited this page Dec 9, 2018 · 3 revisions

Designing the UI with Widgets

Introducing Basic Widgets

Welcome to this chapter of the tutorial, Flutter: Getting Started. In Flutter, there are some widgets that are considered the basic widgets in the documentation page.These are the widgets you should know before writing your first app and we introduce most of them in this chapter.

What we'll cover includes

  • Container
  • Text
  • Row and Column
  • Image
  • RaisedButton
  • AlertDialog widget : to give feedback to our users
  • Box Constraints
  • Size, Margins, and Padding.

These widgets, together with Scaffold and App bar that we introduced in the previous chapter, give you the basic tools to start working with Flutter.

Even though we are using Visual Studio Code to build our Flutter app, in this chapter, I show you an alternative, which is using the Flutter CLI to create and run your projects.

The CLI works with any editor you choose, even the Windows Notepad or the Mac Text Edit or a Linux vi. If we were cooking you could consider this chapter as the phase of ingredients gathering.

We'll be collecting the basic ingredients we need in order to prepare our delicious dish. To give you an idea of what we'll be building, this is the app we'll build during this chapter. It contains several texts with fonts, columns, rows, an image, and the button, and when you press this button, the user gets an alerts dialog. Okay. Let's get ready to bake some code.

Using Containers

In this lesson, we'll have a look at containers that, as the name implies, contain other widgets. But before doing that, we'll create a new project. We already know that to do that, from Visual Studio Code, we could open the Command palette and choose the Flutter: New Project action and this is easy and fast.

But this time, let's see an alternative that incidentally works with any text editor. In order to use the Flutter CLI, you need your command prompt or terminal and you type flutter with some other keywords.

So for example, if you want to create a new project, you can just type flutter create [project name ].

So open the command prompt or terminal. Let's get to a directory where you want to create your project.

Then type flutter create app_widgets and press Enter.

Adarsh:flutter-getting-started adarshmaurya$ flutter create app_widgets
Creating project app_widgets...
  app_widgets/ios/Runner.xcworkspace/contents.xcworkspacedata (created)
  app_widgets/ios/Runner/Info.plist (created)
  app_widgets/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png (created)
  app_widgets/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png (created)
  app_widgets/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md (created)
  app_widgets/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json (created)
  app_widgets/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png (created)
  app_widgets/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png (created)
  app_widgets/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png (created)
  app_widgets/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png (created)
  app_widgets/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png (created)
  app_widgets/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png (created)
  app_widgets/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png (created)
  app_widgets/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png (created)
  app_widgets/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json (created)
  app_widgets/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png (created)
  app_widgets/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png (created)
  app_widgets/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png (created)
  app_widgets/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png (created)
  app_widgets/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png (created)
  app_widgets/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png (created)
  app_widgets/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png (created)
  app_widgets/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png (created)
  app_widgets/ios/Runner/Base.lproj/LaunchScreen.storyboard (created)
  app_widgets/ios/Runner/Base.lproj/Main.storyboard (created)
  app_widgets/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata (created)
  app_widgets/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (created)
  app_widgets/ios/Flutter/Debug.xcconfig (created)
  app_widgets/ios/Flutter/Release.xcconfig (created)
  app_widgets/ios/Flutter/AppFrameworkInfo.plist (created)
  app_widgets/test/widget_test.dart (created)
  app_widgets/app_widgets.iml (created)
  app_widgets/.gitignore (created)
  app_widgets/.metadata (created)
  app_widgets/ios/Runner/AppDelegate.h (created)
  app_widgets/ios/Runner/main.m (created)
  app_widgets/ios/Runner/AppDelegate.m (created)
  app_widgets/ios/Runner.xcodeproj/project.pbxproj (created)
  app_widgets/android/app/src/main/res/mipmap-mdpi/ic_launcher.png (created)
  app_widgets/android/app/src/main/res/mipmap-hdpi/ic_launcher.png (created)
  app_widgets/android/app/src/main/res/drawable/launch_background.xml (created)
  app_widgets/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png (created)
  app_widgets/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png (created)
  app_widgets/android/app/src/main/res/values/styles.xml (created)
  app_widgets/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png (created)
  app_widgets/android/app/src/main/AndroidManifest.xml (created)
  app_widgets/android/gradle/wrapper/gradle-wrapper.properties (created)
  app_widgets/android/gradle.properties (created)
  app_widgets/android/settings.gradle (created)
  app_widgets/pubspec.yaml (created)
  app_widgets/README.md (created)
  app_widgets/lib/main.dart (created)
  app_widgets/android/app/build.gradle (created)
  app_widgets/android/app/src/main/java/com/example/appwidgets/MainActivity.java (created)
  app_widgets/android/build.gradle (created)
  app_widgets/android/app_widgets_android.iml (created)
  app_widgets/.idea/runConfigurations/main_dart.xml (created)
  app_widgets/.idea/libraries/Flutter_for_Android.xml (created)
  app_widgets/.idea/libraries/Dart_SDK.xml (created)
  app_widgets/.idea/libraries/KotlinJavaRuntime.xml (created)
  app_widgets/.idea/modules.xml (created)
  app_widgets/.idea/workspace.xml (created)
Running "flutter packages get" in app_widgets...             5.8s
Wrote 64 files.

All done!
[✓] Flutter is fully installed. (Channel stable, v1.0.0, on Mac OS X 10.14.1 18B75, locale en-IN)
[!] Android toolchain - develop for Android devices is partially installed; more components are available.
    (Android SDK 28.0.3)
[!] iOS toolchain - develop for iOS devices is partially installed; more components are available. (Xcode 10.1)
[✓] Android Studio is fully installed. (version 3.2)
[!] IntelliJ IDEA Ultimate Edition is partially installed; more components are available. (version 2018.2.3)
[✓] VS Code is fully installed. (version 1.29.1)
[✓] Connected device is fully installed. (1 available)

Run "flutter doctor" for information about installing additional components.

In order to run your application, type:

  $ cd app_widgets
  $ flutter run

Your application code is in app_widgets/lib/main.dart.

Adarsh:flutter-getting-started adarshmaurya$

This will create your app. Leaving the command prompt or terminal open, let's get to Visual Studio Code and open the project folder. As you can see, the project structure has been created and we find our lib folder and the main.dart file. So the result is exactly the same as before when we used the Visual Studio Code procedure, but this works without relying on any specific editor.

In case this shouldn't work on your system, there is another instruction you should try. Just type flutter doctor from the command prompt.This will generally give you hints to help you understand why Flutter isn't working as expected.

Now let's get rid of the starting code in the main.dart file and also delete the test file. Let's import our material.dart library. Next, let's create the main function. In the runApp method, we create a new MaterialApp, and inside that, we set the title property to Simple Layouts. For the home property, let's call a new Home class that we'll create in a moment. Right, the Home class does not exist yet, so let's create a new directory into our project. Let's say UI, and here, we'll create a new home.dart file. Again, we'll import our material.dart class and here we'll create a Home class that extends a StatelessWidget.As we've already seen, when we extend a StatelessWidget, we need to override the build method. Let's use the Visual Studio quick fix to do that. In the build method, we'll return a container. Now a container widget is useful because of its properties. If you are familiar with web development, you could think of a container as a div element. For example, you could specify an alignment. Let's say Alignment.center and you can see that here like for colors, we have a few constants that help us choosing alignment values as well. And as are speaking of colors, let's also add a color to our container. Let's say deepOrangeAccent. Let's also add the child, which will be a Text Pizza, and the textDirection left to right. Okay, let's import our class in the main.dart file.

To run the app, we'll be using the command prompt or terminal. The command to run an app is flutter run.

Adarsh:app_widgets adarshmaurya$ flutter run
Using hardware rendering with device Android SDK built for x86. If you
get graphics artifacts, consider enabling software rendering with
"--enable-software-rendering".
Launching lib/main.dart on Android SDK built for x86 in debug mode...
Initializing gradle...                                       2.9s
Resolving dependencies...                                    1.6s
Gradle task 'assembleDebug'...
Gradle task 'assembleDebug'... Done                          8.8s
Built build/app/outputs/apk/debug/app-debug.apk.
Installing build/app/outputs/apk/app.apk...                  2.6s
D/EGL_emulation(20980): eglMakeCurrent: 0xdfb85900: ver 3 0 (tinfo 0xdfb83df0)
Syncing files to device Android SDK built for x86...
D/        (20980): HostConnection::get() New Host Connection established 0xddd31180, tid 21069
D/EGL_emulation(20980): eglMakeCurrent: 0xe59c1760: ver 3 0 (tinfo 0xde2ec530)
 1.4s

🔥  To hot reload changes while running, press "r". To hot restart (and
rebuild state), press "R".
An Observatory debugger and profiler on Android SDK built for x86 is
available at: http://127.0.0.1:54211/
For a more detailed help message, press "h". To detach, press "d"; to
quit, press "q".

Just make sure to type flutter run into your project folder. After a while, we'll be able to see our app. As expected, our text is centered. Don't worry about the text, it's terrible, but we'll fix it in the next lesson. Note that the background color of the whole screen is deep orange and that means that the container is taking all the screen space. Let's play a little bit with alignment. We could try the top right alignment. The hot reload works a bit differently with a CLI. We need to press a lowercase r in order to see our changes and see how it looks. Then, bottom left, reload, centerRight, reload. As you can see, you have a great control over the position of the content of a container. Let's get back to center.

Now, of course, one of the features of a container is its size, and unsurprisingly, you have two properties that can help you with that, height and width. So let's specify 192.0 for our width and 96.0 for our height, both are double, and let's run the app from here.

Well, the result of this code is a bit surprising. Width and height are completely ignored in our device. What's wrong?

Box Constraints

Well there are some rules in Flutter that are called Box constraints. Box constraints consist of minimum and maximum widths and heights. We could say that every widget is a box and some boxes have minimum and maximum width and heights. These constraints sometimes are passed to children.

For example, in the MaterialApp widget, the box is given a constraint that forces it to exactly fill the application content area, which is the entire screen in our code, and this constraint is passed to the child that is our container. That's why our container ignores the width and height properties.

If you want to learn more about constraints in Flutter, have a look at the link- Flutter Layout . The good news is that some boxes loosen the constraint meaning that the maximum is maintained, but the minimum is removed and one of those is the Center widget.

So in order to be able to use the width and height properties of our container widget, we have to break the constraint that's actually making it as large as the screen by including it in a Center widget.

Let's try this out. And now you can see that width and height work fine. Let's comment out width and height and set some margins. By the way, comments in Dart, like in several other languages, can be single line or multiple line. And chances are, you're already familiar with this syntax.

Two slashes mean a singleline comment

/* This is another comment, but it's a bit longer than the previous one . <- Multiline comment */

opening slash and start and closing start and slash is used for multiline. Even if Flutter developers recommend you always use single line comments multiple times for multiline comments,except when you need to comment out some code, which is what we'll do now. 

When you want to create documentation for your code, you use three slashes. 

/// Documentation <- Code documentation (single line)

***Margin***
Margin is the distance between a widget and the other widgets on the screen. 
***Padding***
Padding is the distance between the content of a widget and its borders. 

Both use the EdgeInsets.All constructor. `EdgeInsets.All()` creates a margin or padding on all four sides of a box, top, right, bottom, and left. If you want to choose which side or sides you want to create your margins, you can use EdgeInsets.only() and then specify the sides you want to create your offset.

```dart
EdgeInsets.All(16.0)

EdgeInsets.only(left:16.0)

Let's try a margin of 50 for all the 4 sides with EdgeInsets.All(50.0) and you can see that now we have a black space all around our orange container.

We could also try a left margin, for example, with EdgeinSets.only(left: 50.0). Now the black space is only on the left side. Okay, let's comment this out. We won't need margins for the remaining of this chapter. Good. Now you know how to know how to use containers in your Flutter apps, but the text we have put here is still terrible. Let's play a little bit with our text next.

Adding Style to Text

The text widget displays a string of text with a single style. It can be multiline or single line. We have already seen that it has a text direction property, but you can manipulate it through several other properties. Among those, we'll see the style because we want to do what you generally do with text, which is deciding how it should look. Okay. Let's delete the container properties so that we can focus on our text. We'll begin by adding some style. The property we'll be using is style that takes a TextStyle object. Into that, we'll begin by adding a fontSize property, which requires a double value. Let's try 30.0 and we can have a look at the result. And from now on, we'll use Visual Studio Code to run the app. If the app is still running on your terminal or command prompt, you can stop it with Ctrl or Cmd+C. When prompted, just type Y to confirm.

Now we can start the app again, this time, with Visual Studio Code and we can see our text is smaller than before. If we make it 15 or 80, we can definitely see the difference. Now this text is underlined. Let's say we want to remove that. We could play with the decoration property and use the TextDecoration object and choose the none value. We could also choose overline, underline, or lineThrough and the result is exactly as expected.

Now you might ask how do I choose a custom font. Well, here's the process.

Using Fonts

Import Font Files -> pubspec.yaml -> Use the fonts in Widgets

First, you import the font files, then you declare the font in the pubspec.yaml file, and then you use the font in a specific widget. Alternatively, you could also set a font as the default in your app, but we won't need that right now.

Let's create a new folder in our project folder called fonts. Next, let's go to fonts.google.com, which is a great resource to look for and download free fonts. Here, we choose oxygen. And after taking a deep breath, we select the font and download it. Once the download has completed, we'll open the zip file and copy its content into the fonts directory of our project. You can see that the font we chose has three different styles and this is okay for this project, but there are fonts that have even 10 or 15 different styles, and if you make heavy use of typography in your project, you might want to have a look at those.

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. YAML, ain't markup language, is a language used to store data. It is commonly used for configuration files and it uses indentation to indicate nesting. Here, we'll uncomment the first few lines of the fonts section with Ctrl+K, Ctrl+U. We have three fonts to include here, the regular, the bold version, after the bold, we'll also specify the weight. So the bold will be 700 and the live version that has an asset property of fonts/Oxygen-Light.ttf that has a weight of 300. Let's remove the other fonts. Okay, we are now ready to try those out in our text widget. First, let's choose the regular font. The font family property of our TextStyle will be Oxygen. Now we can choose the fontWeight property. As we have downloaded three fonts, we'll try all three. Let's begin with the light version and select the FontWeight.w300 value, and if we have a look, we can see the light version of our font. If we choose w400 or normal, we'll see the regular version, and if we choose w700 or bold, our app behaves as expected. Okay, let's get back to normal. We could choose fonts with several more versions so you have more control over the look of your fonts, but this is enough for our purpose. Next, we'll see how to deal with positioning in our screen with rows and columns.

Screen Space with Rows and Columns

In any UI, the way you place objects in the available space is extremely important. There are two simple widgets that may help you a lot, Rows and Columns.

Row and Column Layout The row layout is a list of child widgets placed horizontally and the column is a list of child widgets placed vertically.

Row contains an array of widgets so it does not have a child property, but the children property that contains the array. So let's copy our text and put it here, and then paste it again so that we have two texts. Let's say that in each row of our UI, we want to place a text for the name of a Pizza, and another text for its ingredients. The name of the pizza is Margherita, which by the way is delicious, and the ingredients are Tomato, Mozzarella cheese, and Basil. Let's make the text size of the name and the ingredients both 30 andlet's have a look at the result. You can see that we have a yellow and black square that's telling us we have a problem. This is because the row widget does not scroll and it is considered an error to have more children in a row than will fit in the available room. 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 in the main axis. That means horizontally for a row or vertically for a column. As you can see, putting the text inside an expanded widget solves our problem. Okay, let's make the ingredients font size a bit smaller, a 20. If multiple children are expanded, the available space is divided among them. In other words, when you need to make a child expand to fill the available horizontal space, just wrap the child in an expanded widget.

import 'package:flutter/material.dart';

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
        child: Container(
      alignment: Alignment.center,
      color: Colors.deepOrangeAccent,
      child: Row(
        children: <Widget>[
          Expanded(
              child: Text(
            "Margherita",
            textDirection: TextDirection.ltr,
            style: TextStyle(
              fontSize: 20.0,
              decoration: TextDecoration.none,
              fontFamily: 'Oxygen',
              fontWeight: FontWeight.normal,
            ),
          )),
          Expanded(
              child: Text(
            "Tomato, Mozzarella, Basil",
            textDirection: TextDirection.ltr,
            style: TextStyle(
              fontSize: 20.0,
              decoration: TextDecoration.none,
              fontFamily: 'Oxygen',
              fontWeight: FontWeight.normal,
            ),
          )),
        ],
      ),
      // width: 192.0,
      // height: 96.0,
      // margin: EdgeInsets.only(left:50.0),
    ));
  }
}

As we want to add a second pizza under the first one, this is the time to use a column widget that will contain two rows for two different pizzas. So let's set a column widget that also has a children property that contains an array of widgets. Then let's move the row so that it'sthe first element of the array. Let's put a comma at the end of the row and paste the row again for the second pizza. The second pizza is called marinara, and it's very simple, just Tomato and Garlic. Let's have a look at the result.

import 'package:flutter/material.dart';

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
        child: Container(
            alignment: Alignment.center,
            color: Colors.deepOrangeAccent,
            child: Column(children: <Widget>[
              Row(
                children: <Widget>[
                  Expanded(
                      child: Text(
                    "Margherita",
                    textDirection: TextDirection.ltr,
                    style: TextStyle(
                      fontSize: 20.0,
                      decoration: TextDecoration.none,
                      fontFamily: 'Oxygen',
                      fontWeight: FontWeight.normal,
                    ),
                  )),
                  Expanded(
                      child: Text(
                    "Tomato, Mozzarella, Basil",
                    textDirection: TextDirection.ltr,
                    style: TextStyle(
                      fontSize: 20.0,
                      decoration: TextDecoration.none,
                      fontFamily: 'Oxygen',
                      fontWeight: FontWeight.normal,
                    ),
                  )),
                ],
              ),
                  Row(
                children: <Widget>[
                  Expanded(
                      child: Text(
                    "Marinara",
                    textDirection: TextDirection.ltr,
                    style: TextStyle(
                      fontSize: 20.0,
                      decoration: TextDecoration.none,
                      fontFamily: 'Oxygen',
                      fontWeight: FontWeight.normal,
                    ),
                  )),
                  Expanded(
                      child: Text(
                    "Tomato, Garlic",
                    textDirection: TextDirection.ltr,
                    style: TextStyle(
                      fontSize: 20.0,
                      decoration: TextDecoration.none,
                      fontFamily: 'Oxygen',
                      fontWeight: FontWeight.normal,
                    ),
                  )),
                ],
              )
            ]
                // width: 192.0,
                // height: 96.0,
                // margin: EdgeInsets.only(left:50.0),
                )));
  }
}

You can see that now the column widget that takes the whole available space on the screen has aligned its children from top to bottom, and that's okay, but in order to make it a bit nicer, let's play with padding.

As you may remember, padding is the space between a container and tis content. Let's use the EdgeInsets.only this time and specify a top of 30 and a left of 10, and then, let's have a look at our layout.

import 'package:flutter/material.dart';

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
        child: Container(
            alignment: Alignment.center,
            padding: EdgeInsets.only(top:30.0, left:10.0),
            color: Colors.deepOrangeAccent,

...

That's better. Now you know how to use rows and columns to better use the space on your screen and the ingredients of two great pizzas. Next, let's add some pepper to our app with a nice picture.

Adding Images from Assets

There are several sources you can choose from to display images in your Flutter app.

Source: File, Assets, Network, Provider, Memory

Format: jpg, png, gif(animated), bmp, webP(animated), wBmp

For example, you could load images from the web or from memory.

In this lesson, we load images from assets. Assets are resources you add to your project. In this case, we'll deal with images, but they could be anything really. So first, we'll create a new directory called images. Let's do this in the root folder or our project.Next, we choose an image from openclipart.org, which by the way, is a great resource when you are looking for simple images for your apps as their license is free also for commercial use. Okay, let's search for a pizza here and we find a ton of images. I choose the first one, which right now, is the dancing pizza, but feel free to choose whatever you like. Note that Flutter does not currently support SVG images, so well have to choose the PNG format. Let's download the medium image. This will serve decently most devices. Now let's put the image we just saved into the Images folder into our project.Next, let's get to the pubspec.yaml file.

name: app_widgets
description: A new Flutter project.

# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# Read more about versioning at semver.org.
version: 1.0.0+1

environment:
  sdk: ">=2.0.0-dev.68.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2

dev_dependencies:
  flutter_test:
    sdk: flutter


# For information on the generic Dart part of this file, see the
# following page: https://www.dartlang.org/tools/pub/pubspec

# The following section is specific to Flutter.
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:
   - images/pizza.png


  # An image asset can refer to one or more resolution-specific "variants", see
  # https://flutter.io/assets-and-images/#resolution-aware.

  # For details regarding adding assets from package dependencies, see
  # https://flutter.io/assets-and-images/#from-packages

  # To add custom fonts to your application, add a fonts section here,
  # in this "flutter" section. Each entry in this list should have a
  # "family" key with the font family name, and a "fonts" key with a
  # list giving the asset and other descriptors for the font. For
  # example:
  fonts:
    - family: Oxygen
      fonts:
        - asset: fonts/Oxygen-Regular.ttf
        - asset: fonts/Oxygen-Bold.ttf
          weight: 700
        - asset: fonts/Oxygen-Light.ttf
          weight: 300
  # For details regarding fonts from package dependencies,
  # see https://flutter.io/custom-fonts/#from-packages

Here, we need to create a section for our images. So let's uncomment the asset function and put the right name for our image, which in this case, is pizza1. Now we want to show this image. Let's say that in the Home screen we want to place the image under thepizza names, which means that we'll use the column widget and we'll create a third element under the two rows we already have. To make things a bit more modular, we can create a new class here thatcan call PizzaImageWidget. This will extend a StatelessWidget.

import 'package:flutter/material.dart';

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
        child: Container(
            alignment: Alignment.center,
            padding: EdgeInsets.only(top: 30.0, left: 10.0),
            color: Colors.deepOrangeAccent,
            child: Column(children: <Widget>[
              Row(
                children: <Widget>[
                  Expanded(
                      child: Text(
                    "Margherita",
                    textDirection: TextDirection.ltr,
                    style: TextStyle(
                      fontSize: 20.0,
                      decoration: TextDecoration.none,
                      fontFamily: 'Oxygen',
                      fontWeight: FontWeight.normal,
                    ),
                  )),
                  Expanded(
                      child: Text(
                    "Tomato, Mozzarella, Basil",
                    textDirection: TextDirection.ltr,
                    style: TextStyle(
                      fontSize: 20.0,
                      decoration: TextDecoration.none,
                      fontFamily: 'Oxygen',
                      fontWeight: FontWeight.normal,
                    ),
                  )),
                ],
              ),
              Row(
                children: <Widget>[
                  Expanded(
                      child: Text(
                    "Marinara",
                    textDirection: TextDirection.ltr,
                    style: TextStyle(
                      fontSize: 20.0,
                      decoration: TextDecoration.none,
                      fontFamily: 'Oxygen',
                      fontWeight: FontWeight.normal,
                    ),
                  )),
                  Expanded(
                      child: Text(
                    "Tomato, Garlic",
                    textDirection: TextDirection.ltr,
                    style: TextStyle(
                      fontSize: 20.0,
                      decoration: TextDecoration.none,
                      fontFamily: 'Oxygen',
                      fontWeight: FontWeight.normal,
                    ),
                  )),
                ],
              ),
              PizzaImageWidget(),
            ]
                // width: 192.0,
                // height: 96.0,
                // margin: EdgeInsets.only(left:50.0),
                )));
  }
}

class PizzaImageWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    AssetImage pizzaAsset = AssetImage('images/pizza.png');
    Image image = Image(
      image: pizzaAsset,
      width: 200.0,
      height: 200.0,
    );
    return Container(child: image);
  }
}

Let's make Visual Studio Code create the build method. Now the first thing we need to do is create an AssetImage, let's call it pizzaAsset. This will create an asset from the path we specify in the constructor. Then we can create the image widget, let's call it image, and in the constructor, we'll specify an image property that will be our pizza asset. A width, let's say 400, and a height, again 400, both as doubles. Finally, we can return a container that as a child will have our image. And in the Home class, we'll just add PizzaImageWidget.Let's run the app and see if everything's working as expected. Nice, isn't it. Now you know an effective way to show images to your users. Next, we'll have a look at the RaisedButton.

Giving Feedback: RaisedButton and AlertDialog

The RaisedButton is, well, a button with an elevation. You should use it when the rest of the content is flat like in our screen right now or when there is a lot of empty space. What we'll do now is create a RaisedButton below the pizza image. So again, the RaisedButton will be one of the children of the column widget. When our users press the button, we want to show a message to give feedback. Let's create a new class called OrderButton that will extend a StatelessWidget. Here, we'll create the build method. Now let's delete the default comment. Here, we can create a Container with some top margin so that the button will have some distance from the image above. Let's say it will be 50. As a child of this container, we'll put a RaisedButton. The button has a child property that will contain a text. Let's say Order your Pizza!, a color, we can make it lightGreen, an elevation 5, and this is very important, it must have an onPressed property that we'll call a function. If you omit this property, the button will be flat and grayed out. Okay, let's create an anonymous function here that we'll call an order function that we'll create shortly, and to this function, we will pass the current context that has been passed in the build method. So we have to create a new function that will return void. We call it order with a build context called context as a parameter. BuildContext is, like its name is implying, the context in which a specific widget is built. In this case, we want an alert to pop up over the screen. The context will specify over what other object we want to show the alert. Let's create a variable called alert. This will be an AlertDialog widget. It has a title and a content. For the title, we'll write a Text widget with Order Completed, and for the content, another Text widget with Thanks for your order. Then we call the showDialog method passing the context and the builder that will return our alert that we have just set up. Builder is a function handler so we need to create a function that accepts a single argument BuildContext and returns a widget, which is our alert.

class OrderButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var button = new Container(
        margin: EdgeInsets.only(top: 50.0),
        child: RaisedButton(
          child: Text("Order a Pizza"),
          color: Colors.lightGreen,
          elevation: 5.0,
          onPressed: () {
            order(context);
          },
        ));
    return button;
  }

  void order(BuildContext context) {
    var alert = AlertDialog(
      title: Text("Order Completed"),
      content: Text("Thanks for your order"),
    );
    showDialog(
        context: context,
        builder: (BuildContext context) {
          return alert;
        });
  }
}

Now let's set this widget as the fourth element of our column widget and try this out.

import 'package:flutter/material.dart';

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
        child: Container(
            alignment: Alignment.center,
            padding: EdgeInsets.only(top: 30.0, left: 10.0),
            color: Colors.deepOrangeAccent,
            child: Column(children: <Widget>[
              Row(
                children: <Widget>[
                  Expanded(
                      child: Text(
                    "Margherita",
                    textDirection: TextDirection.ltr,
                    style: TextStyle(
                      fontSize: 20.0,
                      decoration: TextDecoration.none,
                      fontFamily: 'Oxygen',
                      fontWeight: FontWeight.normal,
                    ),
                  )),
                  Expanded(
                      child: Text(
                    "Tomato, Mozzarella, Basil",
                    textDirection: TextDirection.ltr,
                    style: TextStyle(
                      fontSize: 20.0,
                      decoration: TextDecoration.none,
                      fontFamily: 'Oxygen',
                      fontWeight: FontWeight.normal,
                    ),
                  )),
                ],
              ),
              Row(
                children: <Widget>[
                  Expanded(
                      child: Text(
                    "Marinara",
                    textDirection: TextDirection.ltr,
                    style: TextStyle(
                      fontSize: 20.0,
                      decoration: TextDecoration.none,
                      fontFamily: 'Oxygen',
                      fontWeight: FontWeight.normal,
                    ),
                  )),
                  Expanded(
                      child: Text(
                    "Tomato, Garlic",
                    textDirection: TextDirection.ltr,
                    style: TextStyle(
                      fontSize: 20.0,
                      decoration: TextDecoration.none,
                      fontFamily: 'Oxygen',
                      fontWeight: FontWeight.normal,
                    ),
                  )),
                ],
              ),
              PizzaImageWidget(),
              OrderButton(),
            ]
                // width: 192.0,
                // height: 96.0,
                // margin: EdgeInsets.only(left:50.0),
                )));
  }
}

We can see that the RaisedButton shows up, and if we click it,the alert appears, but we can do better here.

We can use the fat arrow syntax to have everything in a single line and we can omit the return statement. Cleaner, isn't it. And it still works.

...
 void order(BuildContext context) {
    var alert = AlertDialog(
      title: Text("Order Completed"),
      content: Text("Thanks for your order"),
    );
    showDialog(context: context, builder: (BuildContext context) => alert);
  }
}

Okay, let's briefly recap what we've seen in this chapter.

  • We have introduced several basic widgets, the container with its alignment and size.
  • We've seen how to use and style the text widget to make our text appear as we'd like.
  • We've used rows and columns to place widgets over the screen.
  • We have included a fantastic pizza image loading it from an asset.
  • And finally, we've used a RaisedButton to give feedback to our users, and in doing that,
  • We've encountered Flutter box constraints with its minimum heights and widths.
  • We've seen how to import fonts in our app.
  • We've talked about position, margin, padding, and we've seen how to add it the pubspec.yaml configuration file for our project.
  • We covered a lot of ground here, and you are now ready for the next great building block of your Flutter toolkit, the stateful widgets.