Flutter is a modern UI framework built on top of the Dart programming language, and at its heart lies the widget tree. Everything in Flutter is a widget—from a simple text label to an entire page.
There are two main types of widgets in Flutter:
- Stateless Widgets
- Stateful Widgets
In this article, we’ll focus on the lifecycle of a Stateless Widget. Unlike Stateful Widgets, which go through multiple stages of creation, updating, and destruction, a Stateless Widget has a very simple lifecycle: it only depends on the build() method.
Let’s break this down step by step.
Introduction to Stateless Widgets
Before understanding the lifecycle, let’s recall what a Stateless Widget is.
- A Stateless Widget is a widget that does not change once it is built.
- It is immutable: its properties cannot be changed after construction.
- It is perfect for static UI, like displaying text, icons, or buttons.
- Examples include:
TextIconElevatedButton
The Concept of a Lifecycle in Flutter
A lifecycle in Flutter refers to the different phases a widget goes through:
- Creation
- Rendering
- Updates (if applicable)
- Destruction
For Stateful Widgets, the lifecycle is complex. They have multiple lifecycle methods like initState(), didChangeDependencies(), setState(), and dispose().
But for Stateless Widgets, the lifecycle is extremely simple.
Lifecycle of a Stateless Widget
The lifecycle of a Stateless Widget consists of only one major method:
1. build(BuildContext context)
- This is the only method you override when creating a Stateless Widget.
- It describes the widget’s UI.
- It is called when the widget is created and inserted into the widget tree.
Unlike Stateful Widgets, there are no extra lifecycle methods such as initialization or disposal.
Step-by-Step Lifecycle Flow
Here’s how it works:
- Constructor is Called
- When you create a new instance of a Stateless Widget, its constructor runs.
- You typically pass immutable data (via
finalfields).
const MyWidget(title: 'Hello Flutter'); build()is Called- Immediately after the constructor, Flutter calls the
build()method. - This method returns a widget tree describing the UI.
@override Widget build(BuildContext context) { return Text(title); }- Immediately after the constructor, Flutter calls the
- Widget Renders on Screen
- The UI defined by
build()appears on the device’s screen.
- The UI defined by
- Rebuild (If Triggered Externally)
- Stateless Widgets don’t rebuild themselves.
- They only rebuild if their parent widget triggers a rebuild and passes new data.
- Widget is Destroyed
- If the widget is removed from the widget tree, it is destroyed.
- Unlike Stateful Widgets, there’s no
dispose()method here.
Example of a Stateless Widget Lifecycle
Let’s write a simple example:
import 'package:flutter/material.dart';
class GreetingWidget extends StatelessWidget {
final String message;
const GreetingWidget({super.key, required this.message});
@override
Widget build(BuildContext context) {
print('build() called for GreetingWidget');
return Center(
child: Text(
message,
style: const TextStyle(fontSize: 24),
),
);
}
}
Key Notes:
- When you use this widget, the constructor runs first.
- Then the
build()method is called. - The
printstatement helps visualize when the build process happens.
What Triggers a Stateless Widget to Rebuild?
Stateless Widgets cannot change by themselves. They only rebuild when:
- The Parent Widget Rebuilds
- If the parent widget is rebuilt (e.g., due to state change higher up), the child Stateless Widget rebuilds too.
- New Data is Passed
- If new parameters are provided through the constructor, Flutter discards the old widget and creates a new one.
Example:
GreetingWidget(message: 'Hello World');
GreetingWidget(message: 'Welcome Back!');
Here, the widget is rebuilt because the message changed.
Why Does build() Get Called More Than Once?
Sometimes, you may notice build() being called multiple times. This is normal in Flutter.
Reasons include:
- Screen resizing (orientation change).
- Parent widget rebuilds.
- Navigator pushing/popping pages.
Even though build() may be called often, Stateless Widgets remain efficient because they don’t track state.
Comparing Lifecycle: Stateless vs Stateful Widgets
| Lifecycle Aspect | Stateless Widget | Stateful Widget |
|---|---|---|
| Initialization | Constructor only | initState() |
| Build | build() | build() |
| Updates | From parent only | Can update via setState() |
| Disposal | Automatic | dispose() available |
This shows how much simpler Stateless Widgets are.
Best Practices for Using Stateless Widget Lifecycle
- Keep
build()Pure- Avoid adding heavy logic inside
build(). - It should only describe the UI.
- Avoid adding heavy logic inside
- Use
constWherever Possible- Mark your widgets as
constto avoid unnecessary rebuilds.
const Text('Static Label'); - Mark your widgets as
- Pass Data via Constructor
- Always pass values through
finalfields in the constructor.
- Always pass values through
- Don’t Try to Manage State Internally
- Stateless Widgets cannot hold changing data.
- Compose Widgets
- Break complex UI into smaller reusable Stateless Widgets.
Example Project: Lifecycle in Action
Here’s a mini Flutter app that demonstrates the lifecycle of a Stateless Widget:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Stateless Lifecycle Example')),
body: const ParentWidget(),
),
);
}
}
class ParentWidget extends StatefulWidget {
const ParentWidget({super.key});
@override
State<ParentWidget> createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
String message = 'Hello Flutter!';
void updateMessage() {
setState(() {
message = 'Message Updated!';
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
GreetingWidget(message: message),
ElevatedButton(
onPressed: updateMessage,
child: const Text('Update Message'),
),
],
);
}
}
class GreetingWidget extends StatelessWidget {
final String message;
const GreetingWidget({super.key, required this.message});
@override
Widget build(BuildContext context) {
print('GreetingWidget build() called');
return Text(
message,
style: const TextStyle(fontSize: 22),
);
}
}
What Happens Here:
- The
GreetingWidgetis Stateless. - When you press the button, the parent rebuilds, passing a new message.
GreetingWidgetrebuilds automatically with new data.
Advantages of Stateless Widget Lifecycle
- Simplicity
- Only one method to handle (
build).
- Only one method to handle (
- Performance
- Lightweight and efficient.
- Predictability
- No hidden states, easy to debug.
- Clean Architecture
- Encourages separation of UI and logic.
Limitations of Stateless Widget Lifecycle
- No State Management
- Cannot handle dynamic data internally.
- Depends on Parent Widgets
- Needs external changes to rebuild.
- Not Suitable for Interactive Widgets
- Forms, animations, and counters require Stateful Widgets.
Leave a Reply