Alura > Cursos de Mobile > Cursos de Flutter > Conteúdos de Flutter > Primeiras aulas do curso Flutter: entendendo comunicação HTTP e utilizando Web API

Flutter: entendendo comunicação HTTP e utilizando Web API

Autenticando transferência - Introdução

Boas vindas! Sou Alex Felipe, instrutor da Alura e apresentarei a vocês o segundo curso de Flutter com Web API. Como se trata de uma continuação, presumimos que o aluno tenha assimilado todo o conteúdo do primeiro curso, também disponível na plataforma. Por meio dos conhecimentos adquiridos, faremos melhorias no projeto que vínhamos trabalhando - o Bytebank - e aprenderemos novos conteúdos.

O foco deste curso será basicamente a funcionalidade de transferência do banco digital. Embora a funcionalidade esteja operando, existem alguns cuidados necessários no que diz respeito ao envio de dados para uma Web API.

Na versão inicial do projeto, conseguíamos realizar uma transferência bancária para um contato qualquer de maneira direta. Agora, atualizaremos a aplicação e implementaremos uma tela de autenticação, conhecida como dialog, um recurso esperado em aplicativos de negócios financeiros.

dialog

Personalizaremos o aspecto visual da tela dialog, como o espaçamento entre caracteres, além de ocultá-los e limitar quantos podem ser inseridos na caixa de diálogo. Caso o usuário digite a senha correta, exibiremos um feedback visual de carregamento do processo, indicando que uma informação está sendo enviada, e então uma tela de confirmação da transferência. Com isso, a experiência do usuário final será muito melhorada em relação à versão antiga do projeto.

confirmação

Contudo, devemos nos lembrar do conceito de caminho feliz e da possibilidade do usuário seguir outros percursos no dia-a-dia, como tentar fazer uma transferência com valor ou senha inválidos, falta de acesso à rede, entre outros problemas. Nosso aplicativo deve ser inteligente o suficiente para determinar a ocorrência de erros e notificá-los ao usuário de maneira adequada.

Existem algumas particularidades do código que precisaremos compreender para executar as ações citadas, e são esses conteúdos que aprenderemos no curso. Vamos lá?

Autenticando transferência - Criando o dialog de autenticação

Começaremos com a implementação da tela que autenticará a transferência. No fluxo de navegação do aplicativo, ao clicarmos em "Transfer", ganhamos acesso a uma lista de contatos. Selecionaremos um desses contatos para realizar a transferência bancária, acessaremos o formulário no qual definiremos o valor da transação, e então clicaremos no botão "Transfer".

Nossa tarefa é autenticar essa movimentação, apresentando uma nova tela que solicita uma senha para o usuário. Uma vez que a senha digitada for verificada na Web API, a transferência poderá ser executada.

O primeiro passo será criarmos o componente visual da tela que solicitará a senha. Considerando as técnicas que já conhecemos no Flutter, o percurso normal seria criarmos um novo arquivo no pasta "screens". Porém, como vimos na apresentação, estamos lidando com um novo component conhecido como dialog. Ao consultarmos a documentação do Flutter, no tópico Material Components widgets, especificamente no tema Dialogs, alerts and panels , encontraremos a seguinte definição:

AlertDialog

Alerts are urgent interruptions requiring acknowledgemment that inform the user about a situation. The AllerDialog widget implements ths component.

Para realizar a implementação, precisaremos ter acesso a um arquivo que representará essa caixa de diálogo, que também poderá ser reutilizada em outras telas. Com base na estrutura que realizamos no projeto, podemos criaremos o arquivotransaction_auth_dialog no diretório "components". Feito isso, vamos inserir sua estrutura inicial, criando um StatelessWidget chamado TransactionAuthDialog, além de fazer a importação do material.dart.

import 'package:flutter/material.dart';

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

Como se trata de uma implementação mais relacionada ao visual, e não a uma regra de negócios, a sugestão é modificar a apresentação no MaterialApp() em main.dart para trabalharmos diretamente com o TransactionAuthDialog().

class BytebankApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primaryColor: Colors.green[900],
        accentColor: Colors.blueAccent[700],
        buttonTheme: ButtonThemeData(
          buttonColor: Colors.blueAccent[700],
          textTheme: ButtonTextTheme.primary,
        )
      ),
      home: TransactionAuthDialog(),
    );
  }
}

Feita essa mudança no código, executaremos o aplicativo para verificarmos se ele apresenta o comportamento esperado, que é justamente uma tela preta.

tela preta

De volta à classe TransactionAuthDialog, passaremos a retornar um AlertDialog(), que seria a estrutura inicial do nosso componente e que ainda não possui nenhum conteúdo.

class TransactionAuthDialog extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return AlertDialog();
  }
}

Na documentação do Fluter, verificaremos a existência de diversas propriedades que podem ser utilizadas para preencher as informações, como título, botões e conteúdo. Primeiramente adicionaremos o título (title), usando o widget Text() para passarmos o texto "Authenticate".

import 'package:flutter/material.dart';

class TransactionAuthDialog extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
        return AlertDialog(
        title: Text('Authenticate'),
        ); 
    }
}

Feita a inserção, a mensagem "Authenticate" será exibida na tela.

titulo

Em seguida, adicionaremos o conteúdo que será representando pelo TextField(). Usaremos também a propriedade actions, que recebe um array de widgets para, por exemplo, exibir mais de um botão. Na documentação, vemos que os botões do dialog possuem uma aparência diferente, e geralmente não acompanham uma borda ou preenchimento. O material nos disponibiliza o FlatButton(), que tem exatamente esse tipo de implementação.

Existe botão exige duas propriedades: onPressed, já visto no RaisedButton e que no momento receberá a impressão da mensagem "confirm"; e child que receberá um texto indicativo sobre a ação do botão, isto é Confirm.

return AlertDialog(
  title: Text('Authenticate'),
  content: TextField(),
  actions: <Widget>[
    FlatButton(
      onPressed: () {
        print('confirm');
      },
      child: Text('Confirm'),
    )
  ],
);

Como resultado, teremos acesso apo campo de texto e ao botão de confirmação.

confirmação

Se clicarmos em "Confirm", a mensagem "confirm" é impressa no console. Ou seja, conseguimos cumprir essa etapa da construção do dialog. Resta ainda inserir o botão de cancelamento da ação, porque até o presente momento ela só pode ser confirmada. Aproveitaremos a estrutura do FlatButton() e modificaremos as informações.

actions: <Widget>[
  FlatButton(
    onPressed: () {
      print('cancel');
    },
    child: Text('Cancel'),
  ),
  FlatButton(
    onPressed: () {
      print('confirm');
    },
    child: Text('Confirm'),
  ),
],

Como resultado, os dois botões serão exibidos na tela e, ao clicados, imprimirão seus respectivos textos no console.

cancel

Para finalizar a parte visual, faremos algumas modificações na área de texto. Perceba que atualmente não temos um campo apropriado para inserção de senhas numéricas, afinal ele não limita caracteres, os exibe diretamente na tela e tem um visual diferente da proposta. O primeiro passo da nossa estilização será ocultar os caracteres, como é padrão quando digitamos senhas. Para tanto, utilizaremos a propriedade obscureText do componente TextField(). Essa propriedade, por padrão, recebe um booleano false. Se passarmos true, o texto do campo será ocultado.

content: TextField(
  obscureText: true,
),

01-obscure

A próxima ação será limitar o número de caracteres, já que nossa regra de negócio prevê apenas quatro. Isso será feito com a propriedade maxLength, também do TextField() e que espera um tipo inteiro indicando o número máximo de caracteres. Nesse caso, usaremos 4.

content: TextField(
  obscureText: true,
  maxLength: 4,
),

Ao testarmos as alterações, notaremos que a nova propriedade notifica o usuário do número de caracteres excedentes, e a cor do campo de texto se tornará vermelha, já que existiam 16 caracteres quando implementamos a propriedade. Contudo, ao apagarmos o texto, não conseguiremos digitar mais que 4 caracteres novamente.

