Alura > Cursos de Mobile > Cursos de Flutter > Conteúdos de Flutter > Primeiras aulas do curso Flutter: Controller, navegação e estados

Flutter: Controller, navegação e estados

Construindo formulários - Apresentação

Boas-vindas ao novo curso de Flutter. Neste curso, vamos dar continuidade ao nosso projeto de tarefas gamificadas.

Audiodescrição: Caio Couto é uma pessoa de pele clara e olhos verdes. Tem cabelo encaracolado castanho-escuro e barba cheia. Está com uma camisa de tie-dye branca, azul e roxa e um colar com pingente de olho. Ao fundo, quarto com iluminação rosa.

Antes de discutir os temas que abordaremos, queremos te mostrar o aplicativo.

Conhecendo o aplicativo

O aplicativo com as tarefas que construímos no último curso agora tem uma nova funcionalidade! Podemos adicionar uma nova tarefa.

Tela do aplicativo de tarefas. No topo da tela temos uma barra azul-claro escrito 'Tarefas'. Em seguida, temos uma lista com vários cartões que contém uma pequena imagem à direita, seguido do nome da tarefa, cinco estrelas a serem preenchidas e um botão de 'Up'. As estrelas vazias são em cinza-claro e as estrelas preenchidas são em azul-claro. Na parte inferior de cada cartão há uma faixa azul com uma barra de progresso branca no lado esquerdo e, no lado direito, o nível da tarefa. Por fim, no canto inferior direito da tela, um botão circular com um ícone de '+'.

Clicando no ícone de "+" no canto inferior direito, vamos adicionar uma tarefa chamada "Derrotar o Dash Vader", com dificuldade 5. Já temos uma imagem do Dash, o mascote do Flutter, vestido de Darth Vader.

Ao clicar em "Adicionar", o aplicativo informa que está criando uma nova tarefa, que aparecerá em nossa lista de tarefas. Podemos interagir com ela da mesma forma que com as outras tarefas. Por exemplo, apertando o botão de "Up" para subir seu nível.

O que vamos aprender?

Para implementar tudo isso, precisamos aprender alguns novos conceitos, como os de formulário, descobrindo como funciona e o que é um TextFormField, TextField e Controller.

Também vamos abordar o errorBuilder da imagem, para evitar que a imagem fique estranha se não carregar corretamente. Em seguida, vamos discutir validações de formulário e diferenças de teclado para cada tipo de formulário.

Começaremos a explorar navegações, entendendo o que é uma navegação no Flutter e o que significa navegações e rotas. Vamos aprender sobre os tipos de navegações e qual vamos usar.

Depois, teremos uma conversa sobre widgets para entender como eles trocam informações entre si. Vamos começar a explorar o mundo dos estados, entendendo o que é um estado e como podemos controlá-lo.

Por fim, finalizaremos nosso projeto falando um pouco sobre bugs e como lidar com eles. Depois de tudo isso, teremos nosso aplicativo completo.

Estão animados e animadas? Porque estamos muito animados para dar essa aula para vocês! Vamos lá?

Construindo formulários - Como será nosso formulário?

Você já assistiu à apresentação deste curso e já sabe como as coisas vão funcionar, certo? Ótimo! Mas precisamos ter mais detalhes sobre o que vamos construir.

Primeiro, vamos começar com o que já sabemos fazer. Vamos conferir a tela de formulário, que é a nova tela que precisamos criar no nosso projeto.

Explorando o layout do formulário

A tela de formulário tem um scaffold, ou seja, uma estrutura que abrange toda a tela. Já sabemos como funciona o scaffold, pois já usamos esse widget.

No topo, temos uma app bar na cor azul, que é uma barra superior com o texto "Nova Tarefa". Isso nós já sabemos fazer. Tem uma seta de voltar com a qual nunca mexemos antes, mas não precisa se preocupar com ela agora.

Abaixo do app bar, temos o body do scaffold que é um contêiner branco e, dentro dele, parece que tem um contêiner cinza que tem uma borda preta. Nós nunca trabalhamos com borda pintadas. Será uma novidade.

Esse contêiner cinza possui alguns elementos dentro dele, um abaixo do outro. Temos o elemento nome, dificuldade e imagem. Se mencionamos a palavra "abaixo", isso significa que está tudo dentro de um column.

Então, temos um column com os filhos nome, dificuldade, imagem seguido de uma contêiner azul com a borda arredondada com uma imagem dentro, o qual também sabemos fazer. É uma imagem de uma câmera riscada, ou seja, indicando que não há fotos.

Abaixo dessa imagem, temos o botão "Adicionar". Também já sabemos fazer um botão. Ou seja, já sabemos fazer essa tela.

O que não sabemos é o que são esses elementos de nome, dificuldade e imagem. Se clicamos neles, descobriremos que são campos de texto. Nunca trabalhamos com campo de texto. Então, uma coisa de cada vez, certo?

Quando clicamos em um campo de texto, ele abre o teclado para a pessoa usuária poder digitar. Enquanto no campo de nome aparece o teclado de letras, no campo de dificuldade, abre-se o teclado número. Na imagem, podemos acessar a área de transferência para colar uma URL. Com isso, ele carrega automaticamente uma imagem naquele contêiner azul. Incrível! Por fim, poderíamos clicar no botão "Adicionar" para adicionar a nossa tarefa.

A ideia é criar esse layout, o front-end do nosso projeto. Já entendemos que devemos fazer um scaffold, que terá um contêiner cinza com três elementos em coluna, uma parte de imagem e um botão. Vamos começar por aí?

Criando nova tela

Agora, vamos abrir o nosso projeto, que estará disponível para vocês baixarem, caso vocês não tenham feito o último curso.

Nesse projeto inicial, vamos criar uma nova tela. Primeiro, devemos criar um novo documento dentro da nossa pasta "screens".

Para isso, acessemos a aba "Project" à esquerda no Android Studio para entrar na pasta "lib". A pasta "lib" terá duas pastas: "components" e "screens". Vamos entrar na pasta "screens", clicar com o botão direito e selecionar a opção "New > Dart File".

Vamos chamar esse novo arquivo Dart de form_screen, pois será a tela de formulário. Nele, vamos adicionar um novo widget. Que widget vai ser esse? Criaremos um StatefulWidget, usando o atalho de escrita stful, chamado FormScreen.

Precisamos adicionar o nosso MaterialApp. Basta clicar em StatefulWidget, apertar "Alt + Enter" e importe material.dart.

form_screen.dart:

import 'package:flutter/material.dart';

class FormScreen extends StatefulWidget {
    const FormScreen({Key? key}) : super(key: key);

    @override
    State<FormScreen> createState() => _FormScreenState();
}

class FormScreen extends State<FormScreen> {
    @override
    Widget build(BuildContext context) {
        return Container();
    }
}

Agora que já temos o Material importado, já temos uma tela. Mas precisamos começar a visualizar como é que essa nossa tela está mudando.

Em função disso, vamos no main.dart, onde temos um home que é o InitialScreen. No curso anterior, criamos toda a tela inicial com as nossas tarefas. Mas, não queremos visualizá-la agora, queremos visualizar o FormScreen que estamos criando.

Por isso, vamos substituir esse InitialScreen() pelo FormScreen(). Cuidado que existem outros widgets com nomes parecido, como FormState e FormFieldState. Tem que ser o que acabamos de criar, FormScreen.

O Android Studio vai importar automaticamente essa tela do projeto e vai parar de usar o InitialScreen, portanto, sua importação vai ficar cinza. Mas, não vamos tirá-la agora porque mais tarde vamos voltar a usá-la.

main.dart:

import 'package:nosso_primeiro_projeto/screens/form_screen.dart';

// código omitido…

class MyApp extends StatelessWidget {
    const MyApp({Key? key}) : super(key: key);

    @override
    Widget build(BuildContext context) {
        return MaterialApp(
            title: 'Flutter Demo',
            theme: ThemeData(
                primarySwatch: Colors.blue,
            ),
            home: const FormScreen(),
        );
    }
}

Feito isso, podemos dar um Hot Reload para mudar a tela do emulador, clicando no botão de "Run" com ícone de play (ou atalho "Shift + F10") na barra de ferramenta.

A tela do emulador fica toda preta, porque só temos um StatefulWidget que retorna um Container(). Mais nada. Não é isso que queremos, certo? Devemos criar a nossa tela do jeito já conhecemos.

Estruturando Scaffold

Para isso, precisamos substituir o Container() por um Scaffold(). Esse Scaffold precisa do parâmetro appBar que terá o widget AppBar(). Lembrando que, dentre as sugestões da IDE, o widget tem um ícone amarelo com um sinal de igual.

Nesse AppBar(), podemos colocar um title, como já fizemos antes. Esse título será uma constante Text() com a string Nova Tarefa.

form_screen.dart:

class _FormScreenState extends State<FormScreen> {
    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
                title: const Text('Nova Tarefa'),
            ),
        );
    }
}

Vamos dar um Hot Reload clicando no botão de "Flutter Hot Reload" com ícone de raio (ou atalho "Ctrl + "). Já temos a barra superior azul de nova tarefa.

Construindo o body

Agora precisamos colocar os elementos no centro, no body. O body fica onde? Dentro do AppBar? Não. Ele fica dentro do Scaffold, como outro parâmetro.

Digitamos body que será um Container(). Em seguida, vamos definir uma cor para esse contêiner. Para isso, usamos color: Colors.black12, que é um cinza bem claro, quase branco.

E queremos que esse contêiner tenha uma altura (height) de 650. Após adicionar uma vírgula, também colocamos uma largura (width) de 375. Esses valores são parte do design que fizemos lá no passado.

Algumas vezes é preciso experimentar alguns valores, mas nesse caso já deixamos tudo pronto. Por isso, não vamos precisar chutar nenhum valor.

Dica: Use o atalho "CTRL + Alt + L" para indentar o código.

Criamos o contêiner. O que acontece se damos um Hot Reload? É criado um contêiner cinza-claro, que não encaixou na tela toda. Ele parece alinhado à esquerda. Se você quiser visualizá-lo de forma destacada, pode mudar a cor dele para black, por exemplo.

Não está como queríamos. Queremos que ele esteja no centro, ou seja, centralizado no body. Como podemos fazer isso?

Vamos usar um novo Width que envolverá o contêiner. Para isso, selecionamos Container(), usamos o atalho "Alt + Enter" e escolhemos a opção "Wrap with center". O Center é um widget que ainda não conhecemos, que centraliza o nosso widget dentro do parente dele.

class _FormScreenState extends State<FormScreen> {
    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
                title: const Text('Nova Tarefa'),
            ),
            body: Center (
                child: Container(
                    color: Colors.black12,
                    height: 650,
                    width: 375,
                ),
            ),
        );
    }
}

Ao dar um Hot Reload novamente, o nosso contêiner cinza está bem no centro da tela. Só que ainda não parece muito com o que queríamos. Planejamos ter cantos arredondados e uma borda. Isso significa que teremos que decorar o nosso contêiner. Já decoramos ele antes, não é mesmo?

Dentro do contêiner, adicionamos um decoration, usando um BoxDecoration(). Devemos tomar cuidado, pois o parâmetro color precisa estar dentro de BoxDecoration para evitar problemas. Então, vamos tirar o color de dentro do Container e vamos colocar no BoxDecoration.

Outra decoração que queremos colocar no BoxDecoration é a borda circular, que já aprendemos a fazer. Isto significa que precisamos ter um borderRadius que precisa de um BorderRadius.circular() com o tamanho 10.

Se damos um Hot Reload, a borda já estará mais circular. Mas não acabou aí. Ele tem também uma borda preta visível em volta. Como fazemos isso?

Acrescentaremos um novo parâmetro chamado border que precisa de um widget chamado Border.all(), ou seja, uma borda que englobe todo o nosso widget. E qual será o tamanho dessa borda? Vamos definir como width: 3.

Agora o nosso contêiner tem uma borda fina preta. A nova tela está começando a ficar bonita.

Além disso, tínhamos que definir que o nosso body era um column com vários filhos. E dentro desses filhos ia ter uns widgets que ainda nunca mexemos.

Para finalizar esse contêiner, vamos acrescentar um child depois da BoxDecoration, ainda dentro do Container. Esse filho vai ser um Column() que conterá os children: [] que serão os elementos dentro do contêiner cinza.

class _FormScreenState extends State<FormScreen> {

    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
                title: const Text('Nova Tarefa'),
            ),
            body: Center(
                child: Container(
                    height: 650,
                    width: 375,
                    decoration: BoxDecoration(
                        color: Colors.black12,
                        borderRadius: BorderRadius.circular(10),
                        border: Border.all(width: 3),
                    ),
                    child: Column(
                        children: []
                    ),
                ),
            ),
        );
    }
}

Vamos continuar a criar esses elementos no próximo vídeo.

Construindo formulários - TextField ou TextFormField?

Foi exatamente neste ponto que paramos. Criamos nosso scaffold com o app bar de nova tarefa, também montamos o body com um contêiner cinza com uma borda estilizada. Agora, precisamos começar a trabalhar com os elementos que receberão nossas informações.

Precisamos criar um novo elemento onde possamos clicar e digitar dentro dele, assim como em nosso formulário da aula anterior. Precisamos de um tipo de widget que seja como um texto, mas um campo de texto. E como encontramos um widget como esse?

Para isso, vamos à documentação do Flutter que pode nos ajudar.

Diferença entre TextField e TextFormField

Vamos acessar o site do Flutter, o flutter.dev. No topo à direita, clicaremos no menu que é um ícone com três linhas horizontais. Temos várias opções no menu, clicaremos na opção "Docs", que é a documentação. Entre as opções da documentação, vamos selecionar a opção chamada "Cookbook", que é o nosso livro de receitas do Flutter. Ele vai nos ajudar muito.

No cookbook, existem vários tópicos, como animação, design, efeitos, formulários e mais. Dentre as opções de formulário, a segunda opção é sobre criar e estilizar um campo de texto, que em inglês é text field.

Sinta-se à vontade para ler esse tutorial, ele é bem curto e menciona que existem dois tipos de widgets para campos de texto: TextField e TextFormField. E qual seria a grande diferença entre eles?

Vamos mostrar para vocês qual é a diferença na prática. Voltando ao nosso projeto, vamos colocar um TextField() como um filho da coluna que criamos.

form_screen.dart:

body: Center(
        child: Container(
                // código omitido…

                child: Column(
                        children: [
                                TextField(),
                        ],
                ),
        ),
),

Feito isso, vamos dar um hot reload (atalho "Ctrl + ") no projeto. No emulador do aplicativo, apareceu a caixa de texto. O que acontece se clicamos nesta área? Ele vai abrir o teclado para que possamos digitar. Podemos digitar o que quisermos. Já é um campo de texto. Que fácil!

E qual seria a grande diferença entre o TextField e o TextFormField? Vamos testar? Vamos criar um TextFormField(), logo abaixo do TextField(). Lembrando que eles estão dentro de um Column, então um vai estar abaixo do outro.

body: Center(
        child: Container(
                // código omitido…

                child: Column(
                        children: [
                                TextField(),
                                TextFormField(),
                        ],
                ),
        ),
),

Vamos dar um hot reload no app para podermos visualizar o TextFormField. Apareceu outro campo de texto logo abaixo, no qual também podemos digitar. Não mudou nada. Se os dois são, para que servem e qual a diferença entre um e outro? A diferença entre esses widgets não é superficial. Na superfície, os dois são bem parecidos, mas o TextFormField tem um parâmetro diferente.

Podemos tanto conferir a documentação do TextFormField ou colocar o mouse em cima do TextField. Dentro desse TextField, existem vários parâmetros. É importante que vocês tenham curiosidade, quando veem dois widgets extremamente parecidos, em conferir a documentação de cada um deles.

O TextField tem controller, decoration e outras várias funções e parâmetros que ele pede. Por exemplo, temos uma função onChanged, que estabelece seu comportamento quando a informação muda.

Agora, vamos procurar o parâmetro que diferencia o TextField do TextFormField.

Agora, vamos passar o mouse em cima do TextFormField para procurar pelo onChanged. Ele possui os mesmos parâmetros e funções que o TextField, apenas alguns adicionais. Abaixo do onChanged, temos algumas outras funções como onTap, onSaved e uma chamada validator.

O TextFormField tem uma função que o TextField não tem, que se chama validação. Temos a habilidade de validar a informação que entra nesse campo de texto.

Qual seria a vantagem de validar? Suponha que você criou um campo de texto que só recebe números de telefone. Portanto, você quer que a pessoa coloque números de telefone nesse campo de texto, mas alguém foi lá e colocou seu nome completo. Isso seria um grande problema, porque números são diferentes de texto. Às vezes, você quer manipular o número de um jeito, e você não pode fazer da mesma forma com um texto.

Por isso, não vale a pena você deixar a sua pessoa usuária colocar letras no lugar de números. Sendo assim, você precisa validar essa informação. Em outras palavras, ter certeza que a informação colocada é a que você está pedindo. Isso dá um grande poder para o TextFormField.

Além disso, quando você tem vários TextFormField juntos dentro de um widget chamado Form, significa que ele vai procurar a validação de todos os campos de texto antes de permitir que você crie o objeto que você deseja. Isso é ideal para o nosso cenário.

Queremos criar uma tarefa que precise de nome, dificuldade e imagem. Logo, precisamos validar se o nome é nome, se a dificuldade é uma dificuldade, se a imagem realmente é uma imagem, antes criarmos essa nossa nova tarefa.

Estilizando o campo de texto

Para o nosso contexto, vale a pena usar o TextFormField e o validator. Então, não vamos usar o TextField por enquanto, podemos apagá-lo.

Agora, queremos estilizar o TextFormField. Primeiramente, vamos mexer em alguns parâmetros dele para começar a entender como esse widget funciona.

O primeiro parâmetro que vamos adicionar será o decoration, que já conhecemos. Só que o decoration do TextFormField precisa de um tipo diferente de widget, que é o InputDecoration(). Dentro desse InputDecoration, podemos colocar as decorações que desejamos. Por exemplo, queremos que essa decoração tenha uma borda estilizada. Para isso, inserimos um border que precisa de um widget específico quando está dentro do InputDecoration, que é o OutlineInputBorder().

Também queremos o ele tenha um texto de dica para que a pessoa usuária saiba qual o que é esse campo de texto facilmente. Para isso, vamos escrever hintText que pede uma string. O hintText desse campo será 'Nome'. Ao dar um hot reload no aplicativo, vai aparecer texto 'Nome' dentro do campo de texto.

Além disso, precisamos que a cor de dentro desse nosso TextField seja diferente. Ele deve ter uma coloração mais branca. Para isso, vamos usar o fillColor, que é a cor que preenche o TextFormField. Essa cor será Colors.white70. Não vamos deixar o branco puro porque é desconfortável ao olho.

Mas, se damos um hot reload, nada muda. Isso porque precisamos de mais um parâmetro dentro do InputDecoration, que é o filled. Ou seja, se ele está preenchido ou não. Nesse caso, vamos colocar true (verdadeiro). Dessa forma, ele já permite que a cor de preenchimento seja mostrada.

Para descobrir esses detalhes, o ideal é passar o mouse por cima do elemento para entender quais são os parâmetros que você pode adicionar. É muita informação, mas quanto mais você entende, mais você consegue manipular.

Com isso, já criamos um TextField com um preenchimento branco e uma borda estilizada. Mas, ainda precisamos acrescentar outros detalhes. Por exemplo, não queremos que a dica nome esteja alinhada à esquerda. Preferimos que esteja bem centralizada. Como fazemos isso? No próprio TextFormField, temos um parâmetro chamado textAlign no qual podemos colocar um TextAlign.center. E aí, o texto vai ficar no centro do jeito que queremos.

Para deixar mais bonito, vamos envolver esse TextFormField com um padding para ter um espaçamento em relação ao contêiner. Basta clicar em "Alt + Enter" com o TextFormField selecionado e escolher "Wrap with Padding" para colocar um espaçamento estilizado.

body: Center(
    child: Container(
        // código omitido…

        child: Column(
            children: [
                Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: TextFormField(
                        textAlign: TextAlign.center,
                        decoration: InputDecoration(
                            border: OutlineInputBorder(),
                            hintText: 'Nome',
                            fillColor: Colors.white70,
                            filled: true,
                        ),
                    ),
                ),
            ],
        ),
    ),
),

Agora já conseguimos escrever alguma informação no campo de texto. Por exemplo, podemos com o nome da tarefa que queremos fazer que é "Assistir Star Wars". Porém, essa informação não está sendo usada, ela não está sendo armazenada.

Essa informação "Assistir Star Wars" não está chegando ao nosso projeto. Ninguém consegue fazer nada com ela. Para resolver isso, precisamos de um parâmetro específico do TextFormField e do TextField também, chamado controller, que é responsável por controlar a informação que está dentro do nosso campo de texto.

No próximo vídeo, vamos te explicar como o controller funciona.

Sobre o curso Flutter: Controller, navegação e estados

O curso Flutter: Controller, navegação e estados possui 196 minutos de vídeos, em um total de 46 atividades. Gostou? Conheça nossos outros cursos de Flutter em Mobile, ou leia nossos artigos de Mobile.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda Flutter acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas