Search code examples
flutterdartflutter-form-builder

Validate a URL, without requiring it, using a Regular Expression in Flutter


I'm creating a form in Flutter and want the following functionality:

  1. If the user enters a URL, I want to ensure it is valid with a regular expression.
  2. If the user leaves the field blank, I do not want to return an error message.

The following Reg Exp validator performs this way when I perform these four steps:

  • If I hot reload and enter a valid URL, it accepts it.
  • If I change the input field to a new non-valid URL, it still accepts it.
  • If I hot reload and enter an invalid URL, it does not accept it.
  • If I change the URL to a valid URL, it still does not accept it.

It's as if it only runs the validator once and then any subsequent entry is not checked again. I do have a field that uses value.isEmpty as a validator and it does work as expected by checking the input each time I click my button with _formKey.currentState.save();

child: TextFormField(
              decoration: InputDecoration(
                border: OutlineInputBorder(),
                labelText: 'Related URL',
              ),
              textInputAction: TextInputAction.next,
              keyboardType: TextInputType.url,
              focusNode: _relatedUrlFocusNode,
              onFieldSubmitted: (_) {
                FocusScope.of(context).requestFocus(_notesFocusNode);
              },
              //this doesn't work
              validator: (String value) {
                if (RegExp(r"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")
                        .hasMatch(value)) {
                  return 'Please enter a valid URL';
                }
                return null;
              },
              onSaved: (String value) {
                relatedUrl = value;
              },
            ),

...other fields...

ElevatedButton(
            child: Text('ADD & ACTIVATE'),
            onPressed: () {
              if (!_formKey.currentState.validate()) {
                return;
              }
              ScaffoldMessenger.of(context)
                  .showSnackBar(SnackBar(content: Text('Processing Data')));
              _formKey.currentState.save();
            },
          )

How do I ensure it performs the validator each time I click submit?


Solution

  • You have two problems with your code:

    1. Your check should be if (!RegExp(r"...").hasMatch(value)) {} instead of if (RegExp(r"...").hasMatch(value)) {}
    2. Your Regular Expression is not correct. You may check here. (Dart RegExp are the same as Javascript RegExp)

    Solution with validators package

    Instead of a RegExp, you can use the validators package.

    enter image description here

    Full source code for easy copy-paste:

    import 'package:flutter/material.dart';
    import 'package:flutter_hooks/flutter_hooks.dart';
    import 'package:validators/validators.dart';
    
    void main() {
      runApp(
        MaterialApp(
          debugShowCheckedModeBanner: false,
          title: 'URL Validation Demo',
          home: HomePage(),
        ),
      );
    }
    
    class HomePage extends HookWidget {
      @override
      Widget build(BuildContext context) {
        final _formKey = useState(GlobalKey<FormState>());
        final _focusNode = useFocusNode();
        return Scaffold(
          body: Container(
            padding: EdgeInsets.all(8.0),
            alignment: Alignment.center,
            child: Form(
              key: _formKey.value,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  TextFormField(
                    decoration: InputDecoration(
                      border: OutlineInputBorder(),
                      labelText: 'URL',
                    ),
                    validator: (value) {
                      if (!isURL(value)) {
                        return 'Please enter a valid URL';
                      }
                      return null;
                    },
                  ),
                  const SizedBox(height: 24.0),
                  ElevatedButton(
                    child: Text('VALIDATE'),
                    onPressed: () {
                      if (_formKey.value.currentState.validate()) {
                        ScaffoldMessenger.of(context).showSnackBar(
                            SnackBar(content: Text('Processing Data')));
                      }
                    },
                  ),
                ],
              ),
            ),
          ),
        );
      }
    }