How to build a custom widget in Flutter — GoGoSoon

As we all know Flutter is gaining more and more popularity day by day. Flutter apps has been loved by people for its smoothness. One such example app is Zerodha’s Kite. Kite is a trading app released by India’s biggest stock broker Zerodha. Similar to this there were many complex applications built with Flutter. However, building complex applications is not so simple. You have to refactor your code as and when needed to maintain the app’s performance. One such refactoring technique is extracting the duplicated code/component and reuse them at multiple places.

In this blog, we’ll be learning about replacing a duplicated component by building a custom widget in Flutter.

What is a Custom Widget

In Flutter, a custom widget refers to a user-defined widget that encapsulates a specific set of functionality or visual representation. Custom widgets are the building blocks of a Flutter application. They allow developers to create reusable UI components that can be used throughout the application.

If you’re switching from React Native, assume Custom Widget as a custom React component and what we call as props in React will be called as parameters in Flutter.

Why Custom Widgets

Custom widgets enable you to encapsulate complex UI elements, promote code re-usability, and enhance the maintainability of your Flutter applications. Developers should build custom widgets in Flutter for several reasons. Here are a few,

Code Re-usability

It allow developers to encapsulate complex functionality and appearance into reusable components. Once created, custom widgets can be used multiple times throughout the application, reducing code duplication and promoting a modular development approach.

Maintainability

It contribute to the maintainability of the codebase. By encapsulating specific functionality or visual representation, custom widgets create a separation of concerns. This separation makes it easier to locate, modify, and debug code related to a particular UI component.

Consistent UI

It enable developers to define a consistent and unified UI design across their application.

Abstraction

It provide a level of abstraction that hides the implementation details and complexity of a particular UI element. Developers can create high-level widgets that expose a simplified interface and handle the internal logic, allowing other developers to use the widget without worrying about its internal workings. This abstraction promotes modularity, making it easier to understand, test, and maintain the code.

Build a Custom Widget

Let's start building our custom widget.

Clone the repo

Instead of starting from the scratch, I've created a Flutter app in Github and added a duplicated code/components in that repo. Let's begin from there.

Pull the code from Github by running the below command


git clone https://github.com/5minslearn/Flutter-Custom-Widget.git

or 

git clone git@github.com:5minslearn/Flutter-Custom-Widget.git

By default, it'll be in master branch. I'm switching to a refactor branch (you don't need to) because I want you all to have a look at my initial and final code. Initial code will be in master branch and final code will be in refactor branch.

Run the following command to install all the dependencies.

cd Flutter-Custom-Widget/
flutter pub get

Run the app

Open the repo in Visual Studio Code and spin up your emulator (you may connect your mobile device too). Once your emulator is up and running, press F5 to run the app in the emulator.

Here's the view of your app on the first run.

If you've come so far, that's great.

Analyze the code

Let's look at the code. Open lib/main.dart file.

We have MyApp class called at the beginning. This in-turn calls MyHomePage class.

Here we go. Our code for the entire UI.

If you're someone who loves writing clean code, you would definitely say that it's a ugly code. Here's the reason for it. Look at the code carefully where the lines from 44 to 56 and the lines from 58 to 70 are completely duplicated except a very few handpicked words. Example, the word "Twitter" has been replaced with "Instagram".

The Clean coder will definitely refactor this code before working on adding new features/functionalities. Let us follow the clean coding practice too.

Refactor - Build a custom widget

We have to extract the text and button into a separate component. This component should accept the platform and onPressed as it's parameters. We can template out the common text from them.

So, our code to build the custom widget looks like below.

class CustomButton extends StatelessWidget {
  final String platform;
  final VoidCallback onPressed;

  const CustomButton(
      {super.key, required this.platform, required this.onPressed});

  @override
  Widget build(BuildContext context) {
    return Center(
        child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
      Text("Press the below button to follow me on $platform"),
      ElevatedButton(
        onPressed: () {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(
              content: Text("Pressed Follow on $platform button"),
              duration: const Duration(seconds: 1),
            ),
          );
          onPressed();
        },
        child: Text("Follow on $platform"),
      )
    ]));
  }
}

As we discussed above, we've the template text and accept platform and onPressed parameters. We replace platform wherever we need and call onPressed method as the extension of showing a snack bar.

Add the above code at the very end of the main.dart file.

Integrate Custom Widget

Let's integrate our custom widget into our code.

Replace lines 44 to 56 with the following code,

CustomButton(
  platform: 'Twitter',
  onPressed: () {
    // Open Twitter App
  },
),

Replace lines 58 to 70 with the following code,

CustomButton(
  platform: 'Instagram',
  onPressed: () {
    // Open Instagram App
  },
),

Run your app now.

Unfortunately, you'll not notice any change in the UI. However your underlying code has changed. That's exactly what refactoring is.

Quoting from Martin Fowler,

Refactoring is a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior.

refactoring.com

You may ask a question by look at the above code. The line numbers 43 and 50 also contain the same code (const SizedBox(height: 16),). Why don't you include that into your component.

That's great if this question really hit your mind.

There is no need for the custom widget component to include the SizedBox component. Because, SizedBox component is added in the Home page to give some space between each component. It is not mandatory that whenever we use this button, we have to give a space at the top/bottom of the widget. However if such case arise, you can add the SizedBox widget inside your custom widget.

What's the benefit of building custom widget

You don't see a direct benefit right away. However, you may experience it in the future. Here's a quick example for you.

Let's assume you've built this app for a client. It has become a complex app and you've used this custom widget around 20 places in your app. The app is released and people enjoy using it. Around 6 months later, your client come back to you with the next version of changes. One of the items in the huge list is, "We're coming up with a slight change in theme. Replace all the social media referral buttons to be in outlined shape and change the color to green".

It is one simple configuration change in our custom widget. However assume, if you have not built the custom widget and copy/pasted the same code in all the 20 places. Then you have to carefully look at each place and replace them with care without touching other pieces of code.

These are the only 2 lines we have to change in the our custom widget

      OutlinedButton(
        style: OutlinedButton.styleFrom(foregroundColor: Colors.green),

If you have not refactored your code, you have to make this change in 20 places.

I've pushed my code to the same Github repo. Refer master branch for the non-refactored code and refactor branch for refactored code.

Note

Always use custom widgets for it's specific use cases. For example, in our case, it is for Social media redirects. This widget should not be used at places which is unrelated to it's context. If you do so, remember the above case where the client requirement was to change the design of only the social media referral buttons, but our change will be applied to all the other places where this widget has been used, leading to unexpected bugs. It is always advised to write unit test cases for Custom Widgets which will help us to mitigate the bugs earlier.

One more tip would be to name your component more readable. This makes the other developer know what the widget does just by reading it's name. In our case, I've named it as CustomButton which makes no sense. Instead the best possible alternatives would be SocialMediaButton, SocialButton, etc. which fits into our use case.

Conclusion

In this blog, we learnt about building a custom widget by removing a duplicated code/component. Building custom widgets in Flutter promotes code re-usability, maintainability, consistency, abstraction, flexibility, and community collaboration. Custom widgets are a powerful tool in the Flutter developer's toolkit, enabling the creation of beautiful and functional user interfaces while maximizing efficiency and maintainability.

Hope you enjoyed reading the article. If you wish to learn more about Flutter, subscribe to my article from my site.