irregularidade

Aumentaremos o tamanho do texto por meio da propriedade style e do componenteTextStyle(). Já fizemos modificações semelhantes em outros componentes e a lógica permanece a mesma. Começaremos pelo fontSize, aumentando a fonte para o valor 64 (que também é de base 8). Ainda no TextField(), usaremos a propriedade textAlign recebendo TextAlign.center para alinharmos o texto ao centro do widget. Por último, alteraremos o espaçamento entre os caracteres usando, no TextStyle(), a propriedade letterSpacing com valor 32.

content: TextField(
  obscureText: true,
  maxLength: 4,
  style: TextStyle(fontSize: 64, letterSpacing: 32),
  textAlign: TextAlign.center,
),

Como resultado, nosso campo de texto passará a exibir os caracteres em tamanho maior, centralizados no campo de texto e com o espaçamento adequado entre eles.

espacamento

O aspecto visual está bem mais interessante, contudo o formulário ainda recebe caracteres não numéricos, o que foge da regra de negócios do nosso aplicativo. Por meio da propriedade keyboradType, acrescentaremos a constante TextInputType.number, fazendo com que a tela exiba somente o teclado numérico.

content: TextField(
  obscureText: true,
  maxLength: 4,
  style: TextStyle(fontSize: 64, letterSpacing: 32),
  textAlign: TextAlign.center,
  keyboardType: TextInputType.number,
),

03-keyboard

Para finalizarmos essa primeira parte visual, faremos com que o campo de senha seja envolto por bordas. Para isso, incluiremos a propriedade decoration recebendo InputDecoration(), no qual teremos acesso a algumas propriedades que podem modificar as bordas, como border. Para criarmos a borda que nos foi passada na proposta de implementação, chamaremos uma instância de OutlineInputBorder().

content: TextField(
  obscureText: true,
  maxLength: 4,
  decoration: InputDecoration(
    border: OutlineInputBorder()
  ),
  style: TextStyle(fontSize: 64, letterSpacing: 32),
  textAlign: TextAlign.center,
  keyboardType: TextInputType.number,
),

Assim teremos um aspecto visual bastante similar ao da proposta de implementação.

final

Conseguiremos, também, entregar ao usuário uma tela (um dialog) que solicitará uma senha. A seguir, começaremos a integração dessa tela com o formulário de transferência.

Autenticando transferência - Integrando dialog de autenticação

Já temos o dialog para realizar a autenticação da transferência bancária, mas ainda falta integrá-lo com o formulário. O primeiro passo antes de modificarmos o código será retornar ao Dashboard() para acessarmos o fluxo natural do aplicativo e assim realizar as edições esperadas. Em main.dart substituiremos TransactionAuthDialog() por Dashboard() e então executaremos o aplicativo para entrarmos na tela de formulário.

Na classe TransactionForm temos acesso a todo código do formulário, e nosso interesse nesse momento é trabalhar na ação do onPressed, que cria a transferência e depois chama a Web API. Como faremos o processo de integração com a tela do dialog, comentaremos todo o trecho relativo à execução da Web APÌ.

child: RaisedButton(
  child: Text('Transfer'), onPressed: () {
    final double value = double.tryParse(_valueController.text);
    final transactionCreated = Transaction(value, widget.contact);
//    _webClient.save(transactionCreated).then((transaction) {
//     if(transaction != null){
//       Navigator.pop(context);
//     }
//    });
},
),

Uma das linhas de raciocínio que poderíamos seguir é simplesmente chamar o TransactionAuthDialog(). Porém, ao executarmos o aplicativo e clicarmos no botão "Transfer", na acontecerá. Isso porque é necessário fazermos uma chamada no Flutter para que esse componente apresente alguma coisa, da mesma maneira que quando utilizamos o Navigator.

Neste caso, utilizaremos uma função chamada showDialog(), que receberá o contexto e o builder, que seria o responsável para apresentar a informação.

No builder, teremos uma função que recebe um BuildContext e retorna Widget, e é justamente este retorno que deveremos apresentar como dialog, ou seja, o nosso TransactionAuthDialog(). Faremos a implementação desta função chamando-a de context e retornando, dentro dela, o TransactionAuthDialog().

child: RaisedButton(
  child: Text('Transfer'), onPressed: () {
    final double value = double.tryParse(_valueController.text);
    final transactionCreated = Transaction(value, widget.contact);
    showDialog(context: context, builder: (context) {
      return TransactionAuthDialog();
    });
},
),

Testando a aplicação, a caixa dialog será exibida ao clicarmos no botão "Transfer".

dialog exibido

Porém, o clique em "Confirm" não nos dará acesso a nenhum tipo de conteúdo ou ação. Sendo assim, o próximo passo será pegarmos a informação recebida nesse campo. Note também que a caixa de texto aumenta conforme preenchemos as informações. Podemos ajustar isso diminuindo o letterSpacing do nosso TransactionAuthDialog para 24.

style: TextStyle(fontSize: 64, letterSpacing: 24),

Para coletarmos a senha no dialog, lançaremos mão da mesma técnica utilizada nos featuredItem, isto é, usaremos um callback indicando que algo foi clicado. Ainda em TransactionAuthDialog, criaremos uma função chamada onConfirm. Dado que iremos enviar uma informação, modificaremos o tipo dessa Function para que ela receba como valor uma String. Em seguida, receberemos essa função pelo construtor TransactionAuthDialog().

class TransactionAuthDialog extends StatelessWidget {
  final Function(String) onConfirm;

  TransactionAuthDialog({
    @required this.onConfirm,
  });

Precisaremos de um controlador de modo a recuperarmos a informação no campo de texto, algo que só será possível com um StatefulWidget. Sendo assim, colocaremos o cursor sobre a definição de StatelessWidget, pressionaremos "Alt + Enter" e clicaremos na opção "Convert to StatefulWidget", operando essa conversão automaticamente.

class TransactionAuthDialog extends StatefulWidget {
  final Function(String) onConfirm;

  TransactionAuthDialog({
    @required this.onConfirm,
  });

  @override
  _TransactionAuthDialogState createState() => _TransactionAuthDialogState();
}

Feito isso, em _TransactionAuthDialogState, criaremos um TextEditingController chamado _passwordController indicando que ele representa uma instância de TextEditingController().

class _TransactionAuthDialogState extends State<TransactionAuthDialog> {

  final TextEditingController _passwordController = TextEditingController();
//...

Feito isso, adicionaremos o novo controlador à propriedade controller de TextField():

content: TextField(
  controller: _passwordController,
  obscureText: true,
  maxLength: 4,
  decoration: InputDecoration(border: OutlineInputBorder()),
  style: TextStyle(fontSize: 64, letterSpacing: 24),
  textAlign: TextAlign.center,
  keyboardType: TextInputType.number,
),

O último passo será fazer com que o botão "Confirm" execute alguma ação. No FlatButton() que representa esse botão, ao invés de simplesmente imprimirmos uma mensagem, passaremos a executar widget.onConfirm() enviando a string recebida em _paswordController.text. Dessa maneira, conseguiremos delegar a informação obtida no dialog para quem o está utilizando.

FlatButton(
  onPressed: () {
    widget.onConfirm(_passwordController.text);
  },
  child: Text('Confirm'),
),

Em TransactionForm, a chamada de TransactionAuthDialog() passará a exigir a implementação do onConfirm, que já vem recebendo uma String cujo significado não está definido. Como queremos uma informação mais precisa, retornaremos ao TransactionAuthDialog, onde está o nosso callback, e indicaremos o nome para esse parâmetro - nesse caso, password.

class TransactionAuthDialog extends StatefulWidget {
  final Function(String password) onConfirm;

