Flutter provides a powerful framework for building beautiful and interactive user interfaces. One of the key features of Flutter is its support for widgets, which are reusable UI elements that can be combined to build complex interfaces. In this tutorial, we will explore the basics of widgets in Flutter, and how to use them to build great user interfaces.
Introduction to Widgets
In Flutter, everything is a widget. Widgets are reusable UI elements that can be combined to build complex user interfaces. Widgets can be simple, such as a button or a text label, or they can be more complex, such as a form or a scrollable list.
In Flutter, widgets are represented by the Widget class, which is the base class for all widgets. There are two types of widgets in Flutter: stateless widgets and stateful widgets.
Stateless Widgets
A stateless widget is a widget that does not change over time. This means that the widget always displays the same content, regardless of any external factors. Stateless widgets are created using the Stateless Widget class, which requires the implementation of the build method.
Here is an example of a stateless widget that displays a text label:
class MyText extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text('Hello, world!');
}
}
In this example, we create a new class called MyText that extends StatelessWidget. The build method returns a new Text widget that displays the text "Hello, world!".
Stateful Widgets
A stateful widget is a widget that can change over time. This means that the widget's content can be updated based on external factors, such as user input or data from an API. Stateful widgets are created using the StatefulWidget class, which requires the implementation of two classes: a StatefulWidget class that defines the widget, and a State class that manages the widget's state.
Here is an example of a stateful widget that displays a counter:
class MyCounter extends StatefulWidget {
@override
_MyCounterState createState() => _MyCounterState();
}
class _MyCounterState extends State<MyCounter> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Counter: $_counter'),
ElevatedButton(
onPressed: _incrementCounter,
child: Text('Increment'),
),
],
);
}
}
In this example, we create a new class called MyCounter that extends StatefulWidget. The createState method returns a new instance of _MyCounterState, which extends State<MyCounter>. The _MyCounterState class defines a private variable _counter, which is initialized to 0, and a method _incrementCounter that increases the _counter variable by 1.
The build method returns a new Column widget that contains a Text widget that displays the current value of _counter, and an ElevatedButton widget that calls _incrementCounter when pressed.
Note that when the _counter variable is changed, we call the setState method to notify Flutter that the widget needs to be updated. This is because the widget's state has changed, and the UI needs to be updated to reflect this change.
Common Widgets
Flutter provides a wide range of widgets that can be used to build user interfaces. Here are some of the most commonly used widgets:
Text
The Text widget is used to display a piece of text. It can be customized with different text styles and font families.
Text(
'Hello, World!',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
)
In this example, we create a Text widget that displays the text "Hello, World!" with a font size of 24 and bold font weight.
Containers
The Container widget is used to create a rectangular visual element. It can be used to set a background color, add padding or margin, and add borders or shadows. Here's an example of a Container widget:
Container(
width: 200,
height: 200,
color: Colors.blue,
padding: EdgeInsets.all(16),
margin: EdgeInsets.symmetric(vertical: 16),
child: Text('Hello, world!', style: TextStyle(fontSize: 24, color: Colors.white)),
)
In this example, we create a Container widget that is 200x200 pixels in size and has a blue background color. We add padding of 16 pixels to the content, and set a vertical margin of 16 pixels. The child of the Container is a Text widget that displays the text "Hello, world!" in white with a font size of 24.
Rows and Columns
The Row and Column widgets are used to display multiple widgets horizontally or vertically, respectively. Here's an example of a Row and Column widget:
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text('One'),
Text('Two'),
Text('Three'),
],
)
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Hello,'),
Text('world!'),
],
)
In the first example, we create a Row widget with three Text widgets. We set the mainAxisAlignment property to spaceEvenly, which evenly spaces the widgets along the horizontal axis.
In the second example, we create a Column widget with two Text widgets. We set the mainAxisAlignment property to center, which centers the widgets along the vertical axis, and set the crossAxisAlignment property to start, which aligns the widgets to the left side of the column.
Buttons
Flutter provides several types of buttons, including FlatButton, RaisedButton, and IconButton. Here's an example of a RaisedButton:
RaisedButton(
onPressed: () {
print('Button pressed!');
},
child: Text('Press me'),
)
In this example, we create a RaisedButton with the text "Press me". When the button is pressed, the onPressed callback is called, which prints "Button pressed!" to the console.
Text Input
Flutter provides several types of text input widgets, including TextField, TextFormField, and CupertinoTextField. Here's an example of a TextField:
TextField(
decoration: InputDecoration(
labelText: 'Enter your name',
border: OutlineInputBorder(),
),
onChanged: (value) {
print('Name changed to: $value');
},
)
In this example, we create a TextField with a label "Enter your name" and a border. We add an onChanged callback that is called every time the text in the TextField is changed. The value parameter of the callback contains the new value of the text.
Lists
Flutter provides several types of list widgets, including ListView, GridView, and CustomScrollView. Here's an example of a ListView:
ListView(
children: [
ListTile(
leading: Icon(Icons.mail),
title: Text('Email'),
),
ListTile(
leading: Icon(Icons.phone),
title: Text('Phone'),
),
],
)
create a ListView with two ListTile widgets. The ListTile widget is a specialized widget designed to display a single row in a list. We add an Icon widget to the leading property of each ListTile, which displays an icon to the left of the title.
Image
The Image widget is used to display an image from a local asset or network URL.
Image.network('https://example.com/image.jpg')
In this example, we create an Image widget that displays an image loaded from the network URL 'https://example.com/image.jpg'.
Try these examples yourself !!
Platform Independent vs Platform Dependent widgets
In Flutter, a widget is an element of the user interface, such as a button, text field, or image. Widgets can be either platform dependent or platform independent.
Platform dependent widgets are those that have different implementations on different platforms, such as Android and iOS. Examples of platform dependent widgets include the "AppBar" widget, which is a top app bar that displays the app's title, and the "FloatingActionButton" widget, which is a floating circular button used to trigger a primary action in the app.
Platform independent widgets are those that have the same implementation on all platforms. Examples of platform independent widgets include the "Container" widget, which is a rectangular box that can be decorated with a background color, a border, and padding, and the "Text" widget, which displays a string of text.
In general, it is recommended to use platform independent widgets as much as possible in your Flutter app, as they ensure a consistent user experience across all platforms. However, there may be cases where you need to use platform dependent widgets to achieve a specific design or functionality on a particular platform.
Type of Widget | Description | Examples |
Platform Dependent Widget | Widget that has different implementations on different platforms. | AppBar, FloatingActionButton, BottomNavigationBar |
Platform Independent Widget | Widget that has the same implementation on all platforms. | Text, Container, Image, ListView |
Creating Widgets
Let us create a widget and understand above examples...
To make your own widgets, go to the bottom of main.dart and start typing stful, an abbreviation for “stateful”. This will give you a pop-up similar to the following:
This is the new code that you just added:
class CounterWidget extends StatefulWidget {
@override
_CounterWidgetState createState() => _CounterWidgetState();
}
The first line imports the material package from Flutter, which provides many UI widgets and components.
The next line defines a new class CounterWidget that extends StatefulWidget. StatefulWidget is a base class for widgets that have mutable state, meaning that their content can change dynamically over time.
The CounterWidget class has no properties or methods of its own, but it overrides the createState method to create a new instance of _CounterWidgetState whenever it needs to be built or updated. _CounterWidgetState is a private class that manages the state of the CounterWidget.
The _CounterWidgetState class is defined below, in the next block of code.
class _CounterWidgetState extends State<CounterWidget> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Text('You have pressed the button this many times:',),
SizedBox(height: 8.0),
Text('$_counter',style: Theme.of(context).textTheme.headline4,), SizedBox(height: 8.0),
ElevatedButton(
onPressed: _incrementCounter,
child: Text('Increment'),
),
],
);
}
}
The _CounterWidgetState class extends State<CounterWidget>, which means that it manages the state for a CounterWidget instance.
The first property of _CounterWidgetState is an integer _counter, which stores the current count of button presses.
The _incrementCounter method is called when the user taps the "Increment" button. It updates the value of _counter and triggers a rebuild of the widget tree by calling the setState method.
The build method is called by the framework whenever the widget needs to be rendered on the screen. It returns a Column widget that contains a header text, a text displaying the current count, and an ElevatedButton widget that triggers the _incrementCounter method when pressed.
The header text is displayed using a Text widget, and the count is displayed using another Text widget that uses the headline4 text style from the current theme.
The SizedBox widget is used to add some space between the two Text widgets.
That's it! The stateful widget manages the state of the CounterWidget and updates the UI when the state changes. The stateful widget is used in a parent widget to display the CounterWidget on the screen.
Now let us see stateless widgets.This is an example of a StatelessWidget in Flutter that displays various widgets on the screen. Here is a breakdown of each line of code:
class MyApp extends StatelessWidget {
This is the declaration of a new class called MyApp that extends StatelessWidget. StatelessWidget is a type of widget that cannot change its state during its lifetime, and its user interface is built using a build() method.
@override
Widget build(BuildContext context) {
This is the implementation of the build() method, which is required by all StatelessWidget classes. The method takes a BuildContext as an argument and returns a widget that represents the user interface of the class.
return MaterialApp(
title: 'Flutter Widgets Example',
home: Scaffold(
appBar: AppBar(
title: Text('Coding Age Flutter Widgets Example'),
backgroundColor: Colors.red,
),
body: SingleChildScrollView(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
See the output below -
This creates a new MaterialApp widget, which is a convenience widget that provides many features that are commonly used in mobile applications, such as navigation, theming, and internationalization.
The home property is set to a Scaffold widget, which provides a basic structure for a screen. The appBar property of the Scaffold is set to an AppBar widget, which displays a title and other actions.
The body property of the Scaffold is set to a SingleChildScrollView widget, which allows scrolling when the content is too large for the screen. The padding property of the SingleChildScrollView is set to 16.0 pixels on all sides, and the child property is set to a Column widget, which arranges its children in a vertical column.
The crossAxisAlignment property of the Column is set to CrossAxisAlignment.stretch, which stretches the children horizontally to fill the available space.
Text(
'Platform Independent Widgets:',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18.0,
),
),
This is a Text widget that displays a string of text on the screen. The text is "Platform Independent Widgets:", and the style property of the widget is set to a TextStyle object that specifies the font weight and size.
SizedBox(height: 16.0),
This is a SizedBox widget that creates a box with a fixed height of 16.0 pixels. It is used to add vertical spacing between widgets.
Container(
padding: EdgeInsets.all(16.0),
decoration: BoxDecoration(
color: Colors.blueGrey,
borderRadius: BorderRadius.circular(8.0),
),
child: Text(
'This is a container widget!',
style: TextStyle(
color: Colors.white,
fontSize: 16.0,
),
),
),
This is a Container widget that provides a rectangular area that can contain other widgets. The padding property of the Container is set to 16.0 pixels on all sides, and the decoration property is set to a BoxDecoration object that specifies the background color and border radius of the container. The child property of the Container is set to a Text widget that displays a string of text on the screen. The text is "This is a container widget!", and the style property of the widget is set to a TextStyle object that specifies the text color and size.
SizedBox(height: 16.0),
Image.asset(
'assets/coding age logo.png',
height: 100.0,
),
This is a SizedBox widget that creates a box with a fixed height of 16.0 pixels. It is used to add vertical spacing between widgets. Below the SizedBox, there is an Image widget that displays an image from the asset folder of the app. The asset path is 'assets/coding age logo.png' as shown below in output -
Stateless widget complete code is below -
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
var isAndroid;
return MaterialApp(
title: 'Flutter Widgets Example',
home: Scaffold(
appBar: AppBar(
title: Text('Coding Age Flutter Widgets Example'),
backgroundColor: Colors.red,
),
body: SingleChildScrollView(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
// Platform independent widgets
const Text(
'Platform Independent Widgets:',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18.0,
),
),
const SizedBox(height: 16.0),
Container(
padding: EdgeInsets.all(16.0),
decoration: BoxDecoration(
color: Colors.brown,
borderRadius: BorderRadius.circular(8.0),
),
child: Text(
'This is a container widget!',
style: TextStyle(
color: Colors.white,
fontSize: 16.0,
),
),
),
SizedBox(height: 16.0),
Image.asset(
'assets/coding age logo.png',
height: 100.0,
),
SizedBox(height: 16.0),
TextField(
decoration: InputDecoration(
hintText: 'Enter some text...',
),
),
SizedBox(height: 16.0),
ElevatedButton(
onPressed: () {},
child: Text('Click me!'),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all<Color>(Colors.redAccent),
),
),
SizedBox(height: 32.0),
// Platform dependent widgets
Text(
'Platform Dependent Widgets:',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18.0,
),
),
SizedBox(height: 16.0),
FloatingActionButton(
backgroundColor: Colors.red,
onPressed: () {},
child: Icon(Icons.add),
)
,
SizedBox(height: 16.0),
AppBar(
backgroundColor: Colors.redAccent,
title: Text('Android App Bar'),
),
SizedBox(height: 16.0),
BottomAppBar(
child: Row(
children: <Widget>[
IconButton(
onPressed: () {},
icon: Icon(Icons.menu),
),
Spacer(),
IconButton(
onPressed: () {},
icon: Icon(Icons.search),
),
IconButton(
onPressed: () {},
icon: Icon(Icons.more_vert),
),
],
),
),
SizedBox(height: 32.0),
// Stateful widget
Text(
'Stateful Widget:',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18.0,
),
),
SizedBox(height: 16.0),
CounterWidget(),
],
),
),
),
);
}
}
The code starts with importing the necessary packages for the app to function, including flutter/material.dart and dart:io, specifically the Platform class.
Next, there is a MyApp class that extends StatelessWidget. This class is responsible for creating the overall layout of the app.
The build() method is then called to create the visual elements of the app.
The `
This creates a SizedBox widget with a height of 16 pixels to add some spacing between widgets.
This creates an ElevatedButton widget, which is a platform-independent widget that shows a raised button on all platforms. The onPressed callback is empty for now, and the child is a Text widget with the text "Click me!".
This creates another SizedBox widget to add some spacing.
This creates a Text widget with the text "Platform Dependent Widgets:" and applies some custom styles to it.
This creates another SizedBox widget to add some spacing.
This creates a ternary operator that checks if the platform is Android or not. If it is, it creates a FloatingActionButton widget with an onPressed callback that is currently empty, and a child Icon widget with the add icon.
This creates another SizedBox widget to add some spacing.
It creates an AppBar widget with a title property set to a Text widget with the text "Android App Bar".
This creates another SizedBox widget to add some spacing.
This creates another ternary operator that checks if the platform is Android or not. If it is, it creates a BottomAppBar widget with a child Row widget containing three IconButton widgets.
This creates another SizedBox widget to add some spacing.
This creates another Text widget with the text "Stateful Widget:" and applies some custom styles to it.
This creates another SizedBox widget to add some spacing.
This creates an instance of the CounterWidget class, which is a custom StatefulWidget that will be discussed in more detail later.
Hot reload and you’ll see the new widget in action:
Widget which are in stateless and stateful widget states are divided on the screen.
Thanks for reading, and happy coding!
Mastering Flutter Development Series Article - 6 -> An Introduction to Layouts in Flutter: Best Practices for Naming Widgets and Containers