Why Using Flutter Widget Methods Is Not a Best Practice

Flutter, a UI toolkit Google has created to bring constituents natively compiled applications on mobile, web or desktop from a common codebase, has won supporters among the developers for its flexibility and expressive UI capabilities. On the other hand, like any powerful tool, developers must follow certain best practices so that the work done results remains maintainable, efficient, and scalable.
One of the affective instruments that may be problematic is the overuse of widget methods. In contrast, the practice of dividing the UI into smaller widgets may be the correct one, although if too much logic is sealed to these widget methods, it could become harmful. In this blog, we will look at the reasons for this and use simple examples to show the more prudent way.

1. Performance Issues

When you define your widgets as methods, every time the parent widget rebuilds, these methods are called again. This can lead to unnecessary rebuilds and performance issues.

Example: Widget Method

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home Page'),
      ),
      body: Center(
        child: _buildButton(),
      ),
    );
  }

  Widget _buildButton() {
    return ElevatedButton(
      onPressed: () {},
      child: Text('Press me'),
    );
  }
}

In the above example, _buildButton() is a method that gets called every time MyHomePage is rebuilt. If there are many such methods, it can lead to performance degradation.

Better Approach: Separate Widget

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home Page'),
      ),
      body: Center(
        child: MyButton(),
      ),
    );
  }
}

class MyButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () {},
      child: Text('Press me'),
    );
  }
}

Here, MyButton is a separate widget. It will only rebuild if its state changes, not every time MyHomePage rebuilds.

2. Code Readability and Maintainability

Widget methods can lead to cluttered and hard-to-read code, especially in large applications. Keeping your UI components as separate widgets improves readability and maintainability.

Example: Cluttered Code with Widget Methods

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home Page'),
      ),
      body: Column(
        children: [
          _buildHeader(),
          _buildContent(),
          _buildFooter(),
        ],
      ),
    );
  }

  Widget _buildHeader() {
    return Text('Header');
  }

  Widget _buildContent() {
    return Text('Content');
  }

  Widget _buildFooter() {
    return Text('Footer');
  }
}

Better Approach: Separate Widgets

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home Page'),
      ),
      body: Column(
        children: [
          Header(),
          Content(),
          Footer(),
        ],
      ),
    );
  }
}

class Header extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text('Header');
  }
}

class Content extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text('Content');
  }
}

class Footer extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text('Footer');
  }
}

With this approach, each part of the UI is clearly separated into its own widget, making the code easier to read and maintain.

3. Reusability

Breaking your UI into smaller widgets promotes reusability. You can use these widgets across different parts of your application without duplicating code.

Example: Non-reusable Widget Method

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home Page'),
      ),
      body: Center(
        child: _buildButton(),
      ),
    );
  }

  Widget _buildButton() {
    return ElevatedButton(
      onPressed: () {},
      child: Text('Press me'),
    );
  }
}

Better Approach: Reusable Widget

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home Page'),
      ),
      body: Center(
        child: MyButton(),
      ),
    );
  }
}

class MyButton extends StatelessWidget {
  final VoidCallback onPressed;
  final String label;

  MyButton({this.onPressed, this.label = 'Press me'});

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: onPressed,
      child: Text(label),
    );
  }
}

Now MyButton is a reusable widget that can be used in different parts of the application with different labels and actions.

Conclusion

While using widget methods in Flutter might seem like a convenient way to build your UI, it can lead to performance issues, reduce code readability, and hinder reusability. By breaking down your UI into smaller, reusable widgets, you can create more efficient, maintainable, and scalable Flutter applications. Remember, the key to mastering Flutter is not just about writing code that works but writing code that works well in the long run.