Alura > Cursos de Mobile > Cursos de Flutter > Conteúdos de Flutter > Primeiras aulas do curso Flutter com Web API: evoluindo na integração da aplicação

Flutter com Web API: evoluindo na integração da aplicação

Conhecendo o Projeto base e métodos HTTP - Apresentação

Boas-vindas ao curso Flutter com Web API: Avançando na integração! Eu sou Ricarth Lima e serei o seu instrutor neste curso. Eu sou um homem de cabelos cacheados quase crespos, tenho a pele clara, uso um óculos que possui armação de cor escura, tenho um nariz grande, possuo uma barba espessa, e na maior parte deste curso usarei uma camisa na cor azul escuro.

Quais os pré-requisitos para este curso? É necessário ter visto dois cursos com antecedência:

O que produziremos neste curso? Construiremos um Diário, uma aplicação que possuirá telas com diversas funcionalidades:

Atenção: utilizaremos neste curso o Flutter versão 3.0.2 e o DART versão 2.17.3. É fortemente recomendado que você utilize as versões aqui listadas para evitar qualquer tipo de divergência entre o que será praticado no curso e em seu computador. Quando instalarmos as dependências, relembraremos as versões que estamos utilizando.

Quais as funcionalidades técnicas que aprenderemos neste curso? Vamos aprender a:

Estou muito animado para continuarmos essa aventura com o Web API. Até o próximo vídeo!

Conhecendo o Projeto base e métodos HTTP - Conhecendo o projeto base

Agora que conhecemos o funcionamento do curso, é importante entender o projeto que servirá de base para ele. Já que este curso é continuação direta de Flutter com Web API: Integrando sua aplicação, utilizaremos o projeto exatamente do ponto onde paramos no outro curso.

Antes de partirmos para o código e para o projeto rodando no emulador, é importante entendermos a API local que estamos utilizando desde o curso passado.

Por que usamos uma API local? Se utilizarmos uma API na Web, podem ocorrer alterações entre o momento da gravação desta aula e o momento em que você vai assisti-la, como por exemplo: mudanças na forma da integração, mudanças na forma da comunicação e até mesmo cobranças indevidas.

Estamos utilizando o JSON Server, um pacote do Node JS que possui uma API local juntamente com toda a comunicação e todos os testes que faríamos em uma API na Web, mas que faremos localmente em nossa máquina de forma segura. Caso você não possua este pacote, poderá revisar o conteúdo sobre instalação do JSON Server do curso anterior neste link.

Caso tenha interesse em se aprofundar no tema de API, recomendamos o seguinte material: APIs: Gerenciamento e Criação - Hipsters.tech

O banco de dados simulado pelo JSON Server pode ser encontrado no arquivo db.json através do caminho "server > db.json" a partir da pasta raiz no diretório do nosso projeto. Conforme abordamos anteriormente, para rodar a API local, utilizamos o comando json-server --watch --host + seu endereço de IP + db.json. Em caso de precisar reforçar os seus conhecimentos, é possível acessar o conteúdo sobre configuração do JSON Server do curso anterior neste link.

Agora vamos analisar o código do projeto funcionando. Com o emulador aberto, podemos visualizar a tela inicial de "Home" que simula um calendário diário onde ficam salvas todas as entradas feitas pela pessoa usuária. Podemos salvar essas entradas via API no JSON Server e recuperá-las para serem mostradas na tela.

Se clicarmos em alguma data que não possui entrada, somos direcionados a uma tela de preenchimento onde podemos digitar um conteúdo qualquer. Ao clicamos no botão de Salvar, a aplicação nos mostra o registro na tela já atualizada.

Como a aplicação funciona? Na tela do código, vamos acessar o explorador que fica na lateral da janela e examinar a a estrutura de pastas.

Observação: visualizaremos rapidamente os códigos pois eles não são o nosso foco no momento.

A pasta "database" foi empregada anteriormente para armazenar exemplos e não será mais utilizada. A pasta "helpers" contém auxiliadores que nos ajudam a mostrar de forma mais clara alguns conteúdos, como as frases aleatórias (que não serão mais utilizadas) e principalmente o arquivo weekday que formata as datas em português.

