Chapter 4
IN THIS CHAPTER
Looking at functions in a Flutter app
Learning to type
Dealing with variables and other little things
♪ “Hello, hello again, sh-boom and hopin’ we’ll meet again.” ♪
— JAMES KEYES, CLAUDE FEASTER, CARL FEASTER, FLOYD F. MCRAE, AND JAMES EDWARDS, SUNG BY THE CHORDS, THE CREW-CUTS, STAN FREBERG, AND OTHERS, 1954
Chapter 3 is all about a simple Hello world program. For convenience, I copy one version of the code here, in Listing 4-1.
LISTING 4-1 Yet Another Look at the First Hello Program
import 'package:flutter/material.dart';
main() => runApp(App0401());
class App0401 extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: Material(
child: Center(child: Text("Hello world!")),
),
);
}
}
In Chapter 3, I have you concentrate on the middle of the program — the MaterialApp
and all the stuff inside it. I let you gleefully ignore the other parts of the program. In particular, I let you ignore anything having to do with things called “functions.” This chapter continues the tour of a Hello World program and sets its sites on those “function” things.
Here’s an experiment: Run the app whose code is shown in Listing 4-2.
LISTING 4-2 Words, Words, Words
import 'package:flutter/material.dart';
main() => runApp(App0402());
class App0402 extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: Material(
child: Center(child: Text(highlight("Look at me"))),
),
);
}
}
highlight(words) {
return "*** " + words + " ***";
}
Figure 4-1 shows you the output of the app in Listing 4-2.
Listing 4-2 contains a function declaration and a function call. (See Figure 4-2.)
Think about a recipe — a set of instructions for preparing a particular meal. A function declaration is like a recipe: It’s a set of instructions for performing a particular task. In Listing 4-2, this set of instructions says, “Form the string containing asterisks followed by some words followed by more asterisks, and return that string somewhere.”
Most recipes have names, like Macaroni and Cheese or Triple Chocolate Cake. The function at the bottom of Listing 4-2 also has a name: Its name is highlight
. (See Figure 4-3.) There’s nothing special about the name highlight
. I made up the name highlight
all by myself.
A recipe for macaroni and cheese sits in a book or on a web page. The recipe doesn’t do anything. If no one uses the recipe, the recipe lies dormant. The same is true of a function declaration. The declaration in Listing 4-2 doesn’t do anything on its own. The declaration just sits there.
Eventually, somebody might say, “Please make macaroni and cheese for dinner,” and then someone follows the Macaroni and Cheese recipe’s instructions. One way or another, the process begins when someone says (or maybe only thinks) the name of the recipe.
A function call is code that says, “Please execute a particular function declaration’s instructions.” Imagine a phone or another device that’s running the code in Listing 4-2. When the phone encounters the function call highlight("Look at me")
, the phone is diverted from its primary task — the task of constructing an app with its Material
, Center
, and Text
widgets. The phone takes a detour to execute the instructions in the highlight
function’s body. After figuring out that it should create "*** Look at me ***"
, the phone returns to its primary task, adding the Text
widget with "*** Look at me ***"
to the Center
widget, adding the Center
widget to the Material
widget, and so on.
A function call consists of a function’s name (such as the name highlight
in Listing 4-2), followed by some last-minute information (such as "Look at me"
in Listing 4-2).
Wait! In the previous sentence, what does some last-minute information mean? Read on.
Suppose that your recipe for macaroni and cheese serves one person and calls for two ounces of uncooked elbow macaroni. You’ve invited 100 people to your intimate evening gathering. In that case, you need 200 ounces of uncooked elbow macaroni. In a way, the recipe says the following: “To find the number of ounces of uncooked elbow macaroni that you need, multiply the number of servings by 2.” That number of servings is last-minute information. The person who wrote the recipe doesn’t know how many people you’ll be serving. You provide a number of servings when you start preparing the mac-and-cheese. All the recipe says is to multiply that number by 2.
In a similar way, the highlight
function declaration in Listing 4-2 says, “To find the value that this function returns, combine asterisks followed by the words
that you want to be highlighted followed by more asterisks.”
A function declaration is like a black box. You give it some values. The function does something with those values to calculate a new value. Then the function returns that new value. (See Figures 4-4 and 4-5.)
Figures 4-4 and 4-5 show what it means to give values to a function, and for a function to return a value.
You give values to a function with the function’s parameter list.
Like any constructor call, every function call has a parameter list. Each parameter feeds a piece of information for the function to use. In Figure 4-5, the function call highlight("Look at me")
passes the value "Look at me"
to the highlight
function’s declaration. Inside the function declaration, the name words
stands for "Look at me"
, so the expression "*** " + words + " ***"
stands for "*** Look at me ***"
.
You return a value from a function with a return
statement.
In Listing 4-2, the line
return "*** " + words + " ***";
return
statement. Again, imagine a phone that’s running the code in Listing 4-2. With the execution of this return
statement, this is what happens:
highlight
function.Center(child: Text(highlight("Look at me")))
Center(child: Text("*** Look at me ***"))
Center
, Material
, and MaterialApp
widgets.A cookbook may have only one recipe for chicken fricassee, but you can follow the recipe as many times as you want. In the same way, a particular function has only one declaration, but an app may contain many calls to that function. To see this in action, look at Listing 4-2, and change the code’s child
parameter, like so:
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
Text(highlight("Look at me")),
Text(highlight("Your attention, please"))
])
The new child contains two calls to the highlight
function, each with its own parameter value. The resulting app is what you see in Figure 4-6.
“Dart is boring.” That’s what Faisal Abid said during a presentation at DevFest NYC 2017. He wasn’t talking trash about Dart. He was merely explaining that Dart is much like many other programming languages. If you’ve written some programs in Java, C++, or JavaScript, you find Dart’s features to be quite familiar. You encounter a few surprises, but not too many. When you’re learning to create Flutter apps, you don’t want a new, complicated programming language to get in your way. So, a boring language like Dart is just what you need.
This section presents some unexciting facts about the Dart programming language. Try not to fall asleep while you read it.
A statement is a piece of code that commands Dart to do something. If you think this definition is vague, that’s okay for now. Anyway, in Listing 4-2, the line
return "*** " + words + " ***";
is a statement because it commands Dart to return a value from the execution of the highlight
function.
Unlike a statement, a declaration’s primary purpose is to define something. For example, the highlight
function declaration in Listing 4-2 defines what should happen if and when the highlight
function is called.
Statements and declarations aren’t completely separate from one another. In Listing 4-2, the highlight
function declaration contains one statement — a return
statement. A function declaration may contain several statements. For example, the following declaration contains three statements:
highlight2(words) {
print("Wha' da' ya' know!");
print("You've just called the highlight2 function!");
return "*** " + words + " ***";
}
The first two statements (calls to Dart’s print
function) send text to Android Studio’s Run tool window. The third statement (the return
statement) makes highlight("Look at me")
have the value "*** Look at me ***"
.
What does “five” mean? You can have five children, but you can also be five feet tall. With five children, you know exactly how many kids you have. (Unlike the average American family, you can’t have 2.5 kids.) But if you’re five feet tall, you might really be five feet and half an inch tall. Or you might be four feet eleven-and-three-quarter inches tall, and no one would argue about it.
What else can “five” mean? Nuclear power plants can undergo fire-induced vulnerability evaluation, also known as five. In this case, “five” has nothing to do with a number. It’s just f-i-v-e.
A value’s meaning depends on the value’s type. Consider three of the Dart language’s built-in types: int
, double
, and String
.
An int
is a whole number, with no digits to the right of the decimal point.
If you write
int howManyChildren = 5;
in a Dart program, the 5
means “exactly five.”
A double
is a fractional number, with digits to the right of the decimal point.
If you write
double height = 5;
in a Dart program, the 5
means “as close to five as you care to measure.”
A String
is a bunch of characters.
If you use single quotes (or double quotes) and write
String keystroke = '5';
’5’
means “the character that looks like an uppercase letter S but whose upper half has pointy turns.”A value’s type determines what you can do with that value. Consider the values 86
and "86"
.
The first one, 86
, is a number. You can add another number to it.
86 + 1
is 87
The second one, "86"
, is a string. You can’t add a number to it, but you can add another string to it.
"86" + "1"
is "861"
The Dart language has literals and variables. The value of a literal is the same in every Dart program. For example, 1.5
is a literal because 1.5
means “one-and-a-half” in every Dart program. Likewise, "Hello world!"
in Listing 4-1 is a literal because "Hello world!"
stands for the same string of 12 characters in every Dart program. (Yes, the blank space counts as one of the characters.)
The value of a variable is not the same in every Dart program. In fact, the value of a variable may not be the same from one part of a Dart program to another. Take, for example, the following line of code:
int howManyChildren = 5;
This line is called a variable declaration. The line defines a variable named howManyChildren
whose type is int
. The line initializes that variable with the value 5
. When Dart encounters this line, howManyChildren
stands for the number 5.
Later, in the same program, Dart may execute the following line:
howManyChildren = 6;
This line is called an assignment statement. The line makes howManyChildren
refer to 6 instead of 5. Congratulations on the birth of a new child! Is it a girl or a boy?
An expression is a part of a Dart program that stands for a value. Imagine that your code contains the following variable declarations:
int numberOfApples = 7;
int numberOfOranges = 10;
If you start with these two declarations, each entry in the left column of Table 4-1 is an expression.
TABLE 4-1 Fruitful Expressions
Expression |
Value |
Type |
Notes |
|
|
| |
|
|
| |
|
|
|
Even with |
|
|
|
A |
|
|
|
Arithmetic on |
|
|
| |
|
|
| |
|
|
|
Who says you can’t add apples and oranges? |
|
|
| |
|
|
An asterisk ( | |
|
|
|
A slash ( |
|
|
|
The |
|
|
|
This is how you round up or down to the nearest |
|
|
|
When you divide 20 by 7, you get 2 with a remainder of 6. |
|
|
|
Assuming that you’ve declared |
|
|
|
|
In the last row of Table 4-1, do you really need the toString()
part? Yes, you do. If you write ’9’ + numberOfApples
, you get an error message because ’9’
is a String
and numberOfApples
is an int. You can’t add an int
value to a String
value.
You can apply Dart’s toString
to any expression. For some examples, see Chapter 7.
highlight(words) {
print(20 / 7);
print((20 / 7).runtimeType);
return "*** " + words + " ***";
}
When you run the app, the following lines appear in Android Studio’s Run tool window:
flutter: 2.857142857142857
flutter: double
The value of 20 /7
is 2.857142857142857
, and the value of (20 / 7).runtimeType
is double
.
In Dart, some statements do double duty as both statements and expressions. As an experiment, change the highlight
function in Listing 4-2 so that it looks like this:
highlight(words) {
int numberOfKazoos;
print(numberOfKazoos);
print(numberOfKazoos = 94);
return "*** " + words + " ***";
}
Android Studio issues a warning that the numberOfKazoos
variable isn’t used, but that’s okay. This is only an experiment. Here’s what you see in Android Studio’s Run tool window when you run this code:
flutter: null
flutter: 94
The line int numberOfKazoos;
is a variable declaration without an initialization. That’s fair game in the Dart programming language.
When Dart executes print(numberOfKazoos);
you see flutter: null
in the Run tool window. Roughly speaking, null
means “nothing.” At this point in the program, the variable numberOfKazoos
has been declared but hasn’t yet been given a value, so numberOfKazoos
is still null
.
Finally, when Dart executes print(numberOfKazoos = 94);
you see flutter: 94
in the Run tool window. Aha! The code numberOfKazoos = 94
is both a statement and an expression! Here’s why:
numberOfKazoos = 94
makes the value of numberOfKazoos
be 94
.numberOfKazoos = 94
is 94
.Of these two facts, the second is more difficult for people to digest. (I’ve known some experienced programmers who think about this the wrong way.) To execute print(numberOfKazoos = 94);
Dart covertly substitutes 94
for the expression numberOfKazoos = 94
, as shown in Figure 4-7.
In other words, the value numberOfKazoos = 94
is 94
. So, in addition to doing something, the code numberOfKazoos = 94
also has a value. That’s why numberOfKazoos = 94
is both a statement and an expression.
Simple assignment statements aren’t the only things that double as expressions. Try this code out for size:
numberOfKazoos = 100;
print(numberOfKazoos);
print(numberOfKazoos++);
print(numberOfKazoos);
The code’s output is
flutter: 100
flutter: 100
flutter: 101
If the middle line of output surprises you, you’re not alone. As a statement, numberOfKazoos++
adds 1 to the value of numberOfKazoos
, changing the value from 100 to 101. But, as an expression, the value of numberOfKazoos++
is 100, not 101. (Refer to Figure 4-7.)
Here’s a comforting thought. By the time Dart executes the last print(numberOfKazoos)
statement, the value of numberOfKazoos
has already changed to 101. Whew!
Dart has some other statements whose values are expressions. For example, the following code prints flutter: 15
twice:
int howManyGiraffes = 10;
print(howManyGiraffes += 5);
print(howManyGiraffes);
And the following code prints flutter: 5000
twice:
int rabbitCount = 500;
print(rabbitCount *= 10);
print(rabbitCount);
On occasion, you might want to create a variable whose type can change. To do so, declare the variable using Dart’s var
keyword and leave out an initialization in the declaration. For example, the following code won’t work:
int x = 7;
print(x);
x = "Someone's trying to turn me into a String"; // You can’t do this
print(x);
But the following code works just fine:
var x;
x = 7;
print(x);
x = "I've been turned into a String"; // Dart is happy to oblige
print(x);
Another reason for using var
is to avoid long, complicated type names. For an example, see this chapter’s “Build-in types” section.
In a Dart program, every value has a type. Dart has ten built-in types. (See Table 4-2.)
TABLE 4-2 Dart’s Built-In Types
Type Name |
What Literals Look Like |
Useful Info About the Type |
Number types | ||
|
|
Numbers with no digits to the right of the decimal point — typically, from –9007199254740992 to 9007199254740991. |
|
|
Numbers with digits to the right of the decimal point (possibly, all zero digits). |
|
|
A number of some kind. Every |
Collection types | ||
|
<int>[] |
A bunch of values. The initial value is the 0th, the next value is the 1st, the next value is the 2nd, and so on. (With |
|
|
A bunch of values with no duplicates in no particular order. (With |
|
|
A bunch of pairs, each pair consisting of a key (such as |
Other types | ||
|
|
A sequence of characters. |
|
|
A logical value. A variable of this type has one of only two possible values: |
|
|
A string of Unicode characters. For example, |
|
(Not applicable) |
Turns an identifier in a Dart program into a value in a Dart program. (Don’t worry about it!) |
You can combine types to create new types. One way to do this is to put types inside of collection types. For example, in the following declaration, the variable amounts is a List
containing only int
values.
List<int> amounts = [7, 3, 8, 2];
Of course, you can go crazy layering types within types within other types:
Map<String, Map<String, List<int>>> values = {
"Size": {
"Small": [1, 2, 3],
},
};
In cases like that, your best bet is to use the var
keyword. Dart can usually figure things out by looking at the rest of the code.
var values = {
"Size": {
"Small": [1, 2, 3],
},
};
In addition to the types in Table 4-2, every class is a type. For example, in Listing 4-1, App0401
is the name of a type. It’s a type that’s defined in Listing 4-1. You can add a line to Listing 4-1 that makes a variable refer to an instance of the App0401
class. Here’s one such line:
App0401 myApp = App0401();
Like many other variable declarations, this line has a type name (App0401
), followed by a new variable name (myApp
), followed by an initialization. The initialization makes myApp
refer to a newly constructed App0401
instance.
The Dart language comes with a library full of standard, reusable code. The formal name for such a library is an application programming interface (API). Dart’s API has declarations of many classes. For example, instances of Dart’s DateTime
class are moments in time, and instances of the Duration
class are time intervals.
Similarly, the Flutter toolkit comes with a feature-rich API. In Listing 4-1, Widget
, StatelessWidget
, BuildContext
, MaterialApp
, Material
, Center
, and Text
are the names of classes in the Flutter API.
Woe is me! I can’t read the book Flutter For Dummies unless I go to my local library and check out a copy. The same is true of Dart’s and Flutter’s library classes (well, almost). You can’t use Flutter’s MaterialApp
or Material
classes unless you start your program with
import 'package:flutter/material.dart';
If you delete this line, you can’t even use any of Flutter’s Widget
classes (StatelessWidget
, Widget
, Center
, and Text
, to name a few). That’s because, when you import ’package:flutter/material.dart’
, you automatically import ’package:flutter/widgets.dart’
also.
A relatively small number of Dart’s API classes, like the aforementioned DateTime
class, belong to a package named dart.core
. You can start your program with the line
import 'dart:core';
but it won’t do you any good. Classes from the dart.core
package are always imported, whether you ask for it or not.
This section shows some alternative ways of creating function declarations. Listing 4-3 has the first example.
LISTING 4-3 Messing with Function Declarations
import 'package:flutter/material.dart';
main() {
runApp(App0403());
}
class App0403 extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: Material(
child: Center(child: Text(highlight("Look at me"))),
),
);
}
}
highlight(words) => "*** $words ***";
A run of the code in Listing 4-3 is the same as that of Listing 4-2. (Refer to Figure 4-1.) In a sense, Listing 4-3 contains the same program as Listing 4-2. The notation for things is slightly different, but the things themselves are the same.
In Listing 4-3, the highlight
function declaration
highlight(words) => "*** $words ***";
is shorthand for the more long-winded highlight
declaration in Listing 4-2. When the body of a function declaration contains only one statement, you can use this quick-and-easy fat arrow (=>
) notation.
Back in Listing 4-2, I use the fat arrow notation to declare the main
function. Just to show that I can do it, I “un-fat-arrow” this declaration in Listing 4-3.
Every Dart program has a function named main
. When you start running a program, Dart looks for the program’s main
function declaration and starts executing whatever statements are in the declaration’s body. In a Flutter app, a statement like
runApp(App0403());
tells Dart to construct an instance of App0403
and then run that instance. The runApp
function is part of Flutter’s API.
Listing 4-4 adds some type names to the code from Listing 4-2.
LISTING 4-4 Better Safe than Sorry
import 'package:flutter/material.dart';
void main() => runApp(App0404());
class App0404 extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: Material(
child: Center(child: Text(highlight("Look at me"))),
),
);
}
}
String highlight(String words) {
return "*** $words ***";
}
In Listing 4-4, String
and void
add some welcome redundancy to the code. The occurrence of String
in (String words)
tells Dart that, in any call to the highlight
function, the words
parameter must have type String
. Armed with this extra String
information, Dart will cough up and spit out a bad function call such as
highlight(19)
This is bad because 19
is a number, not a String
. You may argue and say, “I’ll never make the mistake of putting a number in a call to the highlight
function.” And my response is, “Yes you will, and so will I, and so will every other programmer on earth.” When you’re writing code, mistakes are inevitable. The trick is to catch them sooner rather than later.
Near the end of Listing 4-4, String highlight
tells Dart that the value returned by the highlight
function must be a String
. If you accidentally write the following code, Dart will complain like nobody’s business:
String highlight(String words) {
return 99; //Bad code!
}
Sorry, chief. The value 99
isn’t a String
.
Continuing our journey through Listing 4-4, void main
doesn’t quite mean, “The main
function must return a value of type void
.” Why not? It’s okay to put a type name in front of a fat arrow declaration. So, what’s different about void main
?
Simply stated, void
isn’t a type. In a way, void
means “no type.” The word void
reminds Dart that this main
function isn’t supposed to return anything useful. Try declaring void main
and putting a return
statement in the declaration’s body:
void main() {
runApp(App0404());
return 0; // Bad
}
If you do this, Android Studio’s editor adds red marks to your code. Dart is saying, “Sorry, Bud. You can’t do that.”
Chapter 3 distinguishes between constructors’ positional parameters and named parameters. All that fuss about the kinds of parameters applies to functions as well. For example, the highlight function in Listing 4-4 has one parameter — a positional parameter.
highlight("Look at me") // A function call
String highlight(String words) { // The function declaration
return "*** $words ***";
}
If you want, you can turn words
into a named parameter. Simply surround the parameter with curly braces:
highlight(words: "Look at me") // A function call
String highlight({String words}) { // The function declaration
return "*** " + words + " ***";
}
You can even have a function with both positional and named parameters. In the parameter list, all the positional parameters must come before any of the named parameters. For example, the following code displays +++Look at me!+++
.
highlight( // A function call
"Look at me",
punctuation: "!",
symbols: "+++",
)
String highlight( // The function declaration
String words, {
String punctuation,
String symbols,
}) {
return symbols + words + punctuation + symbols;
}
Listing 4-4 contains some familiar-looking code:
class App0404 extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
Here are some facts:
build
is the name of a function, and
Widget build(BuildContext context)
is the function declaration’s header.
The build
function does exactly what its name suggests. It builds something. To be precise, it builds the widget whose content is the entire Flutter app.
The build
function returns a value of type Widget
.
Quoting from Chapter 3, “Being an instance of one class might make you automatically be an instance of a bigger class.” In fact, every instance of the MaterialApp
class is automatically an instance of the StatefulWidget
class, which, in turn, is automatically an instance of the Widget
class. So there you have it — every MaterialApp
is a Widget
. That’s why it’s okay for the build
function’s return
statement to return a MaterialApp
object.
The function’s one-and-only parameter has the type BuildContext
.
When Dart builds a widget, Dart creates a BuildContext
object and passes that to the widget’s build
function. A BuildContext
object contains information about the widget and the widget’s relationship to other widgets in the program. For more info, see Chapter 6.
In Listing 4-4, the build
function’s declaration is inside the class App0404
definition, but the highlight
function declaration isn’t inside any class definition. In a sense, this build
function “belongs to” instances of the App0404
class.
A function that belongs to a class, or to the class’s instances, has a special name. It’s called a method. More on this in Chapter 5.
What happens if a user taps the screen and wants a response from the app in Listing 4-4? Absolutely nothing.
Let’s fix that. Turn the page to see what’s in Chapter 5.
♪ “Goodbye from us to you.” ♪
— BUFFALO BOB ON “THE HOWDY DOODY SHOW,” 1947–1960
18.188.66.13