Não use drogas, use Flutter! (talk #2)

Julio Henrique Bitencourt
Flutter — Comunidade BR
11 min readJan 12, 2019

Esse post foi originalmente escrito em blog.juliobitencourt.com e para uma melhor leitura aconselho ir lá, uma vez que o medium é muito limitado.

Esse post é continuação direta do #1, então qualquer coisa da uma olhada lá primeiro.

No último abordamos o talk de apresentação do SDK Flutter, e nesse continuaremos mostrando dessa vez a explicação detalhada do live-coding realizado para demonstração prática da tecnologia. O código completo está no meu GitHub.

O aplicativo que vamos criar é propositalmente bastante simples, embora já iremos utilizar alguns recursos maneiros como uma requisição http e navegação entre telas, além de botar a mão na massa e ver os Widgets funcionando com Material Design! Tentarei explicar o passo a passo, assim como demonstrar o código e o resultado do mesmo.

App que iremos criar finalizado.

Mas antes de colocarmos a mão na massa é importante entender que conforme já discutimos, em flutter tudo são Widgets e blábláblá… Porém, existem 2 tipos de Widgets, Stateless e Stateful.

1>Os Stateless, são aqueles que literalmente não possuem estado, são ‘estáticos’, simplesmente apresentam a informação, como um botão, texto ou ícone.

Botão, texto e ícone são exemplos de Widgets Stateless

2 > Já os Stateful, possuem estado, são dinâmicos, ou seja, o usuário de alguma forma pode iteragir e mudar o seu estado, como um checkbox que está selecionado/não selecionado, ou um slider.

Checkbox e sliders são exemplos de Widgets Stateful

Agora sim, vamos finalmente ver o primeiro trecho de código.

Esse é o mais simples que um app em Flutter pode ser, o método main() é responsável por iniciar a aplicação, no nosso exemplo ele retorna uma função runApp() (sim, pra quem vem do java, o quão foda isso soa?), do pacote material.dart importado, que é responsável por receber um widget e basicamente jogar ele na tela. Text é o nosso primeiro Widget, stateless conforme mencionado anteriormente, basicamente recebe uma String e nesse caso uma diretriz com a direção que queremos renderizar o texto. Flutter é completamente compatível com LTR (left to right) nosso padrão de escrita e RTL (right to left) como a língua árabe.
O modificador => substitui o famoso return {}. Rodando nosso App o resultado é:

Hello Flutter! alinhado acima

O texto é renderizado RTL em um fundo preto, muito difícil de ler ainda no canto da tela, vamos utilizar um Widget simples Center:

Hello Flutter! alinhado ao centro

Esse simples código acima pode mostrar que não precisa um conhecimento prévio pra entender que será renderizado um texto centralizado, Flutter+Dart é uma ótima combinação, e torna a leitura do código intuitiva. Ainda que, após a versão 2 do dart, o new não é mais obrigatório new Text() -> Text().
Para dar uma cara melhor vamos extrair o que temos no runApp():

MyApp é um novo widget, stateless porque não precisamos manter estado. Todo Widget possui o método build(), muito importante para seu ciclo de vida, chamado automaticamente pelo framework sempre que for necessário construí-lo. Dentro do build() retornamos o MaterialApp, pode-se dizer que este é um posto ipiranga na criação dos Apps, possuindo várias funcionalidades que facilitam a definição de temas, rotas de navegação, entre outras coisas como o título da aplicação e o parâmetro home, onde definimos como página inicial o nosso texto anterior, só que dessa vez sem o textDirection, pois o MaterialApp já define que todos os Widgets ‘filhos’ dele serão por default RTL. Mas apenas isso ainda não mudará em nada o nosso resultado visualmente, por isso vamos introduzir mais um Widget:

Trocamos a home pelo Scaffold, que dessa vez é o posto ipiranga para o Material Design e um de seus parâmetros é o appBar, onde usamos o AppBar, que adivinhem, define a implementação de uma App Bar do Material Design. Com o TextStyledamos uma cara melhor ao texto, deixando ele azul com o tamanho da fonte 30.

Hello Flutter! estilizado alinhado ao centro

Nesse nosso app iremos consultar uma lista de pessoas através de uma requisição http que retornará um Json como resposta, esse Json está hospedado no mesmo repositório do GitHub, que está aqui. A estrutura do Json é bem básica, sendo apenas uma lista de objetos, cada um com idPessoa, nome e urlImagem.

Já em Dart, faremos o mapeamento do objeto que irá representar o Json em nosso código.

Considerações em relação ao código acima:

  • A classe Person possui os atributos com seus respectivos tipos conforme o Json.
  • Se pararmos para analisar, qualquer estrutura Json é basicamente um Map de chave/valor, por isso é exatamente assim que ele é representado em Dart. Na linha 8 criamos o construtor fromJson que vamos utilizar para ‘deserializar’ o json, ou seja, transformar para um Objeto, por isso ele recebe um Map de String(a chave será sempre string) e dynamic, que para Dart é como se fosse um tipo genérico, qualquer tipo pode estar em uma variável do tipo dinâmico.
  • Não iremos utilizar o método toJson criado, mas serve a título de conhecimento, ele é responsável por ‘serializar’ o objeto, transformá-lo em Map/Json.

Agora vamos criar a classe PeopleApi que vai ser a responsável por fazer a requisição do Json, tratar o request e nos devolver a lista de pessoas. E para isso, vai ser necessário utilizar o pacote http do Dart. As libs do Dart são hospedadas em pub.dartlang.org e no Flutter são gerenciadas pelo arquivo pubspec.yaml, então vamos adicionar a seguinte dependência nele:

Isso irá adicionar o pacote http ao nosso projeto.

O nosso método _get() é quem vai fazer a requisição, e é com ele que vamos aprender um conceito muito importante para o Flutter. Dart é single-thread, quando queremos executar alguma tarefa em Flutter que pode levar algum tempo indefinido, como acessar um banco de dados ou fazer uma requisição http, precisamos fazer isso de forma assíncrona, caso contrário se utilizarmos a mesma thread principal do aplicativo iríamos travar ela e o usuário não conseguiria mais interagir com o app durante esse tempo.

Dessa forma, colocando async no método, indicamos ao Dart que em algum momento aquele método vai precisar executar uma tarefa de forma assíncrona, mas quando? Bom, para isso tem o nosso amigo await, assim que o Dart encontra essa palavra ele pensa ‘Ok, daqui pra frente o processamento vai ser assíncrono, então vou voltar para a minha thread principal’, e nesse exato momento ele já retorna um resultado do método, mas pera aí, retorna o que?
O Future! Future é basicamente uma promessa, ou seja, em algum momento no futuro (quando o método assíncrono realmente finalizar a execução) esse Futureterá um resultado concreto.

Sendo assim, no nosso _get() utilizamos o método get do http importado do pacote package:http/http.dart, em conjunto com o await para fazer uma requisição na URL com o Json, e após json do pacote dart:convert para fazer o decode do corpo da requisição, que tem como retorno uma List de Map. Isso aí, COM DUAS LINHAAAS, fizemos um request e decode do response.

Em Dart o caractere underscore _ na frente da nomenclatura de variáveis ou métodos indica que é uma variável ou método privado.

O próximo passo agora é pegar a resposta dessa requisição e transformar na nossa lista de pessoas, vamos modificar então o PeopleApi.

Nosso método loadJsonFromApi() que também será async vai basicamente utilizar o _get() criado anteriormente e para cada Map dessa lista retornada vamos então transformá-lo em uma lista de Person através do construtor fromJson, adicionando tudo em uma lista e retornando.
Vamos voltar agora então para a parte legal:

No nosso arquivo inicial extraímos o Text para um novo Widget PeopleWidget, esse é um pouco diferente do que havíamos criado, pois dessa vez é um StatefulWidget, iremos manter o estado das nossas pessoas em tela. Todo Widget Stateful deve obrigatoriamente possuir uma classe que extenda de State e é a responsável por literalmente gerenciar o seu estado. Vamos modificar um pouco o _PeopleWidgetState.

Como já sabemos, o build() é chamado sempre que é necessário construir o Widget e a verdade é que, no ciclo de vida do próprio ele pode ser chamado inúmeras vezes, não faria sentido consultarmos o nosso json no build então (imagina disparar uma chamada na API cada vez que o usuário faz o scroll na lista). Já por outro lado o initState() é chamado apenas uma vez durante a criação do Widget, um bom candidato para fazermos a chamada a nossa API. O método getPessoas() então, que será executado ao criar o Widget, irá chamar a nossa API e como também é async, em algum momento no futuro, irá preencher a nossa lista _people com a lista de pessoas retornada. Após ter o retorno, o método continua e executa o setState(), esse método é específico dos Widgets Stateful e serve para avisar o próprio Widget que ele precisa ser reconstruído (o método build() é executado novamente). No Text do build() temos um operador ternário, caso a lista de pessoas for nula (lembre que o request do Json será assíncrono, sendo assim a primeira vez que o build for executado, ainda não teremos a lista preenchida), mostramos ‘LOADING’ ou caso contrário imprimimos as pessoas da lista. Abaixo o resultado.

Loading mostrado enquanto as pessoas não são carregadas
Lista de pessoas sem toString()

Adicionando o toString() na Person:

Lista de pessoas com o toString() implementado

Ainda está longe do nosso resultado final, vamos mexer mais.

Caso a requisição não tenha sido concluída, ao invés do ‘LOADING’ trocamos para o CircularProgressIndicator que simplesmente mostra um loading circular. Também trocamos a simples impressão da lista pelo ListView, Widget que facilita a criação de listas horizontais ou verticais com Widgets, ele possui um builder onde passamos a quantidade de widgets que irão pertencer a lista e com isso, cada index da lista chama sob demanda a função que definimos no itemBuilder para retornar o Widget correspondente ao index. Retornamos dessa vez apenas o Text com o nome de cada pessoa.

Demonstração do loading circular.

Temos a lista de nomes, mas ainda sem um visual bom, então:

O ListTile já implementa as especificações do Material Design referente a tamanhos e espaçamentos, é um excelente candidato para usarmos na nossa lista, colocamos o Text no seu parâmetro title e o resultado já é mais agradável.

Com o ListTile nossa lista está melhor visualmente.

Em relação a imagem de perfil, também precisamos fazer uma requisição, toda aquela história de async e tudo mais, porém entretanto todavia, o Flutter já nos dá uma implementação pronta para fazer tudo isso com a URL de uma imagem, o NetworkImage, basta unirmos ele com o CircleAvatar que teremos o resultado desejado, também definiremos em backgroundColor que enquanto o request da imagem não for concluído iremos mostrar um fundo azul. Colocamos isso tudo no leading do ListTile:

Enquanto o request da imagem não conclui, um fundo azul é mostrado.

A ideia agora é ao clicar em uma dessas pessoas, navegar para uma nova tela de detalhes, para isso criamos um novo arquivo details.dart com um novo Widget DetailsPeople Stateless (afinal não vamos manter nenhum estado e apenas mostrar informações da pessoa).

Nesse Widget recebemos a pessoa selecionada no construtor, já no build utilizamos novamente o Scaffold junto com o AppBar para termos um visual de MaterialDesign. No body introduzimos um novo Widget, assim como o próprio nome já nos diz o que ele faz, Column simplesmente renderiza seus Widgets filhos em coluna, um abaixo do outro, nesse caso, a nossa imagem e nome.
Tendo esse arquivo criado, precisamos modificar nosso main.dart para chamá-lo.

Envelopamos todo o ListTile em um GestureDetector, que irá nos permitir identificar quando haverá um tap do usuário no mesmo, como parâmetro do onTapdefinimos a função que será executada. Com o Navigator e o MaterialPageRoutepodemos criar uma rota de navegação para a página de detalhes, o pushsimplesmente coloca a nova tela acima da anterior em uma espécie de stack. O resultado disso tudo é a navegação para a nova tela funcionando:

Navegação entre telas funcionando

O Widget Row funciona exatamente igual ao Column, renderizando os Widgets filhos lado a lado em linha, se colocarmos nosso Column dentro de um Row podemos alinhar os Widgets na horizontal com a ajuda do MainAxisAlignment.center. E por sua vez, se colocarmos a Row dentro de um Container é possível definir uma cor de fundo com a ajuda do Color.

Widgets alinhado ao centro na horizontal
Fundo em cinza

O resultado não foi bem o esperado, pois alteramos a cor de todo o fundo, enquanto queremos alterar a cor de fundo apenas do espaço ocupado pela imagem e nome. Acontece que assim como o Row por default ocupa todo o espaço na horizontal nos permitindo alinhar tudo ao centro, o Column ocupa todo o espaço na vertical, e uma vez que o Column está dentro do Container, ele todo acaba se expandindo também. Para corrigir isso utilizamos o parâmetro mainAxisSize do Column com o mainAxisSize: MainAxisSize.min, dessa forma ele ocupará apenas o espaço mínimo necessário. Já aproveitamos para envelopar nossa imagem e nome dentro dos Paddings para melhorar o espaçamento e resultado do nosso header de detalhes da pessoa, assim como aumentar o radius do avatar e alterar o nome com a ajuda do TextStyle.

Cor de fundo e tamanho avatar ajustados
Nome estilizado com a ajuda do TextStyle

Com isso chegamos ao fim esperado da nossa aplicação, que embora seja muito simples conseguimos ver vários conceitos e Widgets que são de fato úteis e necessários para o real desenvolvimento com o Flutter, entretanto, irei apresentar uma última modificação, a cereja do bolo.

Adicionamos um novo Widget Perspective e o objetivo não é explicar ele (o mesmo pode ser visto detalhado no artigo que usei como base), mas sim mostrar o poder que o Flutter dá para o desenvolvedor na customização (lembra da parte #1?) e sob o que de fato é renderizado no canvas, afinal, com poucas linhas alteradas… Não ficou SHOW??

Perspectiva no Flutter!!! o/

É isso, se você chegou até aqui e acompanhou todo o processo, parabéns, espero que eu tenha ao menos despertado seu interesse no SDK, e já sabe, não use drogas, use Flutter!

--

--