Flutter - Criando formulários com validação


Hoje veremos como criar formulários com validação em aplicações Flutter.

O Flutter é um SDK de aplicativo móvel do Google que ajuda a criar aplicativos móveis modernos para iOS e Android usando uma única (quase) base de código.

Se você não conhece o Flutter veja o meu artigo :  Flutter - Primeiros contatos e impressões

Criando formulário com validação

O Flutter permite criar formulários com validação usando o widget Form que atua como um contêiner para agrupar e validar múltiplos widgets usados para entrada de dados como TextField.

Cada campo de formulário individual deve ser agrupado em um widget FormField, com o widget Form como um ancestral comum a todos eles.

Podemos chamar os FormState para salvar, redefinir ou validar cada FormField que é um descendente deste formulário. Para obter o FormState, podemos usar Form.of com um contexto cujo ancestral é o Form, ou passar um GlobalKey para o construtor Form e chamar GlobalKey.currentState.

Assim podemos definir um roteiro básico para criar e validar formulários:

  1. Criar um Form com um GlobalKey;
  2. Adicionar um TextFormField com lógica de validação;
  3. Criar um button para validar e submeter o formulário;

Vejamos o que fazer em cada etapa.

1 - Criar um Form com um GlobalKey

Primeiro, precisamos de um formulário para trabalhar. O Widget Form atua como um contêiner para agrupar e validar vários campos de formulário.

Quando criamos o formulário, também precisamos fornecer uma GlobalKey. Isso identificará de forma exclusiva o formulário com o qual estamos trabalhando e nos permitirá validar o formulário posteriormente.

No trecho de código a seguir mostra como criar um formulário usando o widget Form usando um GlobalKey:

// Define  um widget Form customizado
class MeuForm extends StatefulWidget {
  @override
  MeuFormState createState() {
    return MeuFormState();
  }
}

// Define a classe State que vai tratar os dados do Form
class MeuFormState extends State<MeuForm> {
  final _formKey = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context) {
    // Cria o widget Form usando  _formKey
    return Form(
      key: _formKey,
      child: // vamos definir o código aqui depois...
    );
  }
}

Nota: Para formulários mais complexos podemos usar Form.of ao invés de Globalkey.

A classe GlobalKey fornece uma chave global única em todo o aplicativo permitindo identificar de forma exclusiva os elmentos.

2 - Adicionar um TextFormField com lógica de validação

Após criar o formulário usando Form temos que definir um widget para os usuários entrarem com os dados. Para isso vamos usar o widget TextFormField que renderiza uma entrada de texto do material design e sabe como exibir erros de validação quando eles ocorrem.

E a validação como fazer ?

Para validar vamos fornecer uma função validadora ao TextFormField. Se houver um erro com as informações fornecidas pelo usuário, a função do validador deverá retornar uma String contendo uma mensagem de erro. Se não houver erros, a função não deve retornar nada.

No exemplo que iremos criar, vamos definir um validador que garanta que o TextFormField não esteja vazio. Se estiver vazio, retornaremos uma mensagem de erro amigável.

A seguir um trecho de código mostrando como definir o validador:

TextFormField(
  // O validador recebe o texto digitado
  validator: (value) {
    if (value.isEmpty) {
      return 'Informe o valor';
    }
  },
);

A classe TextFormField é um FormField que contém um TextField.

A propriedade validator é um método opcional que valida uma entrada e retorna uma string de erro se ela for inválida ou nula.

Em outro exemplo de validação para um campo Nome que deve conter mais de 2 caracteres:

new TextFormField(
  decoration: const InputDecoration(labelText: 'Nome'),
  keyboardType: TextInputType.text,
  validator: (String arg) {
    if(arg.length < 3)
      return 'O Nome deve ter mais de 2 caracteres';
    else
      return null;
  },
  onSaved: (String val) {
    _name = val;
  },
),

Aqui validamos o argumento verificando se o tamanho for menor que 3.

A propriedade onSaved é opcional que pode ser chamado para concluir o processamento formulário.    

3 - Criar um button para validar/submeter o formulário

Após definir o formulário e os campos de texto, vamos fornecer um botão para que o usuário possa enviar o formulário. Neste momento temos que verificar se o formulário é válido, e, se for mostramos uma mensagem de sucesso, caso contrário exibimo uma mensagem de erro.

Um exemplo de código que implementa essa funcionalidade é visto abaixo:

RaisedButton(
  onPressed: () {
    // Validate vai retornar true se o formulário for válido
    // e false se não for.
    if (_formKey.currentState.validate()) {
      // Se o form for válido exibe um a snackbar
      Scaffold
          .of(context)
          .showSnackBar(SnackBar(content: Text('Dados processados com sucesso')));
    }
  },
  child: Text('Submeter'),
);

Para validar o formulário, precisamos usar o _formKey criado na etapa 1. Podemos usar o método _formKey.currentState para acessar o FormState, que é criado automaticamente pelo Flutter quando criamos um formulário.

A classe FormState contém o método validate, que quando for chamado, executará a função do validador para cada campo de texto no formulário. Se tudo estiver ok retorna true, caso contrário retorna false e uma mensagem de erro.

Criando o projeto Flutter

Vamos começar criando o projeto flutter.

Eu estou usando o Flutter versão 1.5.4, e como editor de código estou usando o VS Code com o plugin Flutter instalado.

No Visual Studio Code tecle CTRL+ SHIFT+P para abrir a paleta de comandos e a seguir selecione a opção : Fluter:New Project

