How Flutter Encapsulates Text Input Box Components

  • 2021-12-13 09:15:23
  • OfStack

Considerations for directory UI component encapsulation Text input box interface definition Code implementation Component usage Pit-stepping record Summarize

Considerations for UI Component Encapsulation

To encapsulate an UI component, you usually need to consider the following three points:

How to define the interface: that is, what input parameters the component receives to control the appearance and behavior of the component; Separation from business: UI component should only be responsible for interface, not business, and specific business should be completed by business layer; Easy to use: Because it is an UI component, it should be as simple and easy to use as possible, so that users can get started quickly.

Text input box interface definition

First of all, look at the code that used text boxes in our previous article. In fact, we only called a function to return a new component. The reason for doing this is that the user name and password use member attributes, and different behaviors need to be set according to different member attributes, mainly including:

Text box decorations vary: including placeholders, front icons, and rear icons, whose behavior is bound to member attributes, and different TextEditingCongtroller. onChanged event callback content is different. The keyboard type and whether to hide the input are different. The fields corresponding to the form are different.

Widget _getPasswordInput() {
    return _getInputTextField(
      TextInputType.text,
      obscureText: true,
      controller: _passwordController,
      decoration: InputDecoration(
        hintText: " Enter password ",
        icon: Icon(
          Icons.lock_open,
          size: 20.0,
        ),
        suffixIcon: GestureDetector(
          child: Offstage(
            child: Icon(Icons.clear),
            offstage: _password == '',
          ),
          onTap: () {
            this.setState(() {
              _password = '';
              _passwordController.clear();
            });
          },
        ),
        border: InputBorder.none,
      ),
      onChanged: (value) {
        this.setState(() {
          _password = value;
        });
      },
    );
  }

However, the actual reason for the difference is the difference between member attributes. If we continue to use member attributes, this problem cannot be solved. At this time, we can consider putting the whole form into an Map, configuring differentiated attributes corresponding to different fields in Map, and then we can achieve a general interface. We can define the encapsulated component interface:


Widget _getInputTextFieldNew(
    String formKey,
    String value, {
    TextInputType keyboardType = TextInputType.text,
    FocusNode focusNode,
    controller: TextEditingController,
    onChanged: Function,
    String hintText,
    IconData prefixIcon,
    onClear: Function,
    bool obscureText = false,
    height = 50.0,
    margin = 10.0,
  }) {
  //......
}

The new parameters are as follows:

formKey: Indicates which key of the form Map the text box corresponds to; value: The value of the current form, which controls whether the clear button is displayed onClear: Defines the behavioral response of the right-hand clear button onChanged: Input Ratio Callback

Code implementation

The extracted code has nothing to do with the business page, so it is necessary to extract the code and add an components directory and an form_util. dart file under the lib directory for storing general form components. The implemented code is as follows:


class FormUtil {
  static Widget textField(
    String formKey,
    String value, {
    TextInputType keyboardType = TextInputType.text,
    FocusNode focusNode,
    controller: TextEditingController,
    onChanged: Function,
    String hintText,
    IconData prefixIcon,
    onClear: Function,
    bool obscureText = false,
    height = 50.0,
    margin = 10.0,
  }) {
    return Container(
      height: height,
      margin: EdgeInsets.all(margin),
      child: Column(
        children: [
          TextField(
              keyboardType: keyboardType,
              focusNode: focusNode,
              obscureText: obscureText,
              controller: controller,
              decoration: InputDecoration(
                hintText: hintText,
                icon: Icon(
                  prefixIcon,
                  size: 20.0,
                ),
                border: InputBorder.none,
                suffixIcon: GestureDetector(
                  child: Offstage(
                    child: Icon(Icons.clear),
                    offstage: value == null || value == '',
                  ),
                  onTap: () {
                    onClear(formKey);
                  },
                ),
              ),
              onChanged: (value) {
                onChanged(formKey, value);
              }),
          Divider(
            height: 1.0,
            color: Colors.grey[400],
          ),
        ],
      ),
    );
  }
}

Component usage

The first step is to define the form content of the current page using Map to control how different form fields are rendered.


Map<String, Map<String, Object>> _formData = {
    'username': {
      'value': '',
      'controller': TextEditingController(),
      'obsecure': false,
    },
    'password': {
      'value': '',
      'controller': TextEditingController(),
      'obsecure': true,
    },
  };

Secondly, the text box onChanged and onClear methods that define unification 1 correspond to _ handleTextFieldChanged and _ handleClear. With the fields returned by formKey, you can update the contents corresponding to _ formData. Note here that the as usage is used to convert an Object to an TextEditingController. This conversion can be successful if the corresponding type of Object is TextEditingController, and the following clear () method can be executed normally. But if it is null, executing the clear () method directly at this time will report a null pointer exception. Therefore, a question mark is added after the conversion result, which indicates that the method after null will not be executed, so there will be no null pointer exception. This is the null safety feature introduced by Flutter 2.0. In fact, this special effect has been applied in PHP 7 and Swift languages for a long time.


_handleTextFieldChanged(String formKey, String value) {
    this.setState(() {
      _formData[formKey]['value'] = value;
    });
  }

  _handleClear(String formKey) {
    this.setState(() {
      _formData[formKey]['value'] = '';
      (_formData[formKey]['controller'] as TextEditingController)?.clear();
    });
  }

Then use the encapsulated text box directly using the FormUtil. textField method where textField is used:


//...
FormUtil.textField(
    'username',
    _formData['username']['value'],
    controller: _formData['username']['controller'],
    hintText: ' Please enter your mobile phone number ',
    prefixIcon: Icons.mobile_friendly,
    onChanged: _handleTextFieldChanged,
    onClear: _handleClear,
  ),
FormUtil.textField(
    'password',
    _formData['password']['value'],
    controller: _formData['password']['controller'],
    obscureText: true,
    hintText: ' Please enter your password ',
    prefixIcon: Icons.lock_open,
    onChanged: _handleTextFieldChanged,
    onClear: _handleClear,
),
//...

As you can see, the username and password form fields reuse _ handleTextFieldChanged and _ handleClear. The whole code length is also reduced by nearly 50%, and the encapsulated FormUtil. textField text box can also be used for other form pages, so the maintainability and reusability of the whole code are greatly improved compared with the previous one.

Pit-stepping record

When encapsulating the text box, the onClear function is directly copied to the onTap attribute of GesureDetector, and every input will trigger onClear to automatically empty the text box content. Later, it was found that the actual callback function should be passed. The difference between the two methods is listed below:


//...
// The wrong way 
onTap:onClear,
//...

//...
// The right way 
onTap:() {
  onClear(formKey);
},
//...

Summarize

Encapsulating UI components is very common in the actual development process. Generally speaking, when we see the design draft, we first split the design draft into multiple components, and then consider whether some of them will be used in other occasions. If it is possible to use it, you can consider encapsulation. When encapsulating, consider the external interface parameters first, then pay attention to the fact that UI components should not involve business, and then make it as simple as possible (for example, there are 1 default values, and the required parameters are reduced). Of course, if the company can decide one set of components from product, design and development at the beginning of 1, packaging in advance will make the later development more efficient, but it depends on the urgency of the project time, which can be considered if there is plenty of time.

The above is how Flutter encapsulates the text input box in detail, more information about Flutter encapsulates the text input box please pay attention to other related articles on this site!


Related articles: