Alura > Cursos de Mobile > Cursos de Flutter > Conteúdos de Flutter > Primeiras aulas do curso Flutter com Web API: integrando sua app mobile

Flutter com Web API: integrando sua app mobile

Ajustando o Dashboard - Introdução

Olá pessoal! Eu sou Alex Felipe, instrutor da Alura, e venho apresentar o curso de Flutter com Web API. Para esse treinamento, assumirei que você tem todo o conhecimento do curso de Persistência de Dados Internos utilizando o SQFlight. Como reaproveitaremos o projeto desenvolvido durante esse curso, é recomendado ter feito a implementação de todo o projeto, entendendo suas peculiaridades e dificuldades, e até mesmo tê-lo pronto para então seguirmos sem nenhum problema.

Agora que conhecemos esses pré-requisitos, vamos entender o que será desenvolvido durante o treinamento para, posteriormente, conversarmos sobre conteúdos mais técnicos que serão utilizados por baixo dos panos. Perceba que teremos o mesmo visual inicial do projeto anterior, mas com algumas funcionalidades diferentes. Enquanto antes tínhamos a funcionalidade de contatos, agora teremos uma de transferência e outra que lista as transferências realizadas no uso do aplicativo.

01-transaction

Clicando em "Transfer", acessaremos uma tela na qual ainda mantemos a lista de contatos - ou seja, nossa lista também foi reutilizada para esse novo curso, mas agora com uma nova funcionalidade.

02-transfer

Ao clicarmos em um dos contatos, será aberto um novo formulário para criação de uma transferência.

03-new

Isso nos traz uma nova exigência, que é fazer a comunicação com uma Web API, um serviço online onde as transferências enviadas serão registradas e poderão ser buscadas.

Para visualizarmos as transferências realizadas, voltaremos ao dashboard e acessaremos o Transaction Feed, onde buscaremos todas as transferências que foram cadastradas. Quando não há nenhuma inforação, a mensagem "No transactions found" será exibida para o usuário.

04-feed

Para testarmos essas novas funcionalidades, simularemos uma transferência para o Alex no valor de 2000 reais. Voltando ao Transaction Feed, essa transferência será exibida corretamente.

05-feed

Nesse curso, aprenderemos a fazer tanto a comunicação de mandar as informações para a Web API quanto a de buscá-las. Para termos certeza de que essa comunicação está funcionando, podemos colocar o emulador do Android Studio no movo avião, cortando o acesso à rede. Feito isso, se acessarmos o Transactions Feed, a mensagem "No transactions found" voltará a ser exibida, afinal nenhuma informação foi encontrada.

04-feed

Isso significa que realmente estamos fazendo uma comunicação externa com um serviço online ao invés de trabalharmos com um banco de dados local. Agora que conhecemos o fluxo do produto, vamos entender as técnicas que serão utilizadas por baixo dos panos. Primeiramente, utilizaremos um pacote do próprio Dart, que é justamente o HTTP, para fazermos as comunicações no protocolo HTTP.

Veremos como é feita sua configuração inicial e como configuramos o interceptador para identificarmos o que está acontecendo durante a comunicação, aprenderemos a fazer a conversão dos dados que são enviados e recebidos via HTTP, além de quais cuidados devem ser tomados quando fazemos uma comunicação (seja ela bem sucedida ou não).

Também veremos como aplicar as novas funcionalidades que temos, por exemplo, na lista de contatos, onde o clique agora envia uma informação diferente para o formulário - quando clicamos no Alex, abrimos as informações desse contato, e assim por diante.

Ao longo do nosso treinamento aprenderemos todas essas abordagens, passando por problemas e erros comuns do desenvolvimento, considerando as boas práticas de programação e e conhecendo maneiras mas sucintas de trabalhar com o Dart no Flutter. Bons estudos!

Ajustando o Dashboard - Novas funcionalidades no Dashboard

Agora que o cliente nos apresentou uma nova necessidade, vamos entender o que precisa ser modificado no código para começarmos a implementação. Para isso, consultaremos o documento da proposta de implementação, focado na integração de uma Web API. Logo de cara, perceberemos que o *Dashboard *está de volta, justamente porque agora ele exibirá novas funcionalidades.

Inclusive, podemos comparar a Dashboard desejada com a que temos atualmente, que possui uma única funcionalidade (Contacts) que será substituída por duas outras (Transfer, para transferências, e Transaction Feed, uma lista das transferências realizadas). Na próxima página, que mostra os pormenores da tela de transferência, perceberemos esta é, na verdade, a tela Contacts com um novo nome e um comportamento adicional: ao clicarmos em um dos contatos cadastrados, será aberto um formulário permitindo a inserção de um valor para a transferência. Essa é uma adaptação que também teremos que fazer.

Como já vimos que será necessário modificar bastante o fluxo inicial, vamos adaptar o nosso código para que ele dê suporte a essas múltiplas funcionalidades no dashboard e para que a lista de contatos tenha o novo nome. O primeiro ajuste no código será modificarmos o visual do botão, ou seja, o nome e o ícone. Na classe Dashboard, substituiremos "Contacts" por "Transfer" e o Icons.people por Icons.monetization_on.

children: <Widget>[
  Icon(
    Icons.monetization_on,
    color: Colors.white,
    size: 24.0,
  ),
  Text('Transfer',
      style: TextStyle(
        color: Colors.white,
        fontSize: 16.0,
      )),
],

Feito isso, utilizaremos o Hot Restart para visualizarmos a tela atualizada, que ficou exatamente como desejado.

06-novo

Também modificaremos a classe ContactsList, alterando o Text('Contacts') por Text('Transfer'). Após um novo Hot Restart, faremos a navegação da Dashboard para a tela Transfer, que terá sido atualizada com o novo título.

class ContactsList extends StatelessWidget {

  final ContactDao _dao = ContactDao();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Transfer'),
//...

07-transfer

Terminada essa primeira modificação, agora precisamos adaptar o código para que consigamos adicionar mais de uma funcionalidade. Para isso, necessitamos de uma nova estrutura que mantenha tal funcionalidade, que é justamente todo o código dentro do segundo Padding(), incluindo Material(), InkWell(), Container(), Column(), etc. Minimizaremos então esse código, adicionaremos uma vírgula ao final da linha e pressionaremos "Ctrl + D" para duplicá-lo. Perceba que ficaremos então com dois paddings:

body: Column(
  mainAxisAlignment: MainAxisAlignment.spaceBetween,
  crossAxisAlignment: CrossAxisAlignment.start,
  children: <Widget>[
    Padding(
      padding: const EdgeInsets.all(8.0),
      child: Image.asset('images/bytebank_logo.png'),
    ),
    Padding(...),
    Padding(...),
  ],
),
//...

Ao fazermos o Hot Restart, teremos na tela dois botões - ou seja, duas funcionalidades - em uma mesma coluna na vertical.

08-dois

Porém, na proposta de implementação, a ideia é que as funcionalidades sejam apresentadas horizontalmente. Para isso, precisaremos de outra estrutura, chamada Row() ("linha"), cujo comportamento é muito similar ao de uma coluna, mas no modo horizontal.

Para adicionarmos essa linha, usaremos os recursos do IntelliJ. Após clicarmos em um widget, no caso o Padding(), usaremos o atalho "Alt + Enter" para acessarmos algumas opções, dentre elas "Wrap with Row". Isso basicamente agrupará toda a estrutura selecionada em uma Row(). Em seguida, recortaremos o segundo Padding() com "Ctrl + X" e o colocaremos também dentro da nova Row().

Row(
  children: <Widget>[
    Padding(...),
    Padding(...),
  ],
),
//...

Com essa estrutura pronta, as duas funcionalidades passarão a ser exibidas lado a lado na nossa Dashboard.

09-row

Claro, como replicamos o código, o comportamento ainda não é o esperado (Transfer e Transaction Feed). O ideal seria adaptarmos o código para que essa modificação de conteúdo seja feita facilmente. Pensando nisso, faz sentido extrairmos a estrutura do Padding(), Material(), InkWell() e assim por diante para um widget próprio, que pode ser nomeado, por exemplo, como FeatureItem(), ou seja, um item que representa uma funcionalidade.

Ainda no arquivo dashboard.dart, ao final do código, começaremos a criação desse widget com a estrutura stless seguida de um "Enter", o que criará um StatelessWidget. Como somente a nossa Dashboard utilizará esse recurso, ele será privado, e se chamará _FeatureItem.

class _FeatureItem extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container()
  }
}

Nele, ao invés de Container(), retornaremos toda a estrutura do nosso Padding():

class _FeatureItem extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Padding(...);
  }
}

Agora teremos esse código com um nome mais significativo e, ao invés de utilizarmos os paddings na classe Dashboard, passaremos a utilizar o _FeatureItem(), o widget que foi extraído.

body: Column(
  mainAxisAlignment: MainAxisAlignment.spaceBetween,
  crossAxisAlignment: CrossAxisAlignment.start,
  children: <Widget>[
    Padding(
      padding: const EdgeInsets.all(8.0),
      child: Image.asset('images/bytebank_logo.png'),
    ),
    Row(
      children: <Widget>[
        _FeatureItem(),
        _FeatureItem(),
      ],
    ),
  ],
),
/...

Atualizando a aplicação, ela manterá o mesmo comportamento. Agora podemos indicar quais são os valores esperados nesse Widget. Por exemplo, a string "Transfer" pode ser representada por um name ("nome"), e Icons.monetization_on pode ser representado como icon ("ícone"). Criaremos esses atributos no widget prestando atenção aos seus devidos tipos (String e IconData, respectivamente), e os passaremos em um construtor _FeatureItem().

class _FeatureItem extends StatelessWidget {

  final String name;
  final IconData icon;

  _FeatureItem(this.name, this.icon);
//...

Agora, cada vez que o _FeatureItem() for utilizado, exigiremos que os seus valores sejam enviados. Na primeira chamada,, enviaremos Transfer e Icons.monetization_on, e na segunda Transaction Feed e Icons.description (que é o ícone de "descrição" exibido na proposta de implementação).

Row(
  children: <Widget>[
    _FeatureItem('Transfer', Icons.monetization_on),
    _FeatureItem('Transaction Feed', Icons.description),
  ],
),

Após essa adaptação, teremos um visual bastante semelhar ao que foi apresentado na proposta.

10-proposta

Se clicarmos em "Transfer", a lista de contatos é aberta corretamente. Porém, o mesmo acontece se clicarmos em "Transaction Feed", afinal estamos reutilizando o mesmo código. A seguir trabalharemos de forma que cada funcionalidade tenha sua própria navegação.

Ajustando o Dashboard - Delegando eventos com callbacks

Precisamos modificar o nosso código para que cada uma das suas funcionalidades consiga acessar uma tela específica, já que, como vimos anteriormente, tanto a "Transfer" quanto a "Transaction Feed" atualmente acessam a mesma tela, que é a lista de contatos.

Em _FeatureItem(), que é o código compartilhado por essas funcionalidades, temos em comum a propriedade onTap, que é justamente a identificação de que o widget foi clicado. Dentro dela é feita a navegação para a lista de contatos - ou seja, é aqui que precisaremos atuar. Mas o que deve ser feito para que um código específico seja executado a depender da funcionalidade?

Existem diversas formas de resolver esse problema. Uma delas seria incluirmos mais um argumento no construtor, informando o que a funcionalidade clicada representa, como "Transaction Feed", "Transfer" e assim por diante. Porém, esse tipo de implementação possui um problema: como o código tende a crescer, pode ser que acabemos trabalhando com diversas funcionalidades, exigindo mais responsabilidades do _FeatureItem(), um widget cujo propósito é ser reutilizado e que deveria ter menos responsabilidades.

Pensando nisso, ao invés de mantermos todo o código que toma essas decisões dentro do _FeatureItem(), faz todo sentido trabalharmos com uma outra solução, na qual o Dashboard, após ser notificado de que uma funcionalidade foi clicada, será responsável por tomar a decisão sobre o que deverá ser feito.

Essa implementação pode ser realizada por meio de callbacks, utilizando funções para nos indicar que algo foi clicado e para criar o comportamento esperado. No _FeatureItem(), criaremos um novo atributo cujo tipo será Function, tipo este que determina um callback do Dart. Podemos utilizar um nome como onTap ou onClick para referenciar a ação responsável por sua execução. No caso, utilizaremos onClick para diferenciarmos do onTap que já existe no nosso widget.

Para recebermos o onClick via construtor, usaremos uma técnica um pouco diferente daquilo que já vimos no Flutter, de modo a conhecermos um comportamento comum nesse tipo de abordagem que é o recebimento de callbacks. No construtor, indicaremos que o callback this.onClick será recebido como um parâmetro opcional, o que é feito com chaves ({}).

class _FeatureItem extends StatelessWidget {

  final String name;
  final IconData icon;
  final Function onClick;

  _FeatureItem(this.name, this.icon, {this.onClick});
//...

Agora, na utilização do _FeatureItem(), poderemos mandar também o onClick. Dado que ele representa uma função sem nenhum tipo de argumento, podemos implementá-lo vazio e então definir o seu comportamento.

Row(
  children: <Widget>[
    _FeatureItem(
      'Transfer',
      Icons.monetization_on,
      onClick: () {},
    ),
    _FeatureItem(
      'Transaction Feed',
      Icons.description,
    ),
  ],
),

Agora vamos à implementação "diferente" que foi citada. Ao invés de simplesmente colocarmos um parâmetro opcional, faremos com que o Dart indique para qualquer pessoa que utilize o nosso componente que a implementação do onClick é uma exigência, da mesma maneira que aconteceu quando utilizamos o RaisedButton() ou o FloatingActionButton(), nos quais precisamos implementar o onPress, onTap e assim por diante. Para isso, usaremos a anotação @required.

class _FeatureItem extends StatelessWidget {
  final String name;
  final IconData icon;
  final Function onClick;

  _FeatureItem(this.name, this.icon, {@required this.onClick});
//...

O @required indica que o atributo onClick precisa ser implementado para que o widget funcione da maneira esperada, e pode ser utilizada em vários parâmetros. Feito isso, a IDE passará a informar, na chamada de _FeatureItem(), que o parâmetro onClick é requerido, ainda que o código funcione sem a sua implementação.

Feito o nosso callback, quando identificarmos que o onTap foi executado, bastará executarmos também o onClick(), Dessa forma, ele irá notificar o escopo que implementamos no _FeatureItem() da Dashboard.

child: InkWell(
  onTap: () {
    onClick();
    Navigator.of(context).push(
      (MaterialPageRoute(
        builder: (context) => ContactsList(),
      )),
    );
  },
//...

Para testarmos isso, executaremos um print() simples com a mensagem "transfer was clicked".

_FeatureItem(
  'Transfer',
  Icons.monetization_on,
  onClick: () {
    print('transfer was clicked');
  },
),

Após fazermos o Hot reload e clicarmos no "Transfer" da nossa aplicação, a mensagem "transfer was clicked" será exibida no console, exatamente como planejamos.

I/flutter ( 7236): transfer was clicked

Já se clicarmos em "Transaction Feed", como não implementamos nenhum tipo de ação, receberemos um "null". Agora precisamos adaptar as nossas funcionalidades para que cada uma tenha seu próprio comportamento.

O primeiro passo será removermos a navegação específica do nosso Padding(), deixando apenas o onClick(). Podemos ainda simplificá-lo com uma expressão (arrow function):

return Padding(
  padding: const EdgeInsets.all(8.0),
  child: Material(
    color: Theme.of(context).primaryColor,
    child: InkWell(
      onTap: () => onClick(),
//...

Criaremos então, dentro da classe Dashboard, a função _showContactsList(), que navegará para a lista de contatos. No corpo da função colaremos o código de navegação que criamos anteriormente, e como argumento ela receberá a dependência BuildContext.

void _showContactsList(BuildContext context) {
  Navigator.of(context).push(
    MaterialPageRoute(
      builder: (context) => ContactsList(),
    ),
  );
}

Com isso, quando fizermos o onClick do nosso "Transfer", poderemos chamar o _showContactsList() mandando como argumento o context.

_FeatureItem(
  'Transfer',
  Icons.monetization_on,
  onClick: () {
    _showContactsList(context);
  },
),
//...

Já no "Transaction Feed", faremos de forma um pouco diferente. Usaremos o atalho "Alt + Enter" do IntelliJ para implementar o onClick, que se iniciará como null, e então indicaremos o comportamento esperado. Dado que ainda não temos a tela específica para essa funcionalidade, vamos trabalhar com o print(), passando o texto "transaction feed was clicked" para confirmarmos que tudo está funcionando corretamente.

Após o Hot Restart, voltaremos à aplicação para realizarmos nossos testes. Se clicarmos em "Transfer", seremos corretamente redirecionados para a lista de contatos. Já clicando em "Transaction Feed", a mensagem "transaction feed was clicked" será exibida no console.

I/flutter ( 8617): transaction feed was clicked

Esse tipo de abordagem, delegando responsabilidades de determinados eventos nos nossos widgets, é muito comum e será utilizada toda vez que precisarmos subir algum nível para implementar alguma funcionalidade específica, seja com base em um click ou um gesto.

Para fecharmos essa parte de adição de funcionalidades, vamos verificar o que acontece quando replicamos mais uma feature. Para isso, selecionaremos o _FeatureItem() do nosso Transaction Feed e utilizaremos o atalho "Ctrl + D" para duplicá-lo. Executando novamente a aplicação, teremos um problema, pois estamos ultrapassando o limite da lateral da tela, impedindo o acesso a essa e às demais funcionalidades.

11-lateral

Isso acontece pois o Row() tem um comportamento bem parecido com o do Column(), ou seja, possui um tamanho fixo e não inclui comportamentos de scroll (rolagem). Nesse caso, teremos que fazer algumas adaptações. Uma delas será a adição do widget SingleChildScrollView().

SingleChildScrollView(
  child: Row(
    children: <Widget>[
      _FeatureItem(...),
      _FeatureItem(...),
      _FeatureItem(...),
    ],
  ),
),
//...

Entretanto, com isso, nossa aplicação continuará apresentando o mesmo problema. Isso porque, por padrão, o SingleChildScrollView opera no modo vertical. Sendo assim, precisaremos adaptar a sua direção utilizando a propriedade scrollDirection, que recebe como valor um Axis que permite modificar a sua direção, e que nesse caso definiremos como Axis.horizontal.

SingleChildScrollView(
    scrollDirection: Axis.horizontal,
  child: Row(
    children: <Widget>[
      _FeatureItem(...),
      _FeatureItem(...),
      _FeatureItem(...),
    ],
  ),
),
//...

Dessa vez conseguiremos incluir corretamente o comportamento de scroll, permitindo o acesso às outras funcionalidades na horizontal, tornando nossa aplicação mais flexível.

12-scroll

Claro, também é possível trabalhar com um conceito que já vimos anteriormente, que é o ListView(). Para isso, removeremos o SingleChildScrollView usando "Alt + Enter" e clicando na opção "Replace widget with its children". Em seguida, modificaremos o Row() para ListView(), o que fará com que nossa aplicação deixe de exibir as funcionalidades (já que ainda precisamos determinar um tamanho para o ListView()).

13-sumiu

Antes de adaptarmos o tamanho, adicionaremos a propriedade scrollDirection, novamente com Axis.horizontal, para que as funcionalidades sejam exibidas na horizontal.

ListView(
scrollDirection: Axis.horizontal,
    children: <Widget>[
        _FeatureItem(...),
        _FeatureItem(...),
        _FeatureItem(...),
    ],
),
//...

Entretanto, elas ainda não serão exibidas corretamente na tela. Como não definimos um tamanho, o ListView() tende a ocupar o máximo que ele pode da tela, impedindo a visualização. Para resolvermos isso, o colocaremos dentro de um Container ("Alt + Enter" seguido de "Wrap with Container") e estabeleceremos um height de 100, o mesmo que estávamos utilizando para nossas features.

Container(
  height: 100,
  child: ListView(
    scrollDirection: Axis.horizontal,
    children: <Widget>[
      _FeatureItem(...),
      _FeatureItem(...),
      _FeatureItem(...),
    ],
  ),
),

Feito isso, as funcionalidades passarão a ser exibidas na tela, ainda que não atendam exatamente às nossas necessidades por não estarem no tamanho ideal.

14-funcs

Corrigiremos isso removendo a propriedade height do Container() que está localizado no nosso _FeatureItem() e alterando o valor dessa mesma propriedade, agora no Container() que contém o ListView(), para 120.

15-correto

Com isso, teremos conseguindo implementar as nossas duas funcionalidades utilizando soluções que também nos permitem adicionar diversas outras, cada uma mantendo o seu próprio clique. Antes de partirmos para o próximo passo, removeremos a funcionalidade duplicada.

Sobre o curso Flutter com Web API: integrando sua app mobile

O curso Flutter com Web API: integrando sua app mobile possui 152 minutos de vídeos, em um total de 45 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!

Plus

  • Acesso a TODOS os cursos da plataforma

    Mais de 1200 cursos completamente atualizados, com novos lançamentos todas as semanas, em Programação, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.

  • Alura Challenges

    Desafios temáticos para você turbinar seu portfólio. Você aprende na prática, com exercícios e projetos que simulam o dia a dia profissional.

  • Alura Cases

    Webséries exclusivas com discussões avançadas sobre arquitetura de sistemas com profissionais de grandes corporações e startups.

  • Certificado

    Emitimos certificados para atestar que você finalizou nossos cursos e formações.

  • Alura Língua (incluindo curso Inglês para Devs)

    Estude a língua inglesa com um curso 100% focado em tecnologia e expanda seus horizontes profissionais.

12X
R$85
à vista R$1.020
Matricule-se

Pro

  • Acesso a TODOS os cursos da plataforma

    Mais de 1200 cursos completamente atualizados, com novos lançamentos todas as semanas, em Programação, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.

  • Alura Challenges

    Desafios temáticos para você turbinar seu portfólio. Você aprende na prática, com exercícios e projetos que simulam o dia a dia profissional.

  • Alura Cases

    Webséries exclusivas com discussões avançadas sobre arquitetura de sistemas com profissionais de grandes corporações e startups.

  • Certificado

    Emitimos certificados para atestar que você finalizou nossos cursos e formações.

  • Alura Língua (incluindo curso Inglês para Devs)

    Estude a língua inglesa com um curso 100% focado em tecnologia e expanda seus horizontes profissionais.

12X
R$120
à vista R$1.440
Matricule-se
Conheça os Planos para Empresas

Acesso completo
durante 1 ano

Estude 24h/dia
onde e quando quiser

Novos cursos
todas as semanas