Recently, I have been using Flutter more and diving deeper into it because I want to reach a state of unconscious competence. I also want to start sharing more of what I know about Flutter, beginning with the building blocks of UI. Our first deep dive will be into Flutter widgets.
What Are Flutter Widgets?
Flutter is an open-source framework by Google that provides a powerful toolkit for crafting beautiful, natively compiled mobile, web, and desktop apps from a single codebase. This is where widgets come in. Widgets are the building blocks of the user interface, they define everything you see on the screen.
Imagine a Flutter app as a theater performance, where widgets are the actors. Without actors, there’s no performance just an empty stage. The same applies to Flutter applications: without widgets, there’s no UI at all , no buttons, no text, no lists, nothing! That’s how essential widgets are in Flutter.
The Widget Tree
The widget tree contains the configuration of all the widgets. Remember, the widget tree is built using several widgets, which are combined to form a hierarchical structure. It defines the UI structure, specifying how widgets are arranged and nested.
However, the widget tree does not store visual or layout-related information. Instead, Flutter uses the render tree, a low-level system that handles layout, painting, and rendering on the screen. While developers primarily interact with the widget tree, the render tree works behind the scenes to ensure smooth UI performance.
Flutter optimizes performance by efficiently updating only the parts of the widget tree that change, rather than rebuilding the entire UI. This allows Flutter to maintain a highly responsive interface while minimizing unnecessary computations.
Understanding the relationship between the widget tree and the render tree is crucial for designing efficient and scalable Flutter applications.
A simple example of how widgets are structured in Flutter could be a basic app with a Column
, Text
, and a Button
, showing how widgets are nested inside each other in a tree-like structure:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(//Root widget
home: Scaffold(
appBar: AppBar(title: Text('Widget Tree Example')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Hello, Flutter!'),
ElevatedButton(
onPressed: () {
// Button pressed
},
child: Text('Click Me'),
),
],
),
),
),
);
}
Examples of Flutter Widgets
Let’s now look at some commonly used widgets in Flutter:
1. Text
This is one of the most frequently used widgets, allowing you to display and style text.
Text('This widget is called Text');
2. Icon
This is used to display and style icons.
Icon(Icons.star, color: Colors.blue) // Displays a blue star icon
To change the size, add the size
parameter:
Icon(Icons.star, color: Colors.blue, size: 50)
3. Image
This widget is used to display images.
Image.asset('assets/logo.png')
4. Container
A flexible box used for styling UI elements.
Container(color: Colors.blue, width: 100, height: 100)
5. Column
Used to arrange its children vertically (from top to bottom).
Column(
children: [
Widget1(),
Widget2(),
Widget3(),
],
)
6. Row
Similar to Column
, but arranges children horizontally (from left to right).
Row(
children: [
Widget1(),
Widget2(),
Widget3(),
],
)
Types of Widgets
Using the theater performance analogy, there are two types of actors:
Those who follow the script exactly, delivering the same lines every time.
Those who adjust their performance based on the audience’s reaction.
Widgets work in a similar way. There are two main types:
1. Stateless Widgets (Like Scripted Actors)
These widgets are immutable, meaning they don’t change after being built. They display the same UI regardless of user interaction or data changes.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Stateless Widget Example')),
body: Center(
child: Text('Hello, Flutter!'), // Always displays the same text
),
),
);
}
}
2. Stateful Widgets (Like Adaptive Actors)
These widgets can change over time based on user input, animations, or data updates. They hold state, which allows them to rebuild when something changes.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int counter = 0;
void incrementCounter() {
setState(() {
counter++; // Changes the value of counter
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Stateful Widget Example')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Button pressed $counter times'),
ElevatedButton(
onPressed: incrementCounter, // Calls function when clicked
child: Text('Press Me'),
),
],
),
),
),
);
}
}
Best Practices for Using Widgets in Flutter
Keep Widgets Small and Focused: Each widget should have a single responsibility. If a widget grows too large, split it into smaller widgets.
Use
const
Whenever Possible: If a widget never changes, mark it asconst
. This improves performance by reducing unnecessary rebuilds, thus saving memory.Use Stateless Widgets When Possible: If your widget doesn’t change, use
StatelessWidget
. Only useStatefulWidget
when necessary, stateless widgets perform better and use less memory.Use
SizedBox
Instead ofContainer
When Only Spacing is Needed:SizedBox
is lighter and improves performance.Wrap Widgets Correctly for Proper Layout: Use
Expanded
orFlexible
insideRow
orColumn
to prevent overflow and ensure UI stability on small screens.Use
ListView.builder
for Large Lists:ListView.builder
is more efficient thanListView
with many children since it loads only visible items, saving memory.Avoid Nesting Too Many Widgets: Keep widgets shallow for better readability. Use helper methods or separate widgets when necessary.
Test Your UI on Different Screen Sizes: Use
MediaQuery
orLayoutBuilder
to make widgets responsive and ensure your app looks good on all devices.
Conclusion
Widgets are the heart of Flutter applications, so understanding how they work is essential for building efficient and scalable apps. As you continue your Flutter journey, always strive to follow best practices. Doing so will not only improve the maintainability of your code but also help you create smooth, responsive, and scalable applications.
Keep experimenting, keep building, and most importantly, keep sharing your learning experience!