Chapter 7
Interacting with the User
IN THIS CHAPTER
Collecting responses from the user
Responding to input
Dealing with null values
Advice on love and marriage
Love is in the air! The sun is shining. The birds are singing. My heart is all a-Flutter. (Pun intended.)
Doris D. Developer wants to find a mate, and she has two important criteria. First, she wants someone who’s 18 or older. Second, she’s looking for someone who loves developing Flutter apps. What better way for Doris to achieve her goal than for her to write her own dating app?
This chapter covers Doris’s outstanding work. To create the app, Doris uses several kinds of widgets: a text field, a slider, a dropdown button, and some others. A widget of this kind — one that the user sees and interacts with — is called a control element, or simply a control.
Doris’s app also has some layout widgets, such as Center
, Row
, and Column
, but these layout widgets aren’t called controls. The user doesn’t really see them, and certainly doesn’t interact with them. This chapter’s emphasis is on the controls, not on the layout widgets or the app’s other assorted parts.
Doris’s final dating app isn’t full-featured by commercial standards, but the code for the app is a few hundred lines long. That’s why Doris develops the app in small pieces — first one control, and then another, and another, and so on. Each piece is a small, free-standing practice app.
The first practice app deals with a simple question: Is the prospective mate at least 18 years old?
A Simple Switch
A Switch
is a control that’s in one of two possible states: on or off, yes or no, true or false, happy or sad, over 18 or not. Listing 7-1 has the code for the practice Switch
app.
LISTING 7-1 How Old Are You?
import 'package:flutter/material.dart';
void main() => runApp(App0701());
class App0701 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
const _youAre = ’You are’;
const _compatible = ’compatible with\nDoris D. Developer.’;
class _MyHomePageState extends State<MyHomePage> {
bool _ageSwitchValue = false;
String _messageToUser = "$_youAre NOT $_compatible";
/// State
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Are you compatible with Doris?"),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
_buildAgeSwitch(),
_buildResultArea(),
],
),
),
);
}
/// Build
Widget _buildAgeSwitch() {
return Row(
children: <Widget>[
Text("Are you 18 or older?"),
Switch(
value: _ageSwitchValue,
onChanged: _updateAgeSwitch,
),
],
);
}
Widget _buildResultArea() {
return Text(_messageToUser, textAlign: TextAlign.center);
}
/// Actions
void _updateAgeSwitch(bool newValue) {
setState(() {
_ageSwitchValue = newValue;
_messageToUser =
_youAre + (_ageSwitchValue ? " " : " NOT ") + _compatible;
});
}
}
Figures 7-1 and 7-2 show the app in its two possible states.

FIGURE 7-1: The user turns on the switch.

