Google Flutter - Stateless Widgets

Google Flutter - Stateless Widgets

Introduction

The purpose of this chapter is to introduce stateless widgets and how they can be used.

Not All Widgets Need to be Smart

If you look a user interface, it consists of many Widgets but not many of them have to be smart or interact with the user.

If you look at the default flutter application, there are several widgets but only in fact one Widget with any interactions with the user – the "MyHomePage" Widget that has a counter that counts up when the user clicks on the floating button.

So, the rest of the widgets are used to display something, not interact with the user. That is what stateless widgets are for.

Minimum Code

Here is the minimum code you need for a Stateless Widget:


class EmptyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return;
  }
}      

Creation

Stateless widgets are created by a parent widget in its "build" method. They are given the information they need to do their job when they are created.

Stateless widgets receive arguments (information) from their parent widget in the "build" method, which they store in final member variables.

Example

The code below creates an instance of the CarWidget. Notice how it passes car data to that widget through the constructor.


CarWidget("Bmw", "M3", "https://media.ed.edmunds-media.com/bmw/m3/2018/oem/2018_bmw_m3_sedan_base_fq_oem_4_150.jpg"),

 

  • "Bmw" - Stored in member variable "make".
  • "M3" - Stored in member variable "model".
  • "https://media.ed.edmunds-media.com/bmw/m3/2018/oem/2018_bmw_m3_sedan_base_fq_oem_4_150.jpg" - Stored in member variable "imageSrc".

Rendering

"Build" Method

  • Stateless Widgets generate their UI in their "build" method, the result of which is rendered by Flutter.
  • They can build their UI using values from their member variables, or from other sources.
  • They cannot force themselves to re-render.

Rendering Using Data from Member Variables

When a Stateless Widget is asked to build a UI, it can use the values from these member variables to render the UI (probably with other Stateless Widget children). These values don"t change, they are set in the constructor and that"s it.

Example

Remember earlier how we passed car data to the widget through the constructor? The code below builds a UI to display this data, sourced from the member variables that we set in the constructor.

@override
Widget build(BuildContext context) {
  return Center(
    child: Column(children: [
      Text(make),
      Text(model),
      Image.network(imageSrc)
    ]));
}      

Rendering Using Data from Other Sources

When a Stateless Widget is asked to build a UI, it can use values from other sources, for example InheritedWidgets (which can store information).

Example

The code below builds a UI to say "Hi There", using information from another source (the "Theme" inherited widget) to determine text color.

@override
Widget build(BuildContext context) {
return Center(
    child: Column(children: [
    Text("Hello", style: Theme.of(context).textTheme.display1),
    Text("There", style: Theme.of(context).textTheme.display1)
]));
}

When Does The "Build" Method Execute?

  • The first time the widget is rendered by Flutter (when the 'build' method returns the widget and it is inserted in the ui).
  • When the widget"s parent changes.
  • When the values in another source change, for example when an InheritedWidget it depends on changes.

Lifecycle

These widgets are throw-away widgets, they don’t hang around. You create them in the ‘build’ method of another widget, and they are re-created every time that ‘build’ of the parent widget runs.

Example - "First Stateless"

Let's start off by creating a basic app with Stateless Widgets. Later on, we enhance it to make it look more attractive. Code is here.

Step 1 – Create Default Flutter App

Follow the instructions here.

Step 2 – Replace Application Code

Replace the contents of file ‘main.dart’ in folder ‘lib’ with the following:

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(title: 'Cars'),
    );
  }
}

class MyHomePage extends StatelessWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        appBar: new AppBar(
          title: new Text(this.title),
        ),
        body: new Column(children: [
          CarWidget("Bmw", "M3",
              "https://media.ed.edmunds-media.com/bmw/m3/2018/oem/2018_bmw_m3_sedan_base_fq_oem_4_150.jpg"),
          CarWidget("Nissan", "GTR",
              "https://media.ed.edmunds-media.com/nissan/gt-r/2018/oem/2018_nissan_gt-r_coupe_nismo_fq_oem_1_150.jpg"),
          CarWidget("Nissan", "Sentra",
              "https://media.ed.edmunds-media.com/nissan/sentra/2017/oem/2017_nissan_sentra_sedan_sr-turbo_fq_oem_4_150.jpg"),
        ]));
  }
}

