Flutter Widgets: Text, Icon, & Image (Assets & Network) | Ch. 8

A collage for Flutter Course Chapter 8, showing the Text widget with

Welcome to Chapter 8! In our last chapter, we became layout architects. We mastered the "Big Four" of layout: Container, Column, Row, and Stack. We learned how to build the "skeleton" of any UI.

But a skeleton is just bones. It's time to add the "flesh." This chapter is all about the "content" widgets—the three widgets you will use more than any others. These are the widgets that actually show your users information.

We're going to master the "Essential Three":

  1. Text: For displaying text.
  2. Icon: For displaying vector icons.
  3. Image: For displaying, well... images!

The Image widget section is the most critical part of this chapter. We will do a deep dive into the two ways to load an image: from the network (like a URL) and, most importantly, from your app's local assets. This will be our first time editing the pubspec.yaml file to add our own files, which is a fundamental skill.

Let's get started.

1. `Text`: More Than Just Words

The Text widget is simple. You give it a string, and it displays it.


@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Center(
      child: Text('Hello, Flutter!'),
    ),
  );
}

Boring, right? The real power of the Text widget is in its style property.

The style Property & TextStyle

The style property takes a TextStyle widget. This is where you define everything about how the text looks. We used this in our Digital Business Card project, but let's see a full example.


@override
Widget build(BuildContext context) {
  return Scaffold(
    backgroundColor: Colors.black, // Dark background
    body: Center(
      child: Text(
        'I am the Walrus.',
        style: TextStyle(
          color: Colors.white,
          fontSize: 32.0,
          fontWeight: FontWeight.bold, // Makes the text bold
          fontStyle: FontStyle.italic, // Makes the text italic
          letterSpacing: 2.0, // Space between letters
          wordSpacing: 10.0, // Space between words
          fontFamily: 'Pacifico', // The custom font we used in Ch 5
          decoration: TextDecoration.underline,
          decorationColor: Colors.red,
          decorationStyle: TextDecorationStyle.wavy,
        ),
      ),
    ),
  );
}

With TextStyle, you have complete, granular control over how your text is rendered.

  • color: Sets the text color.
  • fontSize: Sets the size in logical pixels.
  • fontWeight: Controls the "thickness" (e.g., FontWeight.bold, FontWeight.w100).
  • fontStyle: FontStyle.italic.
  • decoration: TextDecoration.underline, TextDecoration.overline, or TextDecoration.lineThrough.

Text Alignment & Overflow

What if your text is very long? You have three properties to control this: textAlign, maxLines, and overflow.


@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Center(
      child: Container(
        width: 300, // Give the Text a limited width
        color: Colors.grey[200],
        padding: EdgeInsets.all(10.0),
        child: Text(
          'This is a very, very, very long piece of text that will almost certainly not fit on a single line. We must control the overflow!',
          
          // 1. Alignment: How to align text within its box
          textAlign: TextAlign.center, // or .left, .right, .justify
          
          // 2. Max Lines: How many lines to show
          maxLines: 2,
          
          // 3. Overflow: What to do with the extra text
          overflow: TextOverflow.ellipsis, // This is the '...'
        ),
      ),
    ),
  );
}

The overflow property is fantastic.

  • TextOverflow.ellipsis: Shows the "..." at the end. This is the most common.
  • TextOverflow.fade: Fades the text out at the end.
  • TextOverflow.clip: Just cuts the text off.

RichText & TextSpan

What if you want one part of your text to be a different style? Like this: "Hello, World!"

You can't do that with a single Text widget. For this, you need the RichText widget.

RichText works with TextSpans, which are "spans" of text that can be nested, each with its own style.


@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Center(
      child: RichText(
        text: TextSpan(
          // This is the "default" style for all children
          style: TextStyle(
            fontSize: 30.0,
            color: Colors.black,
          ),
          children: <TextSpan>[
            TextSpan(text: 'Hello, '),
            TextSpan(
              text: 'World',
              style: TextStyle(
                fontWeight: FontWeight.bold,
                color: Colors.blue,
              ),
            ),
            TextSpan(text: '! Welcome to '),
            TextSpan(
              text: 'Flutter',
              style: TextStyle(
                fontStyle: FontStyle.italic,
                color: Colors.green,
                fontSize: 34.0,
              ),
            ),
          ],
        ),
      ),
    ),
  );
}

2. `Icon`: A Thousand Pictures in One Font

The Icon widget is one of the easiest to use. Flutter comes bundled with the entire Material Design Icon font.

This means you have thousands of high-quality icons ready to use, for free, with no setup.

To use one, you just call Icon(Icons.THE_ICON_NAME).


@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Center(
      // We can put them in a Row (from Ch 7!)
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: <Widget>[
          Icon(
            Icons.phone,
            color: Colors.green,
            size: 50.0,
          ),
          Icon(
            Icons.email,
            color: Colors.red,
            size: 50.0,
          ),
          Icon(
            Icons.favorite,
            color: Colors.pink,
            size: 50.0,
          ),
          Icon(
            Icons.home,
            color: Colors.blue,
            size: 50.0,
          ),
        ],
      ),
    ),
  );
}

The two properties you'll use 99% of the time are color and size. That's all there is to it. It's just a piece of "text" from a special font, so it scales perfectly and can be any color.

3. `Image`: Bringing Your App to Life

This is the big one. Your app will feel static until you add images. In Flutter, there are two primary ways to display an image:

  1. Image.network: Load an image from a URL on the internet.
  2. Image.asset: Load an image that is bundled *inside* your app.

Image.network (The Easy Way)