FIGURE 7-2: The user turns off the switch again.
The code in Listing 7-1 isn’t much different from the code in Chapter 5. In Chapter 5, the floating action button has an onPressed
parameter. In Listing 7-1, the Switch
widget has something similar. Listing 7-1 has an onChanged
parameter. The onChanged
parameter’s value is a function; namely, the _updateAgeSwitch
function. When the user flips the switch, that flip triggers the switch’s onChanged
event, causing the Flutter framework to call the _updateAgeSwitch
function.
Unlike the event handling functions in Chapter 5, the _updateAgeSwitch
function in Listing 7-1 isn’t a VoidCallback
. A VoidCallback
function takes no parameters, but the _updateAgeSwitch
function has a parameter. The parameter’s name is newValue
:
void _updateAgeSwitch(bool newValue)
When the Flutter framework calls _updateAgeSwitch
, the framework passes the Switch
widget’s new position (off or on) to the newValue
parameter. Because the type of newValue
is bool
, newValue
is either false
or true
. It’s false
when the switch is off and true
when the switch is on.
If _updateAgeSwitch
isn’t a VoidCallback
, what is it? (That was a rhetorical question, so I answer it for you… .) The _updateAgeSwitch
function is of type ValueChanged<bool>
. A ValueChanged
function takes one parameter and returns void
. The function’s parameter can be of any type, but a ValueChanged<bool>
function’s parameter must be of type bool
. In the same way, a ValueChanged<double>
function’s parameter must be of type double
. And so on.
void _updateAgeSwitch(bool newValue) {
// setState(() {
_ageSwitchValue = newValue;
_messageToUser = _youAre + (_ageSwitchValue ? " " : " NOT ") + _compatible;
// });
}
Then I uncommented the setState
call and commented out the assignment statements:
void _updateAgeSwitch(bool newValue) {
setState(() {
// _ageSwitchValue = newValue;
// _messageToUser =
// _youAre + (_ageSwitchValue ? " " : " NOT ") + _compatible;
});
}
In both cases, I restarted the program and then tapped on the switch. Not only did the _messageToUser
refuse to change, but the switch didn’t even budge. That settles it! The look of the switch is completely dependent on the _ageSwitchValue
variable and the call to setState
. If you don’t assign anything to _ageSwitchValue
or you don’t call setState
, the switch is completely unresponsive.
Dart’s const keyword
Here’s my cardinal rule: Once I’ve made a decision, I never change my mind. The only exception to this rule is when I change my mind about the cardinal rule.
In app development, the issue of change is very important. The term variable comes from the word vary, which means “change.” But some things shouldn’t change. In Listing 7-1, I refer to the strings ’You are’
and ’compatible with\nDoris D. Developer’
more than once, so I create handy names _youAre
and _compatiblc
for these strings. That way, I don’t have to type things like ’compatible with\nDoris D. Developer’
more than once. I don’t risk typing the phrase correctly one time and incorrectly another time.
But what if the value of _youAre
is allowed to change throughout the run of the program? A developer who’s working with my code might mistakenly write
_youAre = 'sweet';
I don’t want that to happen. I want _youAre
to stand for ’You are’
throughout the run of the program. Android Studio should flag the assignment _youAre = ’sweet’
as an error. That’s why, in Listing 7-1, I declare _youAre
with the word const
. Dart’s const
keyword is short for constant. As a constant, the value of _youAre
cannot change. The same holds true for the declaration of _compatible
in Listing 7-1. The use of Dart’s const
keyword is a safety measure, and it’s a darn good one!
// Don’t do this:
class _MyHomePageState extends State<MyHomePage> {
const _youAre = 'You are';
But this code is just fine:
Prod: Note the bold code.-BB
// Do this instead:
class _MyHomePageState extends State<MyHomePage> {
static const _youAre = 'You are';
Compatible or NOT?
For some users, the Dating app should say, “You are compatible with Doris D. Developer.” For other users, the app should add NOT to its message. That’s why Listing 7-1 contains the following code:
_messageToUser =
_youAre + (_ageSwitchValue ? " " : " NOT ") + _compatible;
The expression _ageSwitchValue ? " " : " NOT "
is a conditional expression, and the combination of ?
and :
in that expression is Dart’s conditional operator. Figure 7-3 shows you how Dart evaluates a conditional expression.

FIGURE 7-3: Evaluating a conditional expression.
A conditional expression looks like this:
condition ? expression1 : expression2
When the condition
is true
, the value of the whole expression is whatever you find in the expression1
part. But, when the condition
is false
, the value of the whole expression is whatever you find in the expression2
part.
In addition to its conditional expressions, Dart has if
statements. A conditional expression is like an if
statement but, unlike an if
statement, a conditional expression has a value. That value can be assigned to a variable.
To illustrate the point, I give you an if
statement whose effect is the same as the conditional expression in Listing 7-1:
if (_ageSwitchValue) {
_messageToUser = _youAre + " " + _compatible;
} else {
_messageToUser = _youAre + " NOT " + _compatible;
}
Translated into plain English, this if
statement says:
If the bool variable _ageSwitchValue has the value true,
_messageToUser = _youAre + " " + _compatible;
otherwise
_messageToUser = _youAre + " NOT " + _compatible;
In some situations, choosing between an if
statement and a conditional expression is a matter of taste. But in Listing 7-1, the conditional expression is a clear winner. After all, an if
statement doesn’t have a value. You can’t assign an if
statement to anything or add an if
statement to anything. So, code of the following kind is illegal:
// THIS CODE IS INVALID.
_messageToUser =
_youAre +
if (_ageSwitchValue) {
" ";
} else {
" NOT ";
} +
_compatible;
Wait For It!
Today’s users are impatient. They want instant feedback. How do I know this? A bunch of actors convinced me. Here’s the story:
A long time ago, in a theater far, far from Broadway, I saw a musical comedy in the company of nine other people. Three of them were my friends, two of them were watching from third-row seats, and the other four were the play’s performers. I remember this event for two reasons. First, it was the beginning of my lifelong philosophy about making performers feel good. The play’s jokes weren’t funny, but I laughed out loud at every one of them. The singing was out of tune, but I clapped vigorously after each song. Eventually, we were all having a good time, and the actors didn’t regret playing to a six-person audience.
The other memorable part of this event was the play’s signature song. It was kind of silly, but it stuck in my mind for years and years. To this day, my wife and I chuckle whenever we howl out the song’s first few measures. The title of this tune was “Immediate Gratification.” It was a mockery of modern culture, in which every need is urgent and every desire must be fulfilled.
In following this line of thought, I present a humble widget known as the RaisedButton
. A button isn’t much. You press it, and something happens. You press it again, and something may or may not happen. Buttons used to be the go-to control for web developers and app developers. But these days, buttons are passé. When a user flips a switch, the app responds immediately. There’s no waiting around to find a button to press. The old light-grey rectangle with the word Submit on it has taken a back seat.
In celebration of the good old days, this section’s example shuns the quick response of the app in Listing 7-1. When the user flicks a switch, the switch simply moves. The app doesn’t say, “You’re compatible” or “You’re not compatible” until the user presses a button. Come sit on the porch and relax while this app runs! The code is in Listing 7-2.
LISTING 7-2 Responding to a Button Press
// Copy the code up to and including the _buildAgeSwitch
// method from Listing 7-1 here.
Widget _buildResultArea() {
return Row(
children: <Widget>[
RaisedButton(
child: Text("Submit"),
onPressed: _updateResults,
),
SizedBox(
width: 15.0,
),
Text(_messageToUser, textAlign: TextAlign.center),
],
);
}
/// Actions
void _updateAgeSwitch(bool newValue) {
setState(() {
_ageSwitchValue = newValue;
});
}
void _updateResults() {
setState(() {
_messageToUser = ’You are’ +
(_ageSwitchValue ? " " : " NOT ") +
’compatible with \nDoris D. Developer.’;
});
}
}
Figure 7-4 shows a snapshot from a run of the code in Listing 7-2.

FIGURE 7-4: Good news!
When it’s combined with some code from Listing 7-1, the app in Listing 7-2 has both onPressed
and onChanged
event handlers. In particular:
-
The function
_updateAgeSwitch
handlesonChanged
events for the switch.When the user taps the switch, the appearance of the switch changes from off to on or from on to off.
-
The function
_updateResults
handlesonPressed
events for the button.When the user presses the button, the app’s message catches up with the switch’s status. If the switch is on, the message becomes, “You are compatible.” If the switch is off, the message becomes “You are NOT compatible.”
Between the moment when the user flicks the switch and the time when the user presses the button, the message on the screen might be inconsistent with the switch’s state. In an online form with several questions, that’s not a problem. The user doesn’t expect to see a result until after the concluding button press. But in this chapter’s practice apps, each with only one question for the user, the lack of coordination between the user’s answer and the message that’s displayed is problematic. These practice apps don’t win any user experience awards.
Fortunately, Doris doesn’t publish her practice apps. Instead, she publishes an app that combines all the controls from her practice apps and more.
So, what’s next? I know! How about a slider?
How Much Do You Love Flutter?
Doris the Developer wants to meet someone who loves to create Flutter apps. Her homemade dating app includes a slider with values from 1 to 10. Scores of 8 and above are acceptable. Anyone with a response of 7 or below can take a hike.
Listing 7-3 has the highlights of Doris’s practice slider app.
LISTING 7-3 For the Love of Flutter
// This is not a complete program. (No way!)
class _MyHomePageState extends State<MyHomePage> {
double _loveFlutterSliderValue = 1.0;
Widget _buildLoveFlutterSlider() {
return // …
Text("On a scale of 1 to 10, "
"how much do you love developing Flutter apps?"),
Slider(
min: 1.0,
max: 10.0,
divisions: 9,
value: _loveFlutterSliderValue,
onChanged: _updateLoveFlutterSlider,
label: ’${_loveFlutterSliderValue.toInt()}’,
),
}
void _updateLoveFlutterSlider(double newValue) {
setState(() {
_loveFlutterSliderValue = newValue;
});
}
void _updateResults() {
setState(() {
_messageToUser = _youAre +
(_loveFlutterSliderValue >= 8 ? " " : " NOT ") +
_compatible;
});
}
}
Figure 7-5 shows a run of the slider app with the slider set all the way to 10. (How else do you expect me to set the “love Flutter” slider?)

FIGURE 7-5: Love at first byte.
The Slider
constructor call in Listing 7-3 has these six parameters:
-
min
: The slider’s smallest value.The little gizmo that moves from left to right along a slider is called a thumb. The position of the thumb determines the slider’s value. So
min
is the value of the slider when the slider’s thumb is at the leftmost point. Themin
parameter has typedouble
. -
max
: The slider’s largest value.This is the value of the slider (again, a
double
) when the thumb is at the rightmost point.A slider’s values may increase going from left to right or from right to left. Before displaying a slider, Flutter checks a
textDirection
property. If the value isTextDirection.ltr
, the slider’s minimum value is on the left. But if thetextDirection
property’s value isTextDirection.rtl
, the slider’s minimum value is on the right. Apps written for speakers of Arabic, Farsi, Hebrew, Pashto, and Urdu useTextDirection.rtl
. Other apps useTextDirection.ltr
. In case you’re wondering, Flutter doesn’t support boustrophedon writing — an ancient style in which alternate lines flow from left to right and then from right to left. -
divisions
: The number of spaces between points where the thumb can be placed. (See Figure 7-6.)FIGURE 7-6: Why the number of divisions is 9 in Listing 7-3.
The slider in Listing 7-3 can be placed at values 1.0, 2.0, 3.0, and so on, up to 10.0.
If you omit the
divisions
parameter, or if you set that parameter tonull
, the thumb can be placed anywhere along the slider. For example, with the following constructor, the slider’s value can be 0.0, 0.20571428571428554, 0.917142857142857, 1.0, or almost any other number between 0 and 1.Slider(
min: 0.0,
max: 1.0,
value: _loveFlutterSliderValue,
onChanged: _updateLoveFlutterSlider,
) -
value
: A number in the range frommin
tomax
.This parameter determines the thumb’s position.
-
onChanged
: The event handling function for changes to the slider.When the user moves the slider’s thumb, the Flutter framework calls this function.
-
label
: The widget that’s displayed on the slider’s value indicator.As the user moves the thumb, an additional shape appears. That shape is the slider’s value indicator. In Figure 7-5, the bubble with the number 10 on it is the slider’s value indicator.
Despite its name, the value indicator doesn’t necessarily display a
Text
widget showing the slider’s value. In fact, the value indicator can display anything you want it to display. (Well, almost anything.)Luckily for us, the widget on the slider in Listing 7-3 displays
_loveFlutterSliderValue
— the slider’s very own value. But remember: If you don’t want numbers like 0.20571428571428554 to appear in the value indicator, you have to convert the slider’sdouble
values intoint
values. That’s why, in Listing 7-3, the widget on the slider’s value indicator displays_loveFlutterSliderValue.toInt()
, not plain old_loveFlutterSliderValue
.If you don’t specify a
label
parameter, or if you specify alabel
but make itnull
, the value indicator never appears.
Dealing with Text Fields
In this section, I introduce Doris’s friend Irving. Unlike Doris, Irving wants a companion with lots of money. To this end, Irving asks Doris to create a variation on her dating app. Irving’s custom-made app has two text fields — one for a user’s name and another for the user’s income. If the user’s income is $100,000 or more, the app reports “compatible.” Otherwise, the app reports “incompatible.” Figure 7-7 has an illustrated version of the app’s _MyHomePageState
class. To see the rest of Irving’s app, look for the App0704 project in the download from this book’s website.

FIGURE 7-7: (Also known as Listing 7-4.) How much do you earn?
InputDecoration _buildDecoration(String label) {
return InputDecoration(
labelText: label,
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
),
);
}
Figure 7-8 shows Pat’s pathetic attempt to be deemed compatible with Irving. With an income of $61,937, Pat doesn’t have a chance. (In 2018, the median income for households in the United States was $61,937. Irving’s sights are set too high.)

