Cyberithub

What are Reusable Components in Flutter [Explained with example]

Advertisements

In this article, we will understand about the reusable components in Flutter. Well, most of the developers out there working with the technology of Android are shifting from native app building to hybrid app development. There are several reasons for shifting the side of code in the Android phase, but most developers have noticed that hybrid app development makes it easier to develop apps from all angles, such as developing multiple system-based apps using a single code base.

The duplication of widgets, even for a minor change, was also noticed and cared for by developers, which negatively impacts the readability and maintainability of the code. To solve this issue, Flutter has a concept called “Reusable Components." It is always good practice to use reusable components, which thus simplifies and prevents you from handling a messy codebase.

 

What are Reusable Components in Flutter [Explained with example]

What are Reusable Components in Flutter [Explained with example]

Also Read: Are Verification and Validation two different things in Flutter ?

What exactly are reusable components? Well, when developing applications, the majority of the same code is duplicated multiple times, which complicates understanding, importing, and structuring in a well-formatted manner. So, as you might have guessed, reusable components are simply components that are built separately from the application, within it, and can be re-used multiple times, where needed or according to the needs.

Base widgets are created by creating widgets separately for multiple uses. You can also customize and modify the widget according to the needs of your project.

 

Example of Reusable components

Let us suppose that, you are building an application and you want a common TextFormField design throughout the application. As a beginner, you would mostly re-write the same design and logic code for the TextFormField throughout the app, or you would write it once and copy it from one place and paste it in another.

class LoginPage extends StatefulWidget {
   const LoginPage({Key? key}) : super(key: key);

   @override
   State<LoginPage> createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
TextEditingController _phoneNoController =  TextEditingController();
TextEditingController _passwordController =  TextEditingController();
   @override
   Widget build(BuildContext context) {
        return Scaffold(
           appBar: AppBar(
              backgroundColor: Colors.blue,
              title: Text('Login Page'),
           ),
           body: Container(
             child: SafeArea(
               child: Column(
                 children: <Widget>[
                 TextFormField(
                 controller: _phoneNoController,
             readOnly: true,
             obscureText:false,
             decoration: InputDecoration(
                 focusedBorder: OutlineInputBorder(
                    borderSide: BorderSide(
                       color: Colors.greenAccent,
                       width: 1.0,
                    ),
                 ),
              enabledBorder: OutlineInputBorder(
                borderSide: BorderSide(
                  color: Colors.greenAccent,
                  width: 1.0,
                ),
              ),
              border: OutlineInputBorder(
                borderSide: BorderSide(
                  color: Colors.teal ,
                  width: 1.0,
                ),
              ),
              hintText: 'Enter Phone Number',
              helperText: 'Phone Number'
              ),
              TextFormField(
                controller: _passwordController,
                readOnly: true,
                obscureText: true,
                decoration: InputDecoration(
                  focusedBorder: OutlineInputBorder(
                    borderSide: BorderSide(
                      color: Colors.greenAccent,
                      width: 1.0,
                    ),
                  ),
                  enabledBorder: OutlineInputBorder(
                    borderSide: BorderSide(
                      color: Colors.greenAccent,
                      width: 1.0,
                    ),
                  ),
                  border: OutlineInputBorder(
                    borderSide: BorderSide(
                      color: Colors.red,
                      width: 1.0,
                    ),
                  ),
                  hintText: 'Enter your Password',
                  helperText: 'Enter your password',
                  prefixIcon: Icon(Icons.lock),
                  suffix: Icon(Icons.big_eye),
                  enabled: true
                  ),
                  ]),
              ),
           ),
       );
    }
}

The above example demonstrates that most beginners make mistakes. For example, both TextFormField have common design patterns, and most of the things shown can be differentiated based on the conditions provided. Apart, from coding in the above manner, which would typically create a mess in the code quality, make understanding issues and degrade clean code value, we should always separate it as a component, which can be used in multiple places.

class CommonTextFormField extends StatelessWidget {
  CommonTextFormField({
    this.controller,
    this.hintText,
    this.helpText,
    this.prefixIcon,
    this.suffixIcon,
    this.isPassword,
    this.enabled,
    this.readOnly,
    this.borderColor,
});
final TextEditingController controller;
final String hintText;
final String helpText;
final IconData prefixIcon;
final IconData suffixIcon;
final bool isPassword;
final bool enabled;
final bool readOnly;
final Color borderColor;

@override
Widget build(BuildContext context) {
  return Container(
    child: TextFormField(
      controller: controller,
      readOnly: null == readOnly ? false : true,
      obscureText: null == isPassword ? false : true,
      decoration: InputDecoration(
        focusedBorder: OutlineInputBorder(
          borderSide: BorderSide(
            color: Colors.greenAccent,
            width: 1.0,
          ),
        ),
        enabledBorder: OutlineInputBorder(
          borderSide: BorderSide(
            color: Colors.greenAccent,
            width: 1.0,
          ),
        ),
        border: OutlineInputBorder(
          borderSide: BorderSide(
            color: null == borderColor ? Colors.teal : borderColor,
            width: 1.0,
          ),
        ),
        hintText: null == hintText ? '' : hintText,
        helperText: null == helpText ? '' : helpText,
        prefixIcon: null == prefixIcon ? null : Icon(prefixIcon),
        suffix: null == suffixIcon ? null : Icon(suffixIcon),
        enabled: null == enabled ? true : false,
       ),
     ),
   );
 }
}

We have created a separate component called 'CommonTextFormField' in the above code which is nothing more than a fully customized component that we can call multiple times in our app without having to write the same logic and design functions over and over. We can see in the code below how we are calling the defined widget along with the parameters and using it multiple times in our app.

class LoginPage extends StatefulWidget {
  const LoginPage({Key? key}) : super(key: key);
  
  @override
  State<LoginPage> createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
TextEditingController _phoneNoController =  TextEditingController();
TextEditingController _passwordController =  TextEditingController();
 @override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(
       backgroundColor: Colors.blue,
       title: Text('Login Page'),
     ),
     body: Container(
       child: SafeArea(
         child: Column(
           children: <Widget>[
           CommonTextFormField(
           controller: _phoneNoController,
           helpText: 'Phone Number',
           hintText: 'Enter your Phone Number',
           prefixIcon: Icons.email,
           ),
           CommonTextFormField(
           controller: _passwordController,
           helpText: 'Password',
           hintText: 'Enter your Password',
           isPassword: true,
           prefixIcon: Icons.lock_open,
           ),
          ]),
        ),
      ),
    );
  }
}

Well, the above creation of CommonTextFormField is a class-based approach to creating a reusable component and using them. But there exists one more approach called the function-based approach in which we create a constant function and then call it multiple times as we need it.

class AppConstants{
  static textFormFieldComponent(TextEditingController controller,String hintText,
  String helpText, IconData prefixIcon, IconData suffixIcon, bool isPassword,
  bool enabled, bool readOnly, Color borderColor) {
    return TextFormField(
      controller: controller,
        readOnly: null == readOnly ? false : true,
        obscureText: null == isPassword ? false : true,
        decoration: InputDecoration(
           focusedBorder: OutlineInputBorder(
             borderSide: BorderSide(
               color: Colors.greenAccent,
               width: 1.0,
             ),
           ),
           enabledBorder: OutlineInputBorder(
             borderSide: BorderSide(
               color: Colors.greenAccent,
               width: 1.0,
             ),
           ),
           border: OutlineInputBorder(
             borderSide: BorderSide(
               color: null == borderColor ? Colors.teal : borderColor,
               width: 1.0,
             ),
           ),
           hintText: null == hintText ? '' : hintText,
           helperText: null == helpText ? '' : helpText,
           prefixIcon: null == prefixIcon ? null : Icon(prefixIcon),
           suffix: null == suffixIcon ? null : Icon(suffixIcon),
           enabled: null == enabled ? true : false,
       );
    }
}

And similarly, you can call this function inside your build widget, to get the output as you need it.

class LoginPage extends StatefulWidget {
  const LoginPage({Key? key}) : super(key: key);

  @override
  State<LoginPage> createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
TextEditingController _phoneNoController =  TextEditingController();
TextEditingController _passwordController =  TextEditingController();

 @override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(
       backgroundColor: Colors.blue,
       title: Text('Login Page'),
     ),
     body: Container(
       child: SafeArea(
         child: Column(
           children: <Widget>[
           AppConstants.textFormFieldComponent(
             controller: _phoneNoController,
             helpText: 'Phone Number',
             hintText: 'Enter your Phone Number',
             prefixIcon: Icons.email,
          ),
           AppConstants.textFormFieldComponent(
             controller: _passwordController,
             helpText: 'Password',
             hintText: 'Enter your Password',
             isPassword: true,
             prefixIcon: Icons.lock_open,
           ),
         ]),
       ),
     ),
   );
 }
}

 

Function vs Class Based Approach

Function-based approaches are mostly used to build small components, which use a very low amount of design and app logic, thus this is mainly used to build side components of the app. While, considering the Class based approach, it is mostly used in places, where there is a high dependency of logic and design approach, and are used to create mostly main components of the app.

 

Conclusion

In this article, we mostly discussed how to reduce the amount of time spent repeatedly copying the same code in order to avoid damaging effects on the code's quality and readability. In addition, we learned how to design reusable and customizable components in Flutter.

Leave a Comment