Creating Custom Input Formatters for Specific Data Requirements in Flutter

When building Flutter applications, you often need to ensure that user input adheres to a specific format. Flutter provides the TextInputFormatter class for this purpose. Custom input formatters allow you to define rules that validate and transform text as users type, ensuring data consistency and validity. This post will guide you through creating custom input formatters for specific data requirements in Flutter.

What is a TextInputFormatter in Flutter?

TextInputFormatter is an abstract class that you can extend to control the input of text fields in Flutter. It intercepts text changes and allows you to modify or reject them based on predefined rules. Using custom formatters, you can enforce rules such as:

  • Limiting the characters allowed (e.g., only numbers or letters).
  • Formatting the text (e.g., adding separators for phone numbers or credit card numbers).
  • Validating the input (e.g., ensuring the email address is in the correct format).

Why Use Custom Input Formatters?

  • Data Consistency: Ensure data entered by users meets your application’s requirements.
  • Improved UX: Provide real-time feedback and prevent invalid input, enhancing the user experience.
  • Reduced Server-Side Validation: Reduce the need for extensive validation on the server side.

How to Create Custom Input Formatters

To create a custom input formatter in Flutter, follow these steps:

Step 1: Extend TextInputFormatter

Create a new class that extends TextInputFormatter and overrides the formatEditUpdate method. This method is called whenever the text field’s value changes.

import 'package:flutter/services.dart';

class CustomFormatter extends TextInputFormatter {
  @override
  TextEditingValue formatEditUpdate(
      TextEditingValue oldValue, TextEditingValue newValue) {
    // Implementation goes here
    return newValue;
  }
}

Step 2: Implement the Formatting Logic

Inside the formatEditUpdate method, implement the logic to transform or validate the text.

Here are some common examples:

Example 1: Uppercase Input Formatter

This formatter converts all input to uppercase:

class UppercaseFormatter extends TextInputFormatter {
  @override
  TextEditingValue formatEditUpdate(
      TextEditingValue oldValue, TextEditingValue newValue) {
    return TextEditingValue(
      text: newValue.text.toUpperCase(),
      selection: newValue.selection,
    );
  }
}
Example 2: Digits Only Input Formatter

This formatter allows only digits to be entered:

class DigitsOnlyFormatter extends TextInputFormatter {
  @override
  TextEditingValue formatEditUpdate(
      TextEditingValue oldValue, TextEditingValue newValue) {
    final regEx = RegExp(r'^\d+$');
    if (regEx.hasMatch(newValue.text) || newValue.text.isEmpty) {
      return newValue;
    }
    return oldValue;
  }
}
Example 3: Phone Number Formatter

This formatter formats the input as a phone number (e.g., (XXX) XXX-XXXX):

import 'package:flutter/services.dart';

class PhoneNumberFormatter extends TextInputFormatter {
  @override
  TextEditingValue formatEditUpdate(
    TextEditingValue oldValue,
    TextEditingValue newValue,
  ) {
    var text = newValue.text;
    if (newValue.selection.baseOffset == 0) {
      return newValue;
    }

    var buffer = StringBuffer();
    for (int i = 0; i < text.length; i++) {
      buffer.write(text[i]);
      var nonZeroIndex = i + 1;
      if (nonZeroIndex % 3 == 0 && nonZeroIndex != text.length && nonZeroIndex < 6) {
        buffer.write(
            ''); // Add a space after every 3 digits until the 6th digit
      }
      if (nonZeroIndex == 6) {
        buffer.write('-'); // Add a dash after the 6th digit
      }
    }

    var string = buffer.toString();
    return TextEditingValue(
      text: string,
      selection: TextSelection.collapsed(
        offset: string.length,
      ),
    );
  }
}

Step 3: Use the Custom Formatter in a TextField

To use the custom formatter, provide it to the inputFormatters property of a TextField:

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Custom Input Formatters'),
        ),
        body: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            children: [
              TextField(
                decoration: InputDecoration(labelText: 'Uppercase Input'),
                inputFormatters: [UppercaseFormatter()],
              ),
              SizedBox(height: 20),
              TextField(
                decoration: InputDecoration(labelText: 'Digits Only'),
                keyboardType: TextInputType.number,
                inputFormatters: [DigitsOnlyFormatter()],
              ),
              SizedBox(height: 20),
              TextField(
                decoration: InputDecoration(labelText: 'Phone Number'),
                keyboardType: TextInputType.number,
                inputFormatters: [PhoneNumberFormatter()],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Complete Example: Combining Multiple Formatters

You can also combine multiple formatters. For example, to create a field that accepts only uppercase letters and numbers:

class UppercaseLettersAndDigitsFormatter extends TextInputFormatter {
  @override
  TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
    final regEx = RegExp(r'^[A-Z0-9]+$');
    final uppercaseText = newValue.text.toUpperCase();

    if (regEx.hasMatch(uppercaseText) || uppercaseText.isEmpty) {
      return TextEditingValue(
        text: uppercaseText,
        selection: newValue.selection,
      );
    }

    return oldValue;
  }
}

Use it in your TextField:

TextField(
  decoration: InputDecoration(labelText: 'Uppercase Letters and Digits Only'),
  inputFormatters: [UppercaseLettersAndDigitsFormatter()],
)

Best Practices

  • Keep It Simple: Ensure the logic within the formatter is straightforward and efficient.
  • Handle Edge Cases: Consider edge cases like pasting text or using assistive technologies.
  • Use Regular Expressions: Regular expressions can simplify complex validation logic.
  • Test Thoroughly: Always test your formatters with various inputs to ensure they work correctly.

Conclusion

Custom input formatters in Flutter are powerful tools for enforcing data consistency and improving user experience. By extending TextInputFormatter and implementing your validation and transformation logic, you can ensure that the data entered by users meets your application's specific requirements. Whether it's formatting phone numbers, allowing only certain characters, or converting input to a specific case, custom formatters are essential for robust and user-friendly Flutter applications.