class CarWidget extends StatelessWidget {
  CarWidget(this.make, this.model, this.imageSrc) : super();

  final String make;
  final String model;
  final String imageSrc;

  @override
  Widget build(BuildContext context) {
    return Center(
        child: Column(children: [
      Text(make),
      Text(model),
      Image.network(imageSrc)
    ]));
  }
}
    

Step 3 – Open Emulator & Run

Plugin your phone or open your emulator. Run the app. You should get something like the following:

Summary So Far

  • The MyApp & Material App Widgets are unchanged.
  • The MyHomePage Widget is unchanged except for the build method, which now contains a Column Widget (see below) containing 3 Car Widgets. Note how we pass the information to each Car Widget in the constructor.
  • We have a new StatelessWidget called CarWidget. It accepts data in the constructor. In the build method it returns a Center Widget (see below) that contains a Column Widget (see below) that contains 3 widgets: a Text Widget for the make, another for the model and an Image Widget for the image.
  • Widgets used (more info about widgets in Chapter ‘Flutter Widgets’).
    • Column Widget - Layout Widget that displays its children vertically.
    • Center Widget - Layout Widget that centers its child.
    • Text Widget - Displays text.
    • Image Widget - Displays an image.

Step 4 - Enhancement - Add Some Padding

Now let’s add some more vertical padding between each car to spread them out a bit. This is achieved by wrapping the existing Center Widget in the ‘build’ method in the CarWidget with a Padding Widget. Note how the Padding constructor requires a ‘padding’ argument and a ‘child’ argument.

Change the ‘build’ method in the CarWidget to the following:


@override
Widget build(BuildContext context) {
   return Padding(
       padding: EdgeInsets.all(20.0),
       child: Center(
           child: Column(children: [
         Text(make),
         Text(model),
         Image.network(imageSrc)
       ])));
}

Now the cars are more spaced out.

Step 5 - Enhancement - Add Scrolling

Depending on how your emulator is setup, you may see Chevrons at the bottom. This is because you have run out of vertical space.

The remedy for this is simple. Edit the MyHomePage Widget and change the Column (the one that contains the CarWidgets) to a ListView.


@override
Widget build(BuildContext context) {
  return new Scaffold(
      appBar: new AppBar(
         title: new Text(this.title),
       ),
       body: new ListView(children: [
         CarWidget(“Bmw”, “M3",
             “https://media.ed.edmunds-media.com/bmw/m3/2018/oem/2018_bmw_m3_sedan_base_fq_oem_4_150.jpg“),
         CarWidget(“Nissan”, “GTR”,
             “https://media.ed.edmunds-media.com/nissan/gt-r/2018/oem/2018_nissan_gt-r_coupe_nismo_fq_oem_1_150.jpg”),
         CarWidget(“Nissan”, “Sentra”,
             “https://media.ed.edmunds-media.com/nissan/sentra/2017/oem/2017_nissan_sentra_sedan_sr-turbo_fq_oem_4_150.jpg”),
       ]));
 }

Step 6 – Enhancement - Add Border

Let’s add a border around each car. This is achieved by wrapping the existing Center Widget in the ‘build’ method in the CarWidget with a Container Widget which has a border decoration and padding.

@override
 Widget build(BuildContext context) {
   return Padding(
       padding: EdgeInsets.all(20.0),
       child: Container(
           decoration: BoxDecoration(border: Border.all()),
           padding: EdgeInsets.all(20.0),
           child: Center(
               child: Column(children: [
             Text(make),
             Text(model),
             Image.network(imageSrc)
           ]))));
 } 

It looks much nicer now:

Step 7 - Enhancement - Final Touches

As a final touch, lets:

  • Combine the make and model together using string interpolation.
  • Change the make and model text style to be bigger.
  • Add some padding between text and image. Wrap image with padding at top

@override
Widget build(BuildContext context) {
   return Padding(
       padding: EdgeInsets.all(20.0),
       child: Container(
           decoration: BoxDecoration(border: Border.all()),
           padding: EdgeInsets.all(20.0),
           child: Center(
               child: Column(children: [
             Text(‘${make} ${model}’, style: TextStyle(fontSize: 24.0)),
             Padding(
                 padding: EdgeInsets.only(top: 20.0),
                 child: Image.network(imageSrc))
           ]))));
 }