A pasta "models" armazena somente um modelo por enquanto: o journal, que representa uma entrada no diário. Cada entrada possui um uuid, um content que representa o conteúdo a ser escrito no diário, uma data de criação e uma data de alteração. O modelo também possui dois construtores: um deles cria o journal (ou diário) vazio enquanto o outro acessa os dados de um map (ou mapa) da Web API, transformando-o em um journal.

O arquivo journal também possui dois métodos:

Pularemos a pasta "screens" por enquanto e seguiremos para a pasta "services". Nela existem dois arquivos:

Para utilizarmos os services acima, dentro da pasta "screens" existem duas pastas que possuem uma tela em cada. A pasta home_screen armazena a tela inicial e é responsável por gerar a lista de entradas — que será atualizada quando necessário. Já a pasta add_journal_screen armazena uma tela simples, responsável por adicionar novas entradas, gerar um novo journal e enviá-lo para a API.

Agora que entendemos em que ponto está o projeto que servirá de base para o nosso curso, implementaremos as novas funcionalidades. Vamos lá?

Conhecendo o Projeto base e métodos HTTP - Configurando tela para alteração

Quando clicamos em uma entrada já existente, não conseguimos lê-la — se for muito extensa — muito menos editá-la. Portanto, a primeira funcionalidade que adicionaremos será a de alteração. Para isso, devemos lembrar que cada bloco que aparece na lista é um journal card (ou cartão de diário), cuja responsabilidade é gerenciar o "clique". Através do explorador de arquivos, acessaremos arquivo do cartão seguindo o caminho "lib >screens > home_screen > widgets > journal_card.dart".