A seguir informe o nome do projeto : flutter_form1 e tecle ENTER.

Na janela de diálogo a seguir, selecione a pasta onde o projeto vai ser salvo e clique em :
Select a folder to create the project in

O Flutter vai criar um projeto padrão onde todo o código da aplicação vai estar no arquivo main.dart dentro da pasta lib do projeto.

A seguir podemos abrir o projeto usando o VS Code usando a opção Open folder e escolhendo a pasta flutter_form1.

Definindo o layout

O leiaute do nosso aplicativo vai ser bem simples

- Temos um Scaffold

- 1 AppBar com o texto : 'Formulário com validação'

- Temos um Form contido em um Container

- 3 TextField : Nome, Celular e Email

- 1 RaisedButton com o texto 'Enviar'

A seguir abra o arquivo main.dart na pasta lib e remova todo o código gerado incluindo o código abaixo:

import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
  GlobalKey<FormState> _key = new GlobalKey();
  bool _validate = false;
  String nome, email, celular;
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text('Formulário com Validação'),
        ),
        body: new SingleChildScrollView(
          child: new Container(
            margin: new EdgeInsets.all(15.0),
            child: new Form(
              key: _key,
              autovalidate: _validate,
              child: _formUI(),
            ),
          ),
        ),
      ),
    );
  }
 

O destaque do código fica por conta do uso do autovalidate que é usado para validar a entrada assim que inserimos os dados.

Inicialmente ele foi configurado como false, pois quando o usuário abrir o formulário, todos os campos estarão, por padrão, vazios e um campo vazio será inválido. Então, quando o usuário abrir o formulário, não queremos mostrar um erro inválido.

A seguir vamos definir o método privado _formUI() que é filho do Form :

Widget _formUI() {
    return new Column(
      children: <Widget>[
        new TextFormField(
          decoration: new InputDecoration(hintText: 'Nome Completo'),
          maxLength: 40,
          validator: _validarNome,
          onSaved: (String val) {
            nome = val;
          },
        ),
        new TextFormField(
            decoration: new InputDecoration(hintText: 'Celular'),
            keyboardType: TextInputType.phone,
            maxLength: 10,
            validator: _validarCelular,
            onSaved: (String val) {
              celular = val;
            }),
        new TextFormField(
            decoration: new InputDecoration(hintText: 'Email'),
            keyboardType: TextInputType.emailAddress,
            maxLength: 40,
            validator: _validarEmail,
            onSaved: (String val) {
              email = val;
            }),
        new SizedBox(height: 15.0),
        new RaisedButton(
          onPressed: _sendForm,
          child: new Text('Enviar'),
        )
      ],
    );
  }

Neste código empilhamos os campos de formulário TextFormField e o RaisedButton usando o widget Column.

Em cada TextFormField definimos :

Na propriedade validator definimos os método para realizar a validação:

E no callback onPressed do RaisedButton invocamos o método _sendForm.

A seguir temos o código desses métodos:

 String _validarNome(String value) {
    String patttern = r'(^[a-zA-Z ]*$)';
    RegExp regExp = new RegExp(patttern);
    if (value.length == 0) {
      return "Informe o nome";
    } else if (!regExp.hasMatch(value)) {
      return "O nome deve conter caracteres de a-z ou A-Z";
    }
    return null;
  }
  String _validarCelular(String value) {
    String patttern = r'(^[0-9]*$)';
    RegExp regExp = new RegExp(patttern);
    if (value.length == 0) {
      return "Informe o celular";
    } else if(value.length != 10){
      return "O celular deve ter 10 dígitos";
    }else if (!regExp.hasMatch(value)) {
      return "O número do celular so deve conter dígitos";
    }
    return null;
  }
  String _validarEmail(String value) {
    String pattern = r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@
((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
    RegExp regExp = new RegExp(pattern);
    if (value.length == 0) {
      return "Informe o Email";
    } else if(!regExp.hasMatch(value)){
      return "Email inválido";
    }else {
      return null;
    }
  }
  _sendForm() {
    if (_key.currentState.validate()) {
      // Sem erros na validação
      _key.currentState.save();
      print("Nome $nome");
      print("Ceclular $celular");
      print("Email $email");
    } else {
      // erro de validação
      setState(() {
        _validate = true;
      });
    }
  }
}

No código acima, cada método faz a validação do respectivo campo usando a classe RegExp que define expressões regulares.

As expressões regulares usadas no Dart têm a mesma sintaxe e semântica que as expressões regulares de JavaScript.

E no método _sendForm() temos a linha de código :  _key.currentState.save();

Que irá chamar todo o método correspondente ao onSave de InputFormField.

Em cada método correspondente, atribuímos o valor do campo de entrada à variável correspondente.

Executando o projeto pressinando F5 iremos obter:

Pegue o arquivo main.dart aqui: main_dart_Form.zip

(disse Jesus)"Aquele que tem os meus mandamentos e os guarda esse é o que me ama; e aquele que me ama será amado de meu Pai, e eu o amarei, e me manifestarei a ele."
João 14:21

Veja os Destaques e novidades do SUPER DVD Visual Basic (sempre atualizado) : clique e confira !

Quer migrar para o VB .NET ?

Quer aprender C# ??

Quer aprender os conceitos da Programação Orientada a objetos ?

Quer aprender o gerar relatórios com o ReportViewer no VS 2013 ?

Referências:


José Carlos Macoratti