  TransactionAuthDialog({
    @required this.onConfirm,
  });

Dessa maneira a implementação terá informações mais precisas e com significado. Para verificarmos se nossa lógica está funcionando e realmente estamos recebendo a informação esperada, faremos um print() da senha recebida.

showDialog(context: context, builder: (context) {
  return TransactionAuthDialog(onConfirm: (String password) {
    print(password)
  },);
});

Após as modificações, se reiniciarmos a aplicação com o teclado do emulador aberto, pode ser que a Dashboard() apresente um problema de exibição. Esse problema pode ser evitado adicionando um SingleChildScrollView à Dashboard(), mas isso não é estritamente necessário. Acessaremos a tela de transferências, clicaremos em "Transfer" e preencheremos o nosso dialog com o valor 9999. Com isso, teremos a saída no console:

I/flutter ( 5294): 9999

Se testarmos outros valores, eles também serão impressos corretamente. Porém, após o clique em "Confirm", o dialog continua sendo apresentado na tela. Para corrigirmos isso, utilizaremos o Navigator, que ficará responsável por remover a tela de cima da pilha de navegação do aplicativo. Assim, após a chamada de onConfirm(), faremos um Navigator.pop() passando o contexto necessário.

FlatButton(
  onPressed: () {
    widget.onConfirm(_passwordController.text);
    Navigator.pop(context);
  },
  child: Text('Confirm'),
),

Também adicionaremos esse comportamento ao botão responsável pelo cancelamento.

actions: <Widget>[
  FlatButton(
    onPressed: () => Navigator.pop(context),
    child: Text('Cancel'),
  ),
  FlatButton(
    onPressed: () {
      widget.onConfirm(_passwordController.text);
      Navigator.pop(context);
    },
    child: Text('Confirm'),
  ),
],

Com isso, conseguiremos interagir com a caixa dialog de modo que ela desapareça após clicarmos em "Confirm" ou "Cancel".

O próximo passo ser recuperarmos o conteúdo do formulário de transferência. Ou seja, ao clicarmos em "Confirm", o código do nosso Web Client, que antes havíamos comentado, deverá ser executado.

child: RaisedButton(
  child: Text('Transfer'), onPressed: () {
    final double value = double.tryParse(_valueController.text);
    final transactionCreated = Transaction(value, widget.contact);
    showDialog(context: context, builder: (context) {
      return TransactionAuthDialog(onConfirm: (String password) {
        _webClient.save(transactionCreated).then((transaction) {
          if(transaction != null){
            Navigator.pop(context);
          }
        });
      },);
    });
},
),

Porém, também precisaremos mandar a senha recebida no dialog. Na chamada de save(), além da transactionCreated, enviaremos um password. Usaremos o atalho "Alt + Enter" e a opção "Add required parameter" para adicionarmos o password como parâmetro dessa função. Nos headers, deixaremos de enviar a senha fixa 1000 e passaremos a enviar a senha recebida por parâmetro.

Future<Transaction> save(Transaction transaction, String password) async {
  final String transactionJson = jsonEncode(transaction.toJson());

  final Response response = await client.post(baseUrl,
      headers: {
        'Content-type': 'application/json',
        'password': password,
      },
      body: transactionJson);

  return Transaction.fromJson(jsonDecode(response.body));
}

Testaremos as modificações na aplicação. Ao clicarmos tanto em "Confirm" quanto em "Cancel", a caixa de dialog desaparecerá como esperávamos. Ao realizarmos uma transferência (por exemplo, 300 para o contato Alex com a senha 100)) e clicarmos em "Confirm", essa transação será registrada na tela Transactions Feed.

transaction feed

Além disso, se errarmos a senha, a transação não será registrada. Dessa maneira, conseguimos realizar a integração entre o formulário e o dialog para realizar a nossa autenticação, e no decorrer do curso vamos aprimorar cada vez mais essa abordagem.

Sobre o curso Flutter: entendendo comunicação HTTP e utilizando Web API

O curso Flutter: entendendo comunicação HTTP e utilizando Web API possui 130 minutos de vídeos, em um total de 39 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