Não use drogas, use Flutter! (talk #2)
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.
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.
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.
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 é:
O texto é renderizado RTL em um fundo preto, muito difícil de ler ainda no canto da tela, vamos utilizar um Widget simples Center
:
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 TextStyle
damos uma cara melhor ao texto, deixando ele azul com o tamanho da fonte 30.
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 umMap
deString
(a chave será sempre string) edynamic
, 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 Future
terá 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.
Adicionando o toString()
na Person
:
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.
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.
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
:
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 onTap
definimos a função que será executada. Com o Navigator
e o MaterialPageRoute
podemos criar uma rota de navegação para a página de detalhes, o push
simplesmente 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:
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
.
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 Padding
s 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
.
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??
É 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!