flutter-refactorizacion-const-clean-code

Widget Refactoring and Using const in Flutter

  • 5 min

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:

  1. Readability: You go from reading 500 lines to reading 50.
  2. Reusability: If you create a “ProductCard” widget, you can use it on the Home, the Cart, and the Search page.
  3. 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)),
)
Copied!

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(...);
}
Copied!

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)),
    );
  }
}
Copied!

And in your main code, you will now have:

Column(
  children: [
    CustomButton(),
    CustomButton(),
  ],
)
Copied!

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),
      ),
    );
  }
}
Copied!

Organizing Files

If the widget is reusable or the file has grown too large, move it out of main.dart.

  1. Create a /lib/widgets folder.
  2. Create a file custom_button.dart.
  3. Cut and paste the class there.
  4. Import package:flutter/material.dart at the beginning of the new file.
  5. 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)
Copied!

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 const to it.
  • VS Code is your friend: enable the “fix all” option on save so it adds const automatically.

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")]),
    ),
  ],
)
Copied!

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)]),
    );
  }
}
Copied!