This is the fastest way to get an image on the screen. Find a URL, and plug it in.


@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Center(
      child: Image.network(
        'https://picsum.photos/400', // A random 400x400 image
      ),
    ),
  );
}

Run this, and you'll see a random image! But... what happens while it's loading? You just see a blank space. What happens if the internet is down, or the URL is broken? You get an ugly error.

This is bad UI. A *professional* app handles these cases. Image.network gives us builders for this.


@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Center(
      child: Image.network(
        'https://picsum.photos/400', // A random 400x400 image
        
        // This is what to show WHILE the image is loading
        loadingBuilder: (BuildContext context, Widget child,
            ImageChunkEvent? loadingProgress) {
          if (loadingProgress == null) {
            return child; // The image is done, show it
          }
          // The image is still loading, show a progress indicator
          return Center(
            child: CircularProgressIndicator(),
          );
        },
        
        // This is what to show IF the image fails to load
        errorBuilder: (BuildContext context, Object exception,
            StackTrace? stackTrace) {
          return Center(
            child: Icon(
              Icons.error,
              color: Colors.red,
              size: 40.0,
            ),
          );
        },
      ),
    ),
  );
}

This is much more robust. Your user now sees a CircularProgressIndicator (a spinning circle) while it loads, and a helpful Icon if it fails.

Image.asset (The "Right" Way)

What about images that are *part* of your app? Your logo? A welcome banner? You can't rely on the internet for those. You need to "bundle" them with your app.

This is called an "asset," and it's a 5-step process.

This is the single most common place new Flutter developers get stuck. Pay close attention to these steps, especially the indentation in Step 3!

If you just try Image.asset('my_image.png'), your app will crash. You must tell Flutter about your asset first.

The 5-Step Guide to Adding Assets

Let's add a local image (e.g., logo.png) to our project.

Step 1: Create an `assets` folder

In the root of your Flutter project (at the same level as your lib and pubspec.yaml folders), create a new folder. You can call it `assets` or `images`. assets is the standard.

Step 2: Add your image to the folder

Copy your logo.png file from your computer and paste it inside this new assets folder.

Step 3: Edit your `pubspec.yaml` file

This is the critical step. Open your pubspec.yaml file. This is your app's "ID card" and "ingredient list" (from Chapter 4).

Scroll down until you find the flutter: section. It will look like this (commented out):


# To add assets to your application, add an assets section, like this:
# assets:
#   - images/a_dot_burr.jpeg
#   - images/a_dot_ham.jpeg

We need to "uncomment" this and tell it about our new assets folder.

INDENTATION IS CRITICAL. YAML (the language pubspec.yaml is written in) uses two spaces for indentation. Not tabs.

Make your flutter: section look exactly like this:


flutter:
  uses-material-design: true

  # This is the part you add
  assets:
    - assets/

What did we do?

  • assets: is "uncommented" (no #) and is indented with two spaces (so it lines up with uses-material-design).
  • - assets/ is *also* "uncommented" and is indented with four spaces (or two spaces relative to assets:).
  • By adding the slash (/) at the end, we are telling Flutter, "I want to include everything inside the assets folder." This is easier than adding each file one by one.

Step 4: Stop and Restart your app

When you save pubspec.yaml, your IDE will run flutter pub get. But this is not always enough.

A simple Hot Reload is not enough to load new assets. You must STOP your app completely and RESTART it (Ctrl+F5 or "Run Without Debugging").

Step 5: Use Image.asset in your code

Now, finally, you can use the widget. Flutter knows about your assets/ folder, so you can just reference the file path.


@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Center(
      child: Image.asset(
        'assets/logo.png', // The path from the root of your project
      ),
    ),
  );
}

If you followed all 5 steps, your logo.png will appear on the screen. If you get a "Failed to load asset" error, go back and check your `pubspec.yaml` indentation!

BoxFit: Fitting Your Image

One last thing. What if your Image is inside a Container with a specific size, but the image is a different shape?

Use the fit property with BoxFit to control this.


      child: Container(
        width: 300,
        height: 150,
        color: Colors.grey,
        child: Image.asset(
          'assets/logo.png', // Let's say this logo is square
          
          // This tells the image how to fit in the non-square box
          fit: BoxFit.contain,
        ),
      ),
  • BoxFit.contain: (Default) The image will shrink to fit inside the box. It will not be cropped. This may leave empty space.
  • BoxFit.cover: This is the most common. The image will scale up to fill the entire box. Parts of the image may be cropped to avoid empty space.
  • BoxFit.fill: The image will stretch and warp to fill the box. This usually looks bad.

Conclusion: Your UI Has Content!

This chapter was a big one. You've moved from "empty boxes" to apps that can show real, dynamic content.

You've learned:

  • How to style Text with TextStyle, control its overflow, and even style individual words with RichText.
  • How to use the thousands of built-in Icons.
  • How to load an Image.network and show loading/error states.
  • The critical 5-step process for adding an Image.asset to your app, including the all-important pubspec.yaml edit.
  • How to control image scaling with BoxFit.

You now have 90% of the "display" widgets you will ever need. You have the skeleton (layout) and the flesh (content). What's missing?

Interaction. How does the user *do* anything?

In the next chapter, we'll learn about user interaction. We'll master Buttons, TextFields (for user input), and the GestureDetector for making any widget tappable.

Go try to build a social media "post" UI. You can do it now. You'll need a Column, then a Row (for the avatar and name), then an Image.network (for the post image), and finally another Row (for the Like, Comment, Share Icons). You have all the pieces!

Comments