The refactoring is improving code structure without changing what it does.
If you have followed the course so far, your main.dart file is probably starting to look scary. You have a Scaffold, inside a SingleChildScrollView, inside a Column, inside 5 Container with BoxDecoration…
We call this the “Parenthesis Hell” or the Pyramid of Doom.
Indentation goes so far to the right that you need a panoramic monitor to read the code. Today we will learn how to clean up this mess by applying Refactoring and, in the process, see why :key[const] helps performance.
Why Refactor?
Refactoring is not about changing what the code does, it is about improving its structure without altering its behavior.
In Flutter, splitting a complex screen into separate widgets has three advantages:
- Readability: You go from reading 500 lines to reading 50.
- Reusability: If you create a “ProductCard” widget, you can use it on the Home, the Cart, and the Search page.
- Isolation: Each widget has a single responsibility and can be rebuilt from the appropriate point in the tree.
Extracting Widgets
Imagine you have this custom button repeated 3 times in your code:
Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(color: Colors.blue, borderRadius: BorderRadius.circular(8)),
child: Text("Press me", style: TextStyle(color: Colors.white)),
)There are two common ways to extract it: an auxiliary function or a widget class.
Auxiliary Function
A function that returns a widget can be enough for a small, local piece.
Widget _buildButton() {
return Container(...);
}Why is it bad? Because every time you call setState on the main screen, Flutter will re-execute this function and create the Widget from scratch, wasting CPU cycles.
Widget Class
When the piece has its own identity, parameters, or logic, it’s better to create a widget class. This way it gets its own context, can be reused, and forms an independent subtree.
Fortunately, VS Code does it for you.
Place the cursor over the Widget you want to extract (e.g., the Container).
Press Ctrl + . (Windows) or Cmd + . (Mac).
Select “Extract Widget”.
Type the name, for example, CustomButton.
VS Code will automatically generate this at the end of your file:
class CustomButton extends StatelessWidget {
const CustomButton({
super.key,
});
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(color: Colors.blue, borderRadius: BorderRadius.circular(8)),
child: Text("Press me", style: TextStyle(color: Colors.white)),
);
}
}And in your main code, you will now have:
Column(
children: [
CustomButton(),
CustomButton(),
],
)Much cleaner!
Parameterizing the Widget
When extracting the widget, you will often want it to be dynamic (change the text or color). Simply add final properties to the constructor, as we learned in the Dart module.
class CustomButton extends StatelessWidget {
final String text;
final VoidCallback onPressed; // Special type for functions without arguments
const CustomButton({
required this.text,
required this.onPressed,
super.key,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onPressed, // Use the function passed to us
child: Container(
// ... layout ...
child: Text(text),
),
);
}
}Organizing Files
If the widget is reusable or the file has grown too large, move it out of main.dart.
- Create a
/lib/widgetsfolder. - Create a file
custom_button.dart. - Cut and paste the class there.
- Import
package:flutter/material.dartat the beginning of the new file. - In your
main.dart, import the new file.
You don’t need to create a file for every small widget: group those with a common responsibility and separate those that are reused or evolve on their own.
const and Performance
Here we arrive at the key technical point. You will see that the Dart linter constantly suggests: “Add ‘const’ modifier”.
Why is it so insistent about const?
When you call setState, Flutter marks that element for rebuilding and re-executes its build method.
However, if you use const before a constructor:
const Icon(Icons.star)You are telling Flutter: “This configuration is constant. If it appears again in the same position, you can reuse the same instance and avoid updating that subtree.”
The Real Impact
const can avoid work during a rebuild, but it doesn’t magically turn a slow interface into a fast one. The cost also depends on the subtree size, layout, painting, and the operations the code performs.
As a practical rule:
- If a widget and all its arguments are known at compile time, add
constto it. - VS Code is your friend: enable the “fix all” option on save so it adds
constautomatically.
Refactoring Example
Let’s see a quick “Before and After”.
Before (Messy):
ListView(
children: [
Container(
child: Row(children: [Icon(Icons.person), Text("John")]),
),
Container(
child: Row(children: [Icon(Icons.person), Text("Anna")]),
),
],
)After (Clean and Optimized):
ListView(
children: [
const UserItem(name: "John"), // Using const because "John" is fixed here
const UserItem(name: "Anna"),
],
)
// In another file:
class UserItem extends StatelessWidget {
final String name;
const UserItem({required this.name});
@override
Widget build(BuildContext context) {
return Container(
child: Row(children: [const Icon(Icons.person), Text(name)]),
);
}
}