© Rap Payne 2019
R. PayneBeginning App Development with Flutterhttps://doi.org/10.1007/978-1-4842-5181-2_3

3. Everything Is Widgets

Rap Payne1 
(1)
Dallas, TX, USA
 
Let’s pretend that you are an insanely talented Lego nerd and got offered one of the few coveted jobs as a Lego Master Builder. Congrats! Let’s also say that your first assignment is to build a six-foot-tall Thor made from 26,000 Legos (Figure 3-1).
../images/482531_1_En_3_Chapter/482531_1_En_3_Fig1_HTML.jpg
Figure 3-1

A Lego Thor. The author snapped this picture at a movie theater once

How would you go about doing that? Ponder that for a minute. Go ahead, we’ll wait.

Would you just start grabbing bricks and putting them together? Probably not. Would you lay out the soles of Thor’s feet and build from the bottom up? Again, no. Here’s my guess as to your common-sense strategy:
  1. 1.

    You’d get a vision of what you’re building. Figure the whole thing out.

     
  2. 2.

    Realize that the entire project is too complex to build at once.

     
  3. 3.

    Break the project into sections (legs, left arm, right arm, torso, left sword, right sword, helmet, cape, head).

     
  4. 4.

    Realize that each of them is still too complex.

     
  5. 5.

    For each section, you break it into sub-sections.

     
  6. 6.

    Repeat steps 4 and 5 until you’ve got simple enough components that each is easy to understand, build, and maintain – for you and for any teammates that you may have.

     
  7. 7.

    Create each simple component.

     
  8. 8.

    Combine simple components to form the larger, more complex components.

     
  9. 9.

    Repeat steps 7 and 8 until you’ve got your entire project created.

     

This process has a name: componentization, and is exactly the thought process we’ll go through with our Flutter projects.

Componentization is not something new. In fact, it was proposed as far back as 1968.1 But the technique has recently exploded in popularity thanks to web frameworks like Angular, React, Vue, Polymer, and native web components. Seems like all the cool kids are doing software components these days. The idea of recursively breaking down the complex bits into simpler bits is called decomposition. And the act of putting the written pieces back together into larger components is called composition.

In the world of Flutter, these components are referred to as widgets. Flutter people like to say “everything is widgets,” meaning that you and I will be using the Google-provided widgets – the ones that ship with Flutter. We’ll compose them together to create our own custom widgets. And our custom widgets will be composed together to create more and more complex custom widgets. This continues until you’ve got yourself a full-blown app.

In the world of Flutter, components are referred to as widgets.

Every app can be thought of in two parts:
  1. 1.

    Behavior – What the software does. All of the business logic goes here: the data reading, writing, and processing.

     
  2. 2.

    Presentation – How the software looks. The user interface. The buttons, textboxes, labels.

     

Only Flutter combines these into one language instead of two.

UI as code

Other development frameworks have proven componentization to be the way to go. The Flutter team has openly stated that they were heavily inspired by React2 which is based on componentization. In fact, all framework makers seem to borrow heavily from one another. But Flutter is unique in the way that the user interface is expressed. Developers use the same Dart language to express an app’s graphical user interface as well as the behavior (Table 3-1). We call this “UI as code.”
Table 3-1

Only Flutter uses the same language for presentation and behavior

Framework

Behavior expressed in ...

UI expressed in ...

Xamarin

C#

XAML

React Native

JavaScript

JSX

NativeScript

JavaScript

XML

Flutter

Dart

Dart

So how does this UI get created? Like many other frameworks and languages, a flutter app starts with a main function. In Flutter, main will call a function called runApp(). This runApp() receives one widget, the root widget which can be named anything, but it should be a class that extends a Flutter StatelessWidget. It looks like this:
// import the Dart package needed for all Flutter apps
import 'package:flutter/material.dart';
// Here is main calling runApp
void main() => runApp(RootWidget());
// And here is your root widget
class RootWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text("Hello world");
  }
}

And that’s all you need to create a “Hello world” in Flutter.

But wait ... what is this Text() thing? It’s a built-in Flutter widget. Since these built-in widgets are so important, we need to take a look at them.

Built-in Flutter widgets

Flutter’s foundational widgets are the building blocks of everything we create and there are tons of them – about 160 at last count.3 This is a lot of widgets for you and I to keep track. But if you mentally organize them, it becomes much more manageable.

They fall into these major categories:
  • Value widgets

  • Layout widgets

  • Navigation widgets

  • Other widgets

Note

These are not Flutter’s official list of categories. Their 14 categories are listed here: https://flutter.dev/docs/development/ui/widgets. We just felt that reorganizing them helps to keep them straight.

We’ll take a brief look at each of these categories with an example or two, and then we’ll do some deep dives in later chapters. Let’s start with value widgets.

Value widgets

Certain widgets hold a value, maybe values that came from local storage, a service on the Internet, or from the user themselves. These are used to display values to the user and to get values from the user into the app. The seminal example is the Text widget which displays a little bit of text. Another is the Image widget which displays a .jpg, .png, or another picture.