FIGURE 7-8: Bad news for Pat.
Text fields have the same kinds of event handlers that switches and sliders have. In particular, a TextField
constructor can have an onChanged
event handler — a function that looks like this:
void _updateStuff(String newValue) {
// When the user types a character, do something with
// the characters inside the text field (the newValue).
}
But what about the press of a button? Is there a nice way to find out what’s in a text field when the field’s characters aren’t changing? Yes, there is. It’s the TextEditingController
— a stand-out feature in Figure 7-7.
In fact, Figure 7-7 has two TextEditingController
objects — one for the Your Name field and another for the Your Income field. The next several paragraphs add details to the numbered callouts in Figure 7-7.
Callouts 1 and 2
In a Flutter program, constructor calls rule the roost. You get a Text
widget with a constructor call like Text("Hello")
. You get a Column
and two Text
widgets with code like Column(children: [Text(’a’), Text(’b’)])
.
When you issue a constructor call, the call itself stands for an object. For example, the call Text("Hello")
stands for a particular Text
widget — an instance of the Text
class. You can assign the call to a variable and use that variable elsewhere in your code:
@override
Widget build(BuildContext context) {
Text myTextInstance = Text("I’m reusable");
return Scaffold(
appBar: AppBar(
title: myTextInstance,
),
body: Column(
children: <Widget>[
myTextInstance,
],
),
);
}
In many cases, you can separate the variable declaration from the call:
Text myTextInstance;
// More code here, and elsewhere …
myTextInstance = Text("I'm reusable");
In Figure 7-7, the declaration of the two controller variables (_nameFieldController
and _incomeFieldController
) is separate from the corresponding TextEditingController
constructor calls. I do this in order to introduce Flutter’s initState
and dispose
methods.
A State
object is like anything else in the world — it comes into being and, eventually, it goes away. Flutter calls initState
when a State
instance comes into being, and calls dispose
when the State
instance goes away.
It may not be obvious, but the code in Figure 7-7 refers to two different initState
methods. The declaration that begins with void initState()
describes a method that belongs to the _MyHomePageState
class. But the _MyHomePageState
class extends Flutter’s own State
class, and that State
class has its own initState
declaration. (See Figure 7-9.)

FIGURE 7-9: Overriding an extended class’s initState method.
When you have two methods named initState
, how do you distinguish one from another? Well, what if you meet a woman named Mary, whose child is also named Mary? Chances are, the child doesn’t call her mother “Mary.” Instead, the child calls her mother “Mom” or something like that. For her mother’s birthday, she buys a souvenir mug displaying the words Super Mom, and her mother smiles politely on receiving another useless gift.
The same kind of thing happens when two classes — a parent and its child — have methods named initState
. The child class (_MyHomePageState
) has to call the initState
method belonging to its parent class (Flutter’s State
class). To do so, the child class calls super.initState()
. Unlike the Mary situation, the use of the keyword super
isn’t meant to be flattering. It’s simply a reference to the initState
method that’s defined in the parent class. (I can’t resist: The keyword super
may not be flattering, but it’s certainly Fluttering.)
To stretch the mother/daughter metaphor a bit further, imagine that Super Mom Mary is a real estate agent. In that case, the child can’t buy a house without first consulting her mother. The child’s decideWhichHouse
method must include a call to the mother’s decideWhichHouse
method, like so:
// The child’s method declaration:
@override
void decideWhichHouse() {
super.decideWhichHouse();
// Etc.
}
That may be the situation when your code overrides Flutter’s initState
method. In some versions of Flutter, if you don’t call super.initState()
, your code won’t run.
Callout 3
Each TextField
constructor can have its own controller
parameter. A text field’s controller mediates the flow of information between the text field and other parts of the app. (For details, jump to the later section “Callout 4.”)
Elsewhere in the TextField
constructor call, the TextInputType.number
parameter in the income text field’s constructor tells a device to display a soft keyboard with only digits on the keys. Alternatives include TextInputType.phone
, TextInputType.emailAdress
, TextInputType.datetime
, and others. For an authoritative list of TextInputType
choices, visit https://api.flutter.dev/flutter/services/TextInputType-class.html
.
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
final currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus) {
currentFocus.unfocus();
}
},
child: Scaffold(
// … Etc.
Callout 4
In Figure 7-7, the expression _nameFieldController.text
stands for the characters that appear in the Name text field, and _incomeFieldController.text
stands for the characters in the Income text field. If the code included the statement
_nameFieldController.text = "May I. Havanother";
execution of that statement would change whatever was already in the Name text field to May I. Havanother.
In Figure 7-7, the expression _nameFieldController.text
adds the user’s name to the outgoing message. The expression _incomeFieldController.text
stands for whatever characters the user has entered in the app’s Income field, but those characters come with a slight catch. The stuff in a text field is always a String
value, never a numeric value. In Figure 7-8, Pat enters 61937 in the Income text field, so the value of _incomeFieldController.text
is "61937"
(the String
), not 61937 (the number).
Luckily, Dart’s int
class has a parse
method. If the value of _incomeFieldController.text
is "61937"
(the String
), the value of int.parse(_incomeFieldController.text)
is 61937
(the int
number value). In Figure 7-7, the code
int.parse(_incomeFieldController.text) >= 1000000
compares a number like 61937
to Irving’s high-demand number of 1000000
. The result of the comparison is either true
or false
, so the value of _richUser
becomes either true
or false
.
Callout 5
Much of the fuss in earlier paragraphs about the initState
method applies equally to Flutter’s dispose
method. Before a State
class gets the heave-ho, the Flutter framework calls the code’s dispose
method. In Figure 7-7, the dispose
method does these three things:
-
It calls the
dispose
method belonging to the_nameFieldController
.The
dispose
method for_nameFieldController
trashes that controller, freeing up any resources that the controller happens to be hogging. -
It calls the
dispose
method belonging to the_incomeFieldController
.Goodbye,
_incomeFieldController
. Glad to see that your resources are being freed up. -
It calls the
State
class’sdispose
method.The
State
class’sdispose
method, built solidly into the Flutter framework, cleans up any other stuff having to do with_MyHomePageState
. As it is withinitState
, your own code’sdispose
method must callsuper.dispose()
.
Creating Radio Buttons
Every dating app has a question about the user’s gender. For this question, Doris decides on a group of radio buttons. Listing 7-5 has much of Doris’s radio button code.
LISTING 7-5 How Do You Identify?
// This is not a complete program.
// Some trees have been saved.
// The trees are happy about that.
enum Gender { Female, Male, Other }
String shorten(Gender gender) => gender.toString().replaceAll("Gender.", "");
class _MyHomePageState extends State<MyHomePage> {
String _messageToUser = "";
Gender _genderRadioValue;
// And later …
Widget _buildGenderRadio() {
return Row(
children: <Widget>[
Text(shorten(Gender.Female)),
Radio(
value: Gender.Female,
groupValue: _genderRadioValue,
onChanged: _updateGenderRadio,
),
SizedBox(width: 25.0),
Text(shorten(Gender.Male)),
Radio(
value: Gender.Male,
groupValue: _genderRadioValue,
onChanged: _updateGenderRadio,
),
SizedBox(width: 25.0),
Text(shorten(Gender.Other)),
Radio(
value: Gender.Other,
groupValue: _genderRadioValue,
onChanged: _updateGenderRadio,
),
],
);
}
Widget _buildResultArea() {
return Row(
children: <Widget>[
RaisedButton(
child: Text("Submit"),
onPressed: _genderRadioValue != null ? _updateResults : null,
),
SizedBox(
width: 15.0,
),
Text(
_messageToUser,
textAlign: TextAlign.center,
),
],
);
}
/// Actions
void _updateGenderRadio(Gender newValue) {
setState(() {
_genderRadioValue = newValue;
});
}
void _updateResults() {
setState(() {
_messageToUser =
"You selected ${shorten(_genderRadioValue)}.";
});
}
}
Figures 7-10 and 7-11 show snapshots from a run of the code in Listing 7-5.

FIGURE 7-10: Before selecting a radio button.

FIGURE 7-11: After selecting a radio button and pressing Submit.
Creating an enum
Chapter 3 introduces Flutter’s built-in Brightness
enum with its values Brightness.light
and Brightness.dark
. That’s nice, but why let the creators of Flutter have all the fun? You can define your own enum by doing what you see in Listing 7-5.
enum Gender { Female, Male, Other }
With this declaration, your code has three new values; namely, Gender.Female
, Gender.Male
, and Gender.Other
. You can use these values in the rest of the app’s code.
Building the radio group
The code in Listing 7-5 has three radio buttons. Each radio button has its own value
but, taken together, all three buttons have only one groupValue
. In fact, the common groupValue
is what ties the three buttons together. When a user selects the button with value Gender.Female
, the groupValue
of all three becomes Gender.Female
. It’s as if part of the code suddenly looked like this:
// Don’t try this at home. This is fake code.
Radio(
value: Gender.Female,
groupValue: Gender.Female,
),
Radio(
value: Gender.Male,
groupValue: Gender.Female,
),
Radio(
value: Gender.Other,
groupValue: Gender.Female,
),
Each radio button has its own onChanged
parameter. In Listing 7-5, the function that handles onChanged
events (the _updateGenderRadio
function) does exactly what you would expect — it changes the radio buttons’ groupValue
to whatever value the user has selected.
Displaying the user’s choice
The shorten
method in Listing 7-5 is a workaround for a slightly annoying Dart language feature. In Dart, every enum value has a toString
method which, in theory, gives you a useful way to display the value. The problem is that, when you apply the toString
method, the result is always a verbose name. For example, Gender.Female.toString()
is "Gender.Female"
, and that’s not quite what you want to display. In Figure 7-10, the user sees the sentence You selected Female instead of the overly technical sentence You selected Gender.Female sentence.
Applying the replaceAll("Gender.", "")
method call turns "Gender."
into the empty string, so "Gender.Female"
becomes plain old "Female"
. Problem solved! — or maybe not.
Look at the declaration of _genderRadioValue
in Listing 7-5:
Gender _genderRadioValue;
This declaration doesn’t assign anything to _genderRadioValue
, so _genderRadioValue
starts off being null
. That’s good because having null
for _genderRadioValue
means that none of the radio group’s buttons is checked. That’s exactly what you want when the app starts running.
But what if the user presses Submit without selecting one of the radio buttons? Then _genderRadioValue
is still null
, so the shorten
method’s gender
parameter is null
. Inside the shorten
method, you have the following nasty situation:
String shorten(Gender null) => null.toString().replaceAll("Gender.", "");
Oops! When you apply toString()
to null
, you get "null"
(the string consisting of four characters). If you don’t do anything about that, the message on the user’s screen becomes You selected null. That’s not user-friendly!
In Listing 7-5, the Submit button’s onPressed
handler responds appropriately whenever _genderRadioValue
is null
. Here’s the code:
RaisedButton(
child: Text("Submit"),
onPressed: _genderRadioValue != null ? _updateResults : null,
),
If _genderRadioValue
isn’t null
, the handler is a nice, conventional _updateResults
method — a method that creates a _messageToUser
and displays that message inside a Text
widget. Nothing special there.
But when _genderRadioValue
is null
, the Submit button’s onPressed
handler is also null
. And the good news is, a RaisedButton
with a null
handler is completely disabled. The user can see the button, but the button’s surface is greyed out, and pressing the button has no effect. That’s great! If no gender is selected and the user tries to press the Submit button, nothing happens and no message appears.
Creating a Dropdown Button
As soon as word gets around about Doris’s dating app, everyone wants a piece of the action. Doris’s friend Hilda wants a dropdown button to gauge the potential mate’s level of commitment. Hilda wants a committed relationship and possibly marriage. Listing 7-6 shows some of the code that Doris writes for Hilda. Figures 7-12, 7-13, and 7-14 show the code in action.*

FIGURE 7-12: The user hasn’t decided yet.
* Thanks to David Nesterov-Rappoport, for creating the Heart and BrokenHeart images shown in Figures 7-13 and 7-14.

FIGURE 7-13: A user with cold feet.

FIGURE 7-14: A serious user.
LISTING 7-6 What Are You Looking For?
// This listing is missing some parts. See App0705 in the files that
// you download from this book's website (www.allmycode.com/Flutter).
enum Relationship {
Friend,
OneDate,
Ongoing,
Committed,
Marriage,
}
Map<Relationship, String> show = {
Relationship.Friend: "Friend",
Relationship.OneDate: "One date",
Relationship.Ongoing: "Ongoing relationship",
Relationship.Committed: "Committed relationship",
Relationship.Marriage: "Marriage",
};
List<DropdownMenuItem<Relationship>> _relationshipsList = [
DropdownMenuItem(
value: Relationship.Friend,
child: Text(show[Relationship.Friend]),
),
DropdownMenuItem(
value: Relationship.OneDate,
child: Text(show[Relationship.OneDate]),
),
DropdownMenuItem(
value: Relationship.Ongoing,
child: Text(show[Relationship.Ongoing]),
),
DropdownMenuItem(
value: Relationship.Committed,
child: Text(show[Relationship.Committed]),
),
DropdownMenuItem(
value: Relationship.Marriage,
child: Text(show[Relationship.Marriage]),
),
];
class _MyHomePageState extends State<MyHomePage> {
Relationship _relationshipDropdownValue;
// And later in the program …
/// Build
Widget _buildDropdownButtonRow() {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
DropdownButton<Relationship>(
items: _relationshipsList,
onChanged: _updateRelationshipDropdown,
value: _relationshipDropdownValue,
hint: Text("Select One"),
),
if (_relationshipDropdownValue != null)
FlatButton(
child: Text(
"Reset",
style: TextStyle(color: Colors.blue),
),
onPressed: _reset,
),
],
);
}
Widget _buildResultsImage() {
if (_relationshipDropdownValue != null) {
return Image.asset((_relationshipDropdownValue.index >= 3)
? "Heart.png"
: "BrokenHeart.png");
} else {
return SizedBox();
}
}
/// Actions
void _reset() {
setState(() {
_relationshipDropdownValue = null;
});
}
void _updateRelationshipDropdown(Relationship newValue) {
setState(() {
_relationshipDropdownValue = newValue;
});
}
}
Building the dropdown button
A DropdownButton
constructor has several parameters, one of which is a list of items
. Each item is an instance of the DropdownMenuItem
class. Each such instance has a value
and a child
. (See Figure 7-15.)
-
An item’s
value
is something that identifies that particular item.In Listing 7-6, the items’ values are
Relationship.Friend
,Relationship.OneDate
, and so on. They’re all members of theRelationship
enum. You don’t want things likeRelationship.OneDate
appearing on the surface of a menu item, so … -
An item’s
child
is the thing that’s displayed on that item.FIGURE 7-15: Anatomy of a dropdown button.
In Listing 7-6, the items’ children are all
Text
widgets, but you can display all kinds of things on the dropdown items. For example, an item’s child can be aRow
containing aText
widget and anIcon
widget.
In addition to its list of items
, a DropdownButton
constructor has onChanged
, value
, and hint
parameters.
-
The
onChanged
parameter does what such parameters do in so many other constructors.The parameter refers to a function that handles the user’s taps, presses, tweaks, and pokes.
- At any moment, the
value
parameter refers to whichever dropdown button item is selected. -
The
hint
parameter tells Flutter what to display when none of the dropdown button’s items has been selected.In this section’s example, Flutter displays the words Select One.
A dropdown button’s
hint
is typically displayed before the user has chosen any of the button’s items. But Listing 7-6 has a Reset button. When the user presses the Reset button, the button’sonPressed
handler sets_relationshipDropdownValue
back tonull
, so the dropdown button’s hint reappears.
The little Reset button
The Reset button in Listing 7-6 is interesting for more than one reason. First, it’s not a RaisedButton
. Instead, it’s a FlatButton
. A FlatButton
is like a RaisedButton
except … well, a FlatButton
is flat. (See Figures 7-13 and 7-14.)
Another reason to wallow in the Reset button’s code is because of a peculiar Dart language feature — one that’s available only from Dart 2.3 onward. Here’s an abridged version of the _buildDropdownButtonRow
method’s code in Listing 7-6:
Widget _buildDropdownButtonRow() {
return Row(
children: <Widget>[
DropdownButton<Relationship>(
),
if (_relationshipDropdownValue != null)
FlatButton(
),
],
);
}
In this code, the Row
widget’s children
parameter is a list, and the list consists of two items: a DropdownButton
and something that looks like an if
statement. But appearances can be deceiving. The thing in Listing 7-6 isn’t an if
statement. The thing in Listing 7-6 is a collection if. In Chapter 4, I unceremoniously sneak in the word collection to describe Dart’s List
, Set
, and Map
types. A collection if
helps you define an instance of one of those types.
In Listing 7-6, the meaning of the collection if
is exactly what you’d guess. If _relationshipDropdownValue
isn’t null
, the list includes a FlatButton
. Otherwise, the list doesn’t include a FlatButton
. That makes sense because, when _relationshipDropdownValue
is null
, there’s no sense in offering the user an option to make it be null
.
Making a Map
Chapter 4 introduces Dart’s types, one of which is the Map
type. A Map
is a lot like a dictionary. To find the definition of a word, you look up the word in a dictionary. To find a user-friendly representation of the enum value Relationship.OneDate
, you look up Relationship.OneDate
in the show
map.
- Relationship.Friend /ri-lAY-shuhn-ship frEnd/ n. Friend.
- Relationship.OneDate /ri-lAY-shuhn-ship wUHn dAYt/ n. One date.
- Relationship.Ongoing /ri-lAY-shuhn-ship AWn-goh-ing/ adj. Ongoing relationship.
- Relationship.Committed /ri-lAY-shuhn-ship kuh-mIt-uhd / adj. Committed relationship.
- Relationship.Marriage /ri-lAY-shuhn-ship mAIR-ij / n. Marriage.
To be a bit more precise, a Map
is a bunch of pairs, each pair consisting of a key and a value. In Listing 7-6, the variable show
refers to a map whose keys are Relationship.Friend
, Relationship.OneDate
, and so on. The map’s values are "Friend"
, "One date"
, "Ongoing relationship"
, and so on. See Table 7-1.
TABLE 7-1 The show
Map
Key |
Value |
Index |
|
|
0 |
|
|
1 |
|
|
2 |
|
|
3 |
|
|
4 |
In a Dart program, you use brackets to look up a value in a map. For example, in Listing 7-6, looking up show[Relationship.OneDate]
gives you the string "One date"
.
In addition to their keys and values, each map entry has an index. An entry’s index is its position number in the declaration of the map, starting with position number 0. Doris’s buddy Hilda wants a committed relationship and possibly marriage. So the code in Listing 7-6 checks this condition:
When this condition is true, the app displays a heart to indicate a good match. Otherwise, the app displays a broken heart. (Sorry, Hilda.)
Onward and Upward
Doris’s work on the dating app has paid off in spades. Doris is now in a committed relationship with an equally geeky Flutter developer — one who’s well over 18 and who earns just enough money to live comfortably. Doris and her mate will live happily ever after, or at least until Google changes the Dart language specification and breaks some of Doris’s code.
The next chapter is about navigation. How can your app go from one page to another? When the user finishes using the new page, how can your app go back? With more than one page in your app, how can the pages share information? For the answers to these questions, simply turn to this book’s next page!