Podemos acessar o código deste arquivo e visualizar a seguinte função: se o journal entregue ao journal card for nulo, será exibida a versão simplificada do bloco com uma função implementada para o "clique". Mas se o *journal não for nulo, será exibida a tela formatada juntamente com o onTap vazio*.

        if (journal != null) {
            return InkWell(
                onTap: () {},
                //Trecho de código omitido
        } else {
            return InkWell (
                onTap: () {
                callAddJournalScreen(context);
            }
            //Trecho de código omitido

Adicionaremos ao onTap vazio uma chamada e salvaremos com "Ctrl + S".

        if (journal != null) {
            return InkWell(
                onTap: () {
                    callAddJournalScreen(context);
                },
                //Trecho de código omitido
        }

Se acessarmos o emulador e clicarmos em qualquer data, com ou sem registros adicionados, será aberta uma nova tela em branco, pois não estamos enviando os dados do registro para o método AddJournalScreen. Nem sempre existirá um journal a ser recuperado pelo método, portanto estamos lidando com um parâmetro opcional que pode ou não ser nulo.

Voltando ao código, iremos até a linha 106 e acessaremos a configuração do método AddJournalScreen. Nele criaremos um parâmetro opcional não nomeado que poderá ser nulo (portanto conterá ponto de interrogação): Journal?. O nome desse parâmetro será journal.

    callAddJournalScreen(BuildContext context, {Journal? journal}){
         Navigator.pushNamed(context, 'add-journal',
            //Trecho de código omitido
      refreshFunction();
    }

Devemos entregar ao método pushNamed um journal que poderá ser vazio ou com informações recebidas por parâmetro. Para isso, criaremos um innerJournal (ou diário interno, em português).

    callAddJournalScreen(BuildContext context, {Journal? journal}){
         Journal innerJournal;
         Navigator.pushNamed(context, 'add-journal',
                //Trecho de código omitido
      refreshFunction();
    }

Se o diário que recebermos não for nulo, preencheremos o innerJournal com as informações dele. Para tal, configuraremos um if.

    callAddJournalScreen(BuildContext context, {Journal? journal}){
         Journal innerJournal;

         if (journal !=null) {
             innerJournal = Journal;
         }

         Navigator.pushNamed(context, 'add-journal',
                //Trecho de código omitido
      refreshFunction();
    }

Para configurar o diário nulo, poderíamos criar um else e inicializá-lo com os argumentos presentes no pushNamed abaixo.

        Navigator.pushNamed (context, 'add-journal',
                arguments: Journal(
                    id: const Uuid().vl(),
                    content: "",
                    createdAt: showedDate,
                    updatedAt: showedDate,
                 )).then((value) { // Journal
            refreshFunction();

No entanto, resolveremos de forma mais rápida, inserindo e inicializando o Journal e também os argumentos do pushNamed diretamente no innerJournal. Eles passam a data, o content vazio e geram um Uuid. Além disso, vamos inserir o innerJournal no lugar dos argumentos que movemos do pushNamed. Em seguida adicionaremos vírgula e quebraremos as linhas para melhor indentação do código.

    callAddJournalScreen(BuildContext context, {Journal? journal}){
        Journal innerJournal = Journal(
                    id: const Uuid().vl(),
                    content: "",
                    createdAt: showedDate,
                    updatedAt: showedDate,
                ); // Journal

        if (journal !=null) {
            innerJournal = Journal;
        }

        Navigator.pushNamed(
            context,
            'add-journal',
                arguments: innerJournal,
                ).then((value) {
            refreshFunction();
  }

Se o conteúdo for diferente de nulo, ele entrará no if e haverá a substituição das informações iniciais pelos dados recebidos por parâmetro. Caso contrário, não entrará neste bloco e continuará a exibir o padrão inicial.

Vamos testar? No emulador, clicaremos em um dos cartões de diário que já contenham informações, mas a tela devolvida pela aplicação ainda está em branco. Isso ocorreu pois o callAddJournalScreen ainda não foi preparado para receber informações externas.

Para resolver esse problema, voltaremos ao código e através do explorador acessaremos o caminho "lib > screens > add_journal_screen". Nele abriremos o arquivo add_journal_screen.dart. Configuraremos o TextEditingController para que a sua propriedade "text" seja substituída pelo innerJournal assim que a tela for iniciada.

Dentro do nosso build, adicionaremos o contentController e o igualaremos ao journal.content. Desta maneira, se entrarmos com um journal preenchido, as informações dele serão armazenadas. Caso contrário, se entrarmos com um journal vazio, ali será armazenada uma string vazia.

    Widget build(BuildContext context) {
    _contentController.text = journal.content;
    return Scaffold(

Vamos conferir se deu certo reiniciando a aplicação e retornando ao emulador. Podemos ver que o problema ainda persiste graças à um detalhe que não configuramos ainda.

Voltaremos ao arquivo journal_card.dart através do explorador e lembraremos do parâmetro journal que passamos como opcional na configuração do método AddJournalScreen.

    callAddJournalScreen(BuildContext context, {Journal? journal}) {

Por ser um parâmetro opcional, quando recebemos um novo cartão diferente de nulo não estamos realizando a chamada com um journal no build. Nesta situação, devemos obrigatoriamente declarar o parâmetro journal dentro do build. Assim haverá o reconhecimento do cartão para que seja realizado o processo de envio. Não é necessário declarar esse parâmetro no código de cartão nulo.

    @override
    Widget build(BuildContext context) {
        if (journal != null) {
            return InkWell(
                onTap: () {
                    callAddJournalScreen(context, journal: journal);
                },

Vamos testar? Após essa configuração, salvaremos o código e voltaremos ao emulador. Quando clicamos em um cartão que possui informações, será aberta uma janela exibindo as informações do cartão.

Editaremos uma informação no emulador, salvaremos e voltaremos ao codigo. Nele abriremos a pasta "server" e acessaremos o arquivo db.json e perceberemos que o código não foi alterado. Ao acessar o nosso Console de Depuração (ou logger) através do ícone na lateral esquerda, encontraremos o erro "id duplicado" conforme exibido abaixo.

Corpo: Error: insert failed, duplicate id

Este erro ocorreu o registro de um novo journal localizado no arquivo add_journal_screen utiliza o serviço register, que por sua vez utiliza o método post. Ou seja, a applicação tentou criar um novo cartão ao invés de modificá-lo.

Se recorrermos aos nossos conhecimentos de HTTP, lembraremos que o post não é utilizado para substituir, e sim o put. Não podemos substituir o post pelo put, pois utilizamos o post quando criamos os journals. Portanto, precisamos criar o put em outro lugar do nosso serviço, ao mesmo tempo em que a tela de add_journal_screen deverá possuir inteligência para discernir entre os métodos de editar e de criar. Realizaremos essa tarefa a seguir.

Sobre o curso Flutter com Web API: evoluindo na integração da aplicação

O curso Flutter com Web API: evoluindo na integração da aplicação possui 238 minutos de vídeos, em um total de 53 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