Chapter 4 Notes: Value Widgets and Input in Flutter
Value Widgets
In Flutter, almost everything is a widget, forming the building blocks of the user interface. This chapter will explore fundamental widgets that inherently hold or display a value: Text, Icon, and Image widgets, as well as widgets specifically designed for enabling user input like Text Fields, Checkboxes, Radio Buttons, Sliders, and Dropdowns.
The Text Widget
The Text widget is a fundamental component for displaying strings of text on the screen. It supports various styling options to control the appearance of the text.
Text('Hello world')
Tip: For static Text widgets, use the
constkeyword to create the widget at compile time, leading to performance improvements during runtime.The
styleproperty allows customization of size, font, weight, color, and more. Comprehensive styling will be covered in Chapter 8, “Styling Your Widgets.”
The Icon Widget
Flutter offers a comprehensive library of pre-designed icons accessible through the Icons class. A complete list of available icons can be found here: https://api.flutter.dev/flutter/material/Icons-class.html.
The Icon widget is used to display an icon. You specify the desired icon using the Icons class.
Icon(
Icons.cake,
color: Colors.red,
size: 200,
)
The Image Widget
Displaying images in Flutter involves two primary steps:
Obtaining the image source (either embedded within the app or fetched from an external source over the Internet).
Configuring the sizing and scaling of the image to fit the layout.
Embedded Images
Embedding images directly into your app offers faster loading times but increases the overall app size. To embed an image:
Place the image file in your project's asset folder (e.g.,
assets/images).Declare the asset in the
pubspec.yamlfile:
flutter:
assets:
- assets/images/photo1.png
- assets/images/photo2.jpg
Run
flutter pub getfrom the command line to process the changes in thepubspec.yamlfile.
Tip: The
pubspec.yamlfile contains essential project metadata, including library dependencies, asset files (like images and fonts), and other configuration details. It serves a similar purpose topackage.jsonin JavaScript projects.
To display the embedded image in your custom widget, use the Image.asset() constructor:
Image.asset('assets/images/photo1.jpg')
Network Images
Network images are fetched from a remote server over the Internet using HTTP or HTTPS. This allows for dynamic updates to the images without requiring app updates.
Image.network(imageUrl)
While network images offer flexibility, they are generally slower to load compared to embedded images due to the network request overhead. Ensure proper error handling and caching mechanisms are in place to optimize the user experience.
Sizing an Image
Images are often placed within a Container widget, which influences their size and layout. By default, Flutter's layout engine shrinks the image to fit its container but does not enlarge it beyond its original dimensions.
This behavior is governed by the BoxFit property, which controls how the image is scaled and positioned within its container. Available options include:
fill: Stretches the image to completely fill the container, potentially distorting the aspect ratio.cover: Scales the image uniformly until it covers the entire container, clipping any portions that exceed the container's bounds.fitHeight: Adjusts the image height to match the container height, clipping the width or adding extra space if necessary.fitWidth: Adjusts the image width to match the container width, clipping the height or adding extra space if necessary.contain: Scales the image down until both its height and width fit entirely within the container, preserving the aspect ratio and potentially adding extra space around the image.scaleDown: Scales the image down to fit the container while maintaining its aspect ratio, only if the image is larger than the container. If the image is smaller, no scaling is applied.
To specify the desired fitting behavior, set the fit property of the Image widget:
Image.asset(
'assets/images/woman.jpg',
fit: BoxFit.contain,
)
Input Widgets
Flutter provides a range of widgets to handle user input, similar to HTML forms. However, they require more manual management of state and control over input behavior. These widgets include Text Fields, Checkboxes, Radio Buttons, Sliders and Dropdowns.
Input widgets generally do not maintain their own state and are often unaware of each other unless grouped together using a Form widget. This allows for centralized management of form data and validation.
Caution: Input widgets should typically be used within a StatefulWidget because their values can change dynamically based on user interaction. StatefulWidgets will be fully explained in Chapter 9, “Managing State.”
Text Fields
A single-line text input box can be created using the TextField widget. It allows users to enter and edit text.
const Text('Search terms'),
TextField(
onChanged: (String val) => _searchTerm = val,
)
The onChanged property is a callback function that is executed after every keystroke, providing the current string value entered by the user. This allows you to dynamically update the app's state based on the input.
To provide an initial value to the TextField, you can use a TextEditingController:
TextEditingController _controller = TextEditingController(text: "Initial value here");
const Text('Search terms'),
TextField(
controller: _controller,
onChanged: (String val) => _searchTerm = val,
)
You can retrieve the current value of the TextField using the _controller.text property.
Enhancing TextFields with InputDecoration
The InputDecoration widget offers a wide array of options to customize the appearance and behavior of TextFields:
return TextField(
controller: _emailController,
decoration: InputDecoration(
labelText: 'Email',
hintText: 'you@email.com',
icon: Icon(Icons.contact_mail),
),
)
InputDecoration options include:
labelText: Displays text above the TextField, typically used as a descriptive label.hintText: Displays light-gray placeholder text inside the TextField when no user input is present. The hint text disappears when the user starts typing.errorText: Displays an error message below the TextField, typically used to indicate validation errors. The error message is often set dynamically based on validation logic.prefixText: Displays static text to the left of the user's input within the TextField.suffixText: Displays static text to the right of the user's input within the TextField.icon: Displays an icon to the left of the entire TextField.prefixIcon: Displays an icon inside the TextField to the left of the input area.suffixIcon: Displays an icon inside the TextField to the right of the input area.Tip: To create a password input box, set the
obscureTextproperty totrue. This hides the entered text, displaying asterisks or dots instead.
return TextField(
obscureText: true,
decoration: InputDecoration(
labelText: 'Password',
),
)
Special Soft Keyboard
The keyboardType property allows you to specify the type of keyboard that is displayed to the user, optimizing the input experience for different types of data:
return TextField(
keyboardType: TextInputType.number,
);
Available options include:
TextInputType.datetime: Displays a keyboard optimized for entering date and time values.TextInputType.emailAddress: Displays a keyboard optimized for entering email addresses, including the @ symbol and other common email-related characters.TextInputType.number: Displays a numeric keyboard, suitable for entering numbers.TextInputType.phone: Displays a phone number keypad, including digits and symbols commonly used in phone numbers.Tip: The
inputFormattersproperty allows you to restrict the type of text that can be entered into the TextField. You can use predefined formatters or create custom ones to enforce specific input rules:BlacklistingTextInputFormatter: Prevents the user from entering certain characters.WhitelistingTextInputFormatter: Allows only specific characters to be entered.LengthLimitingTextInputFormatter: Limits the maximum number of characters that can be entered.
return TextField(
inputFormatters: [
WhitelistingTextInputFormatter(RegExp('[0-9 -]')),
LengthLimitingTextInputFormatter(16)
],
decoration: InputDecoration(
labelText: 'Credit Card',
),
);
Checkboxes
Flutter checkboxes are represented by the Checkbox widget. They have a value property (a boolean indicating whether the checkbox is checked) and an onChanged method that is called whenever the checkbox's state changes.
Checkbox(
value: true,
onChanged: (bool val) => print(val)
)
Tip: A Flutter
Switchwidget serves a similar purpose to a Checkbox but provides a different visual representation. It also shares the same options and usage patterns.
Radio Buttons
Radio buttons are grouped together to allow the user to select only one option from a set of choices. The groupValue property is used to identify the selected radio button within the group. All radio buttons in the same group should share the same groupValue variable.
Each Radio button has a value property that represents the value associated with that specific radio button, whether it is currently selected or not.
enum SearchType { anywhere, text, title }
SearchType _searchType = SearchType.anywhere;
Column(
children: <Widget>[
Row(
children: <Widget>[
Radio<SearchType>(
groupValue: _searchType,
value: SearchType.anywhere,
onChanged: (SearchType? val) => setState(() => _searchType = val!),
),
const Text('Search anywhere'),
],
),
Row(
children: <Widget>[
Radio<SearchType>(
groupValue: _searchType,
value: SearchType.text,
onChanged: (SearchType? val) => setState(() => _searchType = val!),
),
const Text('Search page text'),
],
),
Row(
children: <Widget>[
Radio<SearchType>(
groupValue: _searchType,
value: SearchType.title,
onChanged: (SearchType? val) => setState(() => _searchType = val!),
),
const Text('Search page title'),
],
),
],
)
Sliders
Sliders allow users to select a numeric value within a continuous range by dragging a thumb along a horizontal track.
Slider(
label: _value.toString(),
min: 0, max: 100,
divisions: 100,
value: _value,
onChanged: (double val) => setState(() => _value = val),
)
Key properties of the Slider widget include:
value: A double representing the currently selected value of the slider.onChanged: An event handler that is called whenever the slider's value changes due to user interaction. It provides the new value as a double.min: The minimum value that the slider can represent (defaults to 0.0).max: The maximum value that the slider can represent (defaults to 1.0).label: An optional label that displays the current value of the slider.
Dropdowns
Dropdowns, implemented using the DropdownButton widget, are used for selecting one option from a discrete list of items.
enum SearchType { web, image, news, shopping }
SearchType _searchType = SearchType.web;
DropdownButton<SearchType>(
value: _searchType,
items: const <DropdownMenuItem<SearchType>>[
DropdownMenuItem<SearchType>(
child:Text('Web'),
value: SearchType.web,
),
DropdownMenuItem<SearchType>(
child:Text('Image'),
value: SearchType.image,
),
DropdownMenuItem<SearchType>(
child:Text('News'),
value: SearchType.news,
),
DropdownMenuItem<SearchType>(
child:Text('Shopping'),
value: SearchType.shopping,
),
],
onChanged: (SearchType? val) => setState(() => _searchType = val!),
)
Grouping Form Widgets
Form widgets can be grouped together for easier management and validation using the Form widget. The Form widget acts as a container for related input fields, providing a convenient way to handle form data and trigger validation.
Form Widget
The Form widget groups input fields using a unique key. To use the Form widget, you need to create a GlobalKey of type FormState:
GlobalKey<FormState> _key = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Form(
key: _key,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: // All the form fields will go here
);
}
The FormState object provides the following useful methods:
save(): Saves the values of all contained fields by calling their respectiveonSavedmethods.validate(): Runs thevalidatorfunction for each field in the form, returningtrueif all fields are valid andfalseotherwise.reset(): Resets each field to itsinitialValue.
FormField Widget
FormField is a versatile widget that provides save, reset, and validator event handlers to any inner widget. This allows you to integrate custom input widgets seamlessly into a Form.
FormField<String>(
builder: (FormFieldState<String> state) {
return TextField(); // Any field widget like DropDownButton,
// Radio, Checkbox, or Slider.
},
onSaved: (String? initialValue) {
// Push values to a repository or something here.
},
validator: (String? val) {
// Put validation logic here (further explained below).
return val!.isEmpty ? 'Please enter some text' : null;
},
)
TextFormField Widget
TextFormField combines a TextField and FormField into a single widget, providing built-in support for form integration. It includes onSaved, validator, and reset properties for handling form-related events.
TextFormField(
onSaved: (String? val) {
print('Search Term TextField: form saved $val');
},
validator: (String? val) {
// Put your validation logic here
return val!.isEmpty ? 'Please enter some text' : null;
},
)
Best practice: Use TextField for text inputs that are not part of a Form, and TextFormField for text inputs that are integrated into a Form.
onSaved
The onSaved callback is triggered when the Form.save() method is called. It provides an opportunity to save the current value of the field to a persistent storage or perform other data processing tasks.