Here are some more value widgets:

Checkbox

CircularProgressIndicator

Date & Time Pickers

DataTable

DropdownButton

FlatButton

FloatingActionButton

FlutterLogo

Form

FormField

Icon

IconButton

Image

LinearProgressIndicator

PopupMenuButton

Radio

RaisedButton

RawImage

RefreshIndicator

RichText

Slider

Switch

Text

TextField

Tooltip

We’ll explore value widgets in more detail in the next chapter.

Layout widgets

Layout widgets give us tons of control in making our scene lay out properly – placing widgets side by side or above and beneath, making them scrollable, making them wrap, determining the space around widgets so they don’t feel crowded, and so on:

Align

AppBar

AspectRatio

Baseline

BottomSheet

ButtonBar

Card

Center

Column

ConstrainedBox

Container

CustomMultiChildLayout

Divider

Expanded

ExpansionPanel

FittedBox

Flow

FractionallySizedBox

GridView

IndexedStack

IntrinsicHeight

IntrinsicWidth

LayoutBuilder

LimitedBox

ListBody

ListTile

ListView

MediaQuery

NestedScrollview

OverflowBox

Padding

PageView

Placeholder

Row

Scaffold

Scrollable

Scrollbar

SingleChildScrollView

SizedBox

SizedOverflowBox

SliverAppBar

SnackBar

Stack

Table

Wrap

This is a huge topic which we’ve given its own chapter, Chapter 6, “Laying Out Your Widgets.”

Navigation widgets

When your app has multiple scenes (“screens,” “pages,” whatever you want to call them), you’ll need some way to move between them. That’s where Navigation widgets come in. These will control how your user sees one scene and then moves to the next. Usually this is done when the user taps a button. And sometimes the navigation button is located on a tab bar or in a drawer that slides in from the left side of the screen. Here are some navigation widgets:

AlertDialog

BottomNavigationBar

Drawer

MaterialApp

Navigator

SimpleDialog

TabBar

TabBarView

We’ll learn how they work in Chapter 7, “Navigation and Routing.”

Other widgets

And no, not all widgets fall into these neat categories. Let’s lump the rest into a miscellaneous category. Here are some miscellaneous widgets:

GestureDetector

Dismissible

Cupertino

Theme

Transitions

Transforms

Many of these miscellaneous widgets are covered throughout the book where they fit naturally. GestureDetector is crucial enough that it gets its own chapter, Chapter 5, “Responding to Gestures.”

How to create your own stateless widgets

So we know that we will be composing these built-in widgets to form our own custom widgets which will then be composed with other built-in widgets to eventually form an app.

Widgets are masterfully designed because each widget is easy to understand and therefore easy to maintain. Widgets are abstract from the outside while being logical and predictable on the inside. They are a dream to work with.

Every widget is a class that can have properties and methods. Every widget can have a constructor with zero or more parameters. And most importantly, every widget has a build method which receives a BuildContext4 and returns a single Flutter widget. If you’re ever wondering how a widget got to look the way it does, locate its build method:
class RootWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text('Hello world');
  }
}
In this hello world example which we repeated from earlier in the chapter, we’re displaying a Text widget (Figure 3-2). A single inner widget works but real-world apps will be a whole lot more complex. The root widget could be composed of many other subwidgets:
class FancyHelloWidget extends StatelessWidget {
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("A fancier app"),
        ),
        body: Container(
          alignment: Alignment.center,
          child: Text("Hello world"),
        ),
        floatingActionButton: FloatingActionButton(
          child: Icon(Icons.thumb_up),
          onPressed: () => {},
        ),
      ),
    );
  }
}
../images/482531_1_En_3_Chapter/482531_1_En_3_Fig2_HTML.jpg
Figure 3-2

The app created by this simple widget

So as you can see, the build method is returning a single widget, a MaterialApp, but it contains a Scaffold which contains three subwidgets: an AppBar, a Container, and a FloatingActionButton (Figure 3-3). Each of those in turn contains sub-subwidgets of their own.
../images/482531_1_En_3_Chapter/482531_1_En_3_Fig3_HTML.png
Figure 3-3

The widget tree from our example app above

This is how your build method will always work. It will return a single, massive, nested expression. It is widgets inside widgets inside widgets that enable you to create your own elaborate custom widget.

Widgets have keys

You may hear about a virtual DOM when other developers talk about Flutter. This comes from the world of React. (Remember that Flutter borrowed heavily from React’s excellent architecture.) Well, strictly speaking, Flutter doesn’t have a DOM, but it does maintain something resembling it – the element tree. The element tree is a tiny copy of all the widgets on the screen. Flutter maintains a current element tree and one with batched changes applied.

You see, Flutter might be really slow if it applied every tiny change to the screen and then tried to re-render it hundreds of times per second. Instead, Flutter applies all of those changes to a copy of the element tree. It then periodically “diffs” the current element tree with the modified one and decides what truly needs to be re-rendered. It only re-renders those parts that need it. This is much, much faster.

