Flutter: como criar um formulário
Estou desenvolvendo um app para um mercado em Flutter e o cliente precisa de uma funcionalidade para cadastrar produtos. Vamos criar um formulário com diversos campos usando o Dart e todo o potencial do Flutter.
Preparando o ambiente do Flutter
Se você chegou neste artigo, muito provavelmente você já deu o seu primeiro passo tem o ambiente do Flutter configurado.
Caso seja a sua primeira vez, não se preocupe! Você pode acessar a página oficial, baixar e instalar o Flutter de acordo com o seu sistema operacional. Há também um artigo de Hello World com Flutter na Alura.
Em seguida, crie o projeto mercado, configure o dispositivo para testar (emulador ou celular) e abra o seu editor de código favorito com o plugin do Flutter.
Se não tem a mínima ideia de qual ferramenta utilizar ou criar o projeto com o Flutter, dê uma olhada neste Alura+
Tela do formulário
Para criar um formulário, primeiro precisamos preparar a estrutura inicial. Ao criar um projeto com o Flutter ele fornece uma amostra de código bastante grande no arquivo main.dart que não é necessária para o nosso exemplo.
Sendo assim, remova essa estrutura e deixe esse arquivo da seguinte maneira:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Cadastrando produto'),
),
),
);
}
}
Com essa amostra de código, temos uma estrutura básica de um app que utiliza os componentes do Material Design incluíndo um app bar. Rodando o código chegamos a esse resultado:
Com a tela inicial pronta, podemos começar a implementar o layout.
Adicionando editor de texto
Para criar um formulário, podemos usar diversos widgets do catálogo do Flutter, porém, se pensarmos que o nosso cliente precisa cadastrar produtos, muito provavelmente ele vai precisar adicionar um nome, quantidade ou valor.
Portanto, podemos começar adicionando um campo de texto! Uma das opções comuns no Flutter para um editor, é o TextField
:
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Cadastrando produto'),
),
body: Column(
children: <Widget>[
TextField(),
],
),
),
);
}
Note que agora somos capazes de preencher o primeiro campo com o nome do produto! Sendo assim, o próximo passo é adicionar os demais campos para a quantidade e valor:
body: Column(
children: <Widget>[
TextField(),
TextField(),
TextField(),
],
),
Então precisamos adicionar um botão para criar o produto. Dentre as possibilidades, o mais comum para esse tipo de tela é o RaisedButton
:
body: Column(
children: <Widget>[
TextField(),
TextField(),
TextField(),
RaisedButton(
child: Text('Cadastrar'),
onPressed: () {},
)
],
),
Observe que o onPressed
foi implementado, esse código é necessário tanto para habilitar o clique no botão, como permitir a execução de uma ação.
Pronto! Temos a configuração básica do formulário e podemos seguir com o próximo passo.
Implementando o modelo do produto
Com todos os componentes visuais apresentados, precisamos criar um produto ao preencher os campos e clicar em Cadastrar. Para isso podemos criar um modelo que representa o nosso produto:
class Produto {
final String nome;
final int quantidade;
final double valor;
Produto(
this.nome,
this.quantidade,
this.valor,
);
}
Você pode adicionar esse código no
main.dart
, porém, a boa prática é criar um arquivo exclusivo em um diretório que identifique essa classe como um modelo.
Com o modelo pronto, precisamos criar um objeto do tipo Produto
com base nas informações preenchidas nos campos.
Pegando informações do TextField
Para pegar as informações dos campos, precisamos usar um controlador de edição de texto para cada um deles. No Flutter temos acesso ao TextEdittingController
:
class MyApp extends StatelessWidget {
final TextEditingController _controladorNome = TextEditingController();
final TextEditingController _controladorQuantidade = TextEditingController();
final TextEditingController _controladorValor = TextEditingController();
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Cadastrando produto'),
),
body: Column(
children: <Widget>[
TextField( controller: _controladorNome,),
TextField( controller: _controladorQuantidade,),
TextField( controller: _controladorValor,),
RaisedButton(
child: Text('Cadastrar'),
onPressed: () {},
)
],
),
),
);
}
}
Agora que cada campo tem o seu controlador, precisamos pegar o valor de cada um deles ao clicar em Cadastrar, podemos implementar esse código dentro do escopo do onPressed
:
RaisedButton(
child: Text('Cadastrar'),
onPressed: () {
final String nome = _controladorNome.text;
final int quantidade = int.tryParse(_controladorQuantidade.text);
final double valor = double.tryParse(_controladorValor.text);
},
)
Observe que utilizamos o atributo text
para pegar o valor do controlador, porém, considerando que a variável quantidade
e valor
são diferentes de String
, é necessário converter o valor, por isso utilizamos o tryParse()
de cada uma das classes.
Criando o produto ao clicar no botão
Com os valores disponíveis, podemos criar o produto e fazer um print para verificar o que é apresentado:
RaisedButton(
child: Text('Cadastrar'),
onPressed: () {
final String nome = _controladorNome.text;
final int quantidade = int.tryParse(_controladorQuantidade.text);
final double valor = double.tryParse(_controladorValor.text);
final Produto produtoNovo = Produto(nome, quantidade, valor);
print(produtoNovo);
},
)
Então podemos implementar um toString()
na classe Produto
para testar:
class Produto {
final String nome;
final int quantidade;
final double valor;
Produto(
this.nome,
this.quantidade,
this.valor,
);
@override
String toString() {
return 'Produto{nome: $nome, quantidade: $quantidade, valor: $valor}';
}
}
Então podemos testar considerando um refrigerante com 100 quantidades no valor de 8 reais:
E temos o seguinte resultado via console:
Produto{nome: refrigerante, quantidade: 100, valor: 8.0}
Por mais que o formulário consiga criar o produto, existem alguns detalhes visuais que podem ser melhorados!
Espaçamento nos widgets
Dentre os ajustes visuais, uma das abordagens comuns é adicionar um espaço entre os componentes do formulário e a tela.
Para isso, podemos considerar um padding em todos os cantos do Column
que compõe todos os componentes do formulário:
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column( \\ componentes do formulário ),
),
),
Então podemos aplicar o padding para cada campo especificando os cantos, nesse caso apenas o topo:
child: Column(
children: <Widget>[
TextField( controller: _controladorNome,),
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: TextField( controller: _controladorQuantidade,),
),
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: TextField( controller: _controladorValor,),
),
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: RaisedButton( ... ),
),
],
),
Apenas com essa mudança já temos uma apresentação bem diferente!
Labels no TextField
Além disso, podemos também melhorar a experiência do nosso usuário! Como por exemplo, identificando cada um dos campos. Para isso, podemos utilizar a propriedade decoration
enviando um InputDecoration
:
TextField(
controller: _controladorNome,
decoration: InputDecoration(),
),
A partir do InputDecoration
, podemos usar a propriedade labelText
para identificar cada um dos campos:
Agora é nítido saber o que cada campo representa!
Ajustando o teclado
Por fim, podemos também melhorar a experiência do usuário configurando um teclado específico para o campo, por exemplo, no produto faz sentido considerando letras, números e outros caracteres, mas para a quantidade e valor, faz mais sentido usar um teclado numérico.
Para isso podemos usar a propriedade keyboardType
que recebe um TextInputType
, essa referência já possui algumas constantes com implementações comuns de teclados:
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: TextField(
controller: _controladorQuantidade,
decoration: InputDecoration(labelText: 'Quantidade'),
keyboardType: TextInputType.number,
),
),
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: TextField(
controller: _controladorValor,
decoration: InputDecoration(labelText: 'Valor'),
keyboardType: TextInputType.number,
),
),
Agora conseguimos implementar o nosso formulário com o Flutter!
Para saber mais de Flutter
Durante a implementação do formulário consideramos o uso do StatelessWidget
, porém existem alguns problemas e bugs neste tipo de solução que exige o uso do StatefulWidget
que vemos no curso de fundamentos de Flutter.
Caso tenha interesse em consultar o código final do artigo, fique à vontade e confira o repositório do GitHub.