But occasionally Flutter gets confused when matching the widgets in the element trees. You’ll know to programmatically assign keys if your data changes and widgets get drawn in the wrong location, the data isn’t updated on the screen, or your scroll position isn’t preserved.

You don’t need to worry about keys most of the time. It is needed so rarely that we’re going to be satisfied if you understand that ...
  1. 1.

    Keys exist and why Flutter may need them.

     
  2. 2.

    If your widgets aren’t being redrawn as you might expect when data changes, keys may solve problems.

     
  3. 3.

    You have the opportunity to assign keys to certain widgets.

     

If that’s not enough to satisfy you for now, the great Emily Fortuna has recorded a super ten-minute video on keys.5

Passing a value into your widget

Do you know what this formula means?
y = f(x)
Math majors will recognize this as reading “Y is a function of X.” It concisely communicates that as X (the independent variable) changes, Y (the dependent variable) will change in a predictable way. Flutter lives on this idea, but in Flutter the formula reads like this:
Scene = f(Data)

In other words, as the data in your app changes, the screen will change accordingly. And you, the developer, get to decide how that data is presented as you write a build method in your widgets. It is a foundational concept of Flutter.

Now how might that data change? There’s two ways:
  1. 1.

    The widget can be re-rendered with new data passed from outside.

     
  2. 2.

    Data can be maintained within certain widgets.

     
Let’s talk about the first. To pass data into a widget, you’ll send it in as a constructor parameter like this:
Widget build(BuildContext context) {
  return Person("Sarah"); // Passing "Sarah" into a widget
}
If a widget represents how to render a Person, it would be a very normal thing to pass in a firstName, like we just did with “Sarah” earlier. If you do that, you’ll need to write your widget’s constructor to receive that value:
class Person extends StatelessWidget {
  final String firstName;
  Person(this.firstName) {}
  Widget build(BuildContext context) {
    return Text('$firstName');
  }
}

This is Dart syntax. Note three things. First, you’ll list the input parameter in the constructor (“this.firstName” in the preceding example). Second, make sure you put “this.” in front of it. The “this.” matches it to a class-level property rather than a parameter that is local to the constructor function. And third, mark the corresponding class property as final.

You might want to pass in two or more properties like this:
Widget build(BuildContext context) {
  return Person("Sarah","Ali");
}
Of course passing in two values means creating two final variables and two constructor parameters to handle them:
class Person extends StatelessWidget {
  final String firstName;
  final String lastName;
  Person(this.firstName, this.lastName) {}
  Widget build(BuildContext context) {
    return Text('$firstName $lastName');
  }
}
As you can guess, these are matched positionally which can be easy to mess up and not terribly flexible. A better practice is to have named parameters:
Widget build(BuildContext context) {
  return Person(firstName:"Sarah", lastName:"Ali");
}
This reduces confusion for the other developers who use your widget. Here’s how you’d write your widget to receive that value:
class Person extends StatelessWidget {
  final String firstName;
  final String lastName;
  Person({this.firstName, this.lastName}) {}
  Widget build(BuildContext context) {
    return Container(child: Text('$firstName $lastName'));
  }
}

Do you see the difference? It’s subtle. There are now curly braces around the constructor parameters. This makes them optional and named.

Tip

Note that in all three of the preceding examples, we are using a Person class that might have been defined in the same dart file where you’re using it. But a better practice is to create each class in a separate dart file and import it into other dart files where it is used.

import 'Person.dart';

Stateless and Stateful widgets

So far we’ve been going out of our way to create stateless widgets. So you probably guessed that there’s also a stateful widget. You were right. A stateless widget is one that doesn’t maintain its own state. A stateful widget does.

“State” in this context refers to data within the widget that can change during its lifetime. Think about our Person widget from earlier. If it’s a widget that just displays the person’s information, it should be stateless. But if it is a person maintenance widget where we allow the user to change the data by typing into a TextField, then we’d need a StatefulWidget.

There’s a whole chapter on stateful widgets later. If you just can’t wait to know more about them, you can read Chapter 9, “Managing State,” later in this book. Then come back here.

So which one should I create?

The short answer is create a stateless widget. Never use a stateful widget until you must. Assume all widgets you make will be stateless and start them out that way. Refactor them into stateful widgets when you’re sure you really do need state. But recognize that state can be avoided more often than developers think. Avoid it when you can to make widgets simpler and therefore easier to write, to maintain, and to extend. Your team members will thank you for it.

Note

There is actually a third type of widget, the InheritedWidget. You set a value in your InheritedWidget and any descendent can reach back up through the tree and ask for that data directly. It is kind of an advanced topic, but Rémi Rousselet would have had my head if I hadn’t mentioned it. You can read more about it in Chapter 9, “Managing State,” or watch Emily Fortuna’s concise overview of InheritedWidget here: http://bit.ly/inheritedWidget.

Conclusion

So now we know that Flutter apps are all about widgets. You’ll compose your own custom Stateless or Stateful widgets that have a build method which will render a tree of built-in Flutter widgets. So clearly we need to know about the built-in Flutter widgets which we’ll learn beginning in the next